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"), } }