verstak/cmd/verstak-gui/bindings_worklog.go

214 lines
6.1 KiB
Go

package main
import (
"fmt"
"os"
wailsruntime "github.com/wailsapp/wails/v2/pkg/runtime"
"verstak/internal/core/worklog"
syncsvc "verstak/internal/core/sync"
)
func (a *App) ListWorklog(nodeID string) ([]WorklogDTO, error) {
list, err := a.worklog.ListByNode(nodeID)
if err != nil {
return nil, err
}
return toWorklogDTOs(list), nil
}
func (a *App) CreateWorklog(nodeID, summary string, minutes int) (*WorklogDTO, error) {
return a.CreateWorklogFull(nodeID, summary, "", "", minutes, false, false)
}
func (a *App) CreateWorklogFull(nodeID, summary, details, date string, minutes int, approximate, billable bool) (*WorklogDTO, error) {
if date == "" {
entry, err := a.worklog.Add(nodeID, summary, details, minutes, approximate, billable)
if err != nil {
return nil, err
}
_ = a.sync.RecordOp(syncsvc.EntityWorklog, entry.ID, syncsvc.OpCreate, worklogPayload(entry))
return entryToDTO(entry), nil
}
entry, err := a.worklog.AddWithDate(nodeID, summary, details, date, minutes, approximate, billable)
if err != nil {
return nil, err
}
_ = a.sync.RecordOp(syncsvc.EntityWorklog, entry.ID, syncsvc.OpCreate, worklogPayload(entry))
return entryToDTO(entry), nil
}
// --- report bindings ---
func (a *App) ListWorklogReport(dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) ([]worklog.ReportRow, error) {
f := buildWorklogFilter(dateFrom, dateTo, nodeID, includeChildren, billableFilter, approxFilter)
rows, err := a.worklog.ListReport(f)
if err != nil {
return nil, err
}
a.worklog.BuildReportPaths(rows)
return rows, nil
}
func (a *App) WorklogReportSummary(dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) (*worklog.ReportSummary, error) {
f := buildWorklogFilter(dateFrom, dateTo, nodeID, includeChildren, billableFilter, approxFilter)
return a.worklog.Summary(f)
}
func (a *App) ExportWorklogCSV(dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) (string, error) {
f := buildWorklogFilter(dateFrom, dateTo, nodeID, includeChildren, billableFilter, approxFilter)
return a.worklog.ExportCSV(f)
}
func (a *App) ExportWorklogMarkdown(dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) (string, error) {
f := buildWorklogFilter(dateFrom, dateTo, nodeID, includeChildren, billableFilter, approxFilter)
return a.worklog.ExportMarkdown(f)
}
func (a *App) ExportWorklogPDF(dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) ([]byte, error) {
f := buildWorklogFilter(dateFrom, dateTo, nodeID, includeChildren, billableFilter, approxFilter)
return a.worklog.ExportPDF(f)
}
func boolPtr(s string) *bool {
switch s {
case "yes":
v := true
return &v
case "no":
v := false
return &v
default:
return nil
}
}
func buildWorklogFilter(dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) worklog.ReportFilter {
return worklog.ReportFilter{
DateFrom: dateFrom,
DateTo: dateTo,
NodeID: nodeID,
IncludeChildren: includeChildren,
Billable: boolPtr(billableFilter),
Approximate: boolPtr(approxFilter),
}
}
// SaveWorklogReport generates a worklog report and opens a SaveFileDialog.
func (a *App) SaveWorklogReport(format, dateFrom, dateTo, nodeID string, includeChildren bool, billableFilter, approxFilter string) (string, error) {
f := buildWorklogFilter(dateFrom, dateTo, nodeID, includeChildren, billableFilter, approxFilter)
var data []byte
var ext string
switch format {
case "csv":
s, err := a.worklog.ExportCSV(f)
if err != nil {
return "", err
}
data = []byte(s)
ext = ".csv"
case "markdown":
s, err := a.worklog.ExportMarkdown(f)
if err != nil {
return "", err
}
data = []byte(s)
ext = ".md"
case "pdf":
var err error
data, err = a.worklog.ExportPDF(f)
if err != nil {
return "", err
}
ext = ".pdf"
default:
return "", fmt.Errorf("unknown format: %s", format)
}
from := dateFrom
if from == "" {
from = "all"
}
to := dateTo
if to == "" {
to = "all"
}
defaultName := fmt.Sprintf("verstak-worklog-%s--%s%s", from, to, ext)
path, err := wailsruntime.SaveFileDialog(a.ctx, wailsruntime.SaveDialogOptions{
DefaultFilename: defaultName,
Filters: []wailsruntime.FileFilter{
{DisplayName: format, Pattern: "*" + ext},
},
})
if err != nil {
return "", err
}
if path == "" {
return "", fmt.Errorf("отменено пользователем")
}
if err := os.WriteFile(path, data, 0o644); err != nil {
return "", fmt.Errorf("не удалось сохранить файл: %w", err)
}
return fmt.Sprintf("Отчёт сохранён: %s", path), nil
}
// GetWorklogEntryEvents returns activity events linked to a worklog entry.
func (a *App) GetWorklogEntryEvents(entryID string) ([]EventDTO, error) {
rows, err := a.db.Query(
`SELECT e.id, e.node_id, e.event_type, e.target_type, e.target_id, e.target_path,
e.title, COALESCE(e.metadata,''), e.created_at
FROM activity_events e
JOIN worklog_entry_events wle ON wle.event_id = e.id
WHERE wle.entry_id = ?
ORDER BY e.created_at ASC`, entryID)
if err != nil {
return nil, err
}
defer rows.Close()
var out []EventDTO
for rows.Next() {
var d EventDTO
if err := rows.Scan(&d.ID, &d.NodeID, &d.EventType, &d.TargetType,
&d.TargetID, &d.TargetPath, &d.Title, &d.DetailsJSON, &d.CreatedAt); err != nil {
return nil, err
}
out = append(out, d)
}
return out, rows.Err()
}
// --- helpers ---
func toWorklogDTOs(list []worklog.Entry) []WorklogDTO {
result := make([]WorklogDTO, len(list))
for i := range list {
result[i] = *entryToDTO(&list[i])
}
return result
}
func entryToDTO(e *worklog.Entry) *WorklogDTO {
mins := 0
if e.Minutes != nil {
mins = *e.Minutes
}
return &WorklogDTO{
ID: e.ID,
NodeID: e.NodeID,
Summary: e.Summary,
Minutes: mins,
Date: e.Date,
Details: e.Details,
Approximate: e.Approximate,
Billable: e.Billable,
Source: e.Source,
CreatedAt: e.CreatedAt.Format("2006-01-02T15:04:05Z"),
}
}