Today view and Inbox section: dynamic query, not stored sections
- ListTodayView() on backend: queries nodes created or updated today
using local timezone day boundaries
- todayBoundaries() helper returns start/end of current day in RFC3339
- Section validation: Create() rejects today and inbox as node sections
- validSections moved from repository.go to types.go with IsValidSection
and IsServiceSection helpers
- Frontend selectSection('today') calls ListTodayView instead of
ListNodesBySection('today')
- FAB (create node) hidden in today and inbox sections
- CreateNode in app.go guarded against today/inbox sections
- types.go: today/inbox defined as service sections (sidebar only)
This commit is contained in:
parent
a4ae22c445
commit
69891e395c
|
|
@ -143,6 +143,15 @@ func (a *App) ListNodesBySection(section string) ([]NodeDTO, error) {
|
|||
return toNodeDTOs(list), nil
|
||||
}
|
||||
|
||||
// ListTodayView returns nodes created or updated today — a dynamic day view.
|
||||
func (a *App) ListTodayView() ([]NodeDTO, error) {
|
||||
list, err := a.nodes.ListTodayNodes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toNodeDTOs(list), nil
|
||||
}
|
||||
|
||||
func (a *App) ListChildren(parentID string) ([]NodeDTO, error) {
|
||||
list, err := a.nodes.ListChildren(parentID, false)
|
||||
if err != nil {
|
||||
|
|
@ -161,6 +170,9 @@ func (a *App) GetNodeDetail(nodeID string) (*NodeDTO, error) {
|
|||
}
|
||||
|
||||
func (a *App) CreateNode(parentID, nodeType, title, section string) (*NodeDTO, error) {
|
||||
if section == "today" || section == "inbox" {
|
||||
return nil, fmt.Errorf("cannot create node with section %q", section)
|
||||
}
|
||||
n, err := a.nodes.Create(parentID, nodeType, title, section)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -16,7 +16,7 @@
|
|||
background: #13131f;
|
||||
}
|
||||
</style>
|
||||
<script type="module" crossorigin src="/assets/main-B8EIu1OK.js"></script>
|
||||
<script type="module" crossorigin src="/assets/main-BY9JF_6I.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-Bo58X7Pc.css">
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -135,7 +135,11 @@
|
|||
showCreateNode = false
|
||||
error = ''
|
||||
try {
|
||||
nodes = await wailsCall('ListNodesBySection', id) || []
|
||||
if (id === 'today') {
|
||||
nodes = await wailsCall('ListTodayView') || []
|
||||
} else {
|
||||
nodes = await wailsCall('ListNodesBySection', id) || []
|
||||
}
|
||||
} catch (e) {
|
||||
error = String(e)
|
||||
nodes = []
|
||||
|
|
@ -1055,7 +1059,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !noteEditor && !selectedNode}
|
||||
{#if !noteEditor && !selectedNode && selectedSection !== 'today' && selectedSection !== 'inbox'}
|
||||
<div class="fab" on:click={openCreateNode} title="Добавить дело">+</div>
|
||||
{/if}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import * as App from '../wailsjs/go/main/App.js'
|
|||
|
||||
// Re-export all methods
|
||||
export const listSections = () => App.ListSections()
|
||||
export const listTodayView = () => App.ListTodayView()
|
||||
export const listNodesBySection = (section) => App.ListNodesBySection(section)
|
||||
export const listChildren = (parentID) => App.ListChildren(parentID)
|
||||
export const getNodeDetail = (id) => App.GetNodeDetail(id)
|
||||
|
|
@ -31,6 +32,7 @@ export const duplicateNode = (nodeID) => App.DuplicateNode(nodeID)
|
|||
export const renameNode = (nodeID, newTitle) => App.RenameNode(nodeID, newTitle)
|
||||
export const moveNode = (nodeID, newParentID) => App.MoveNode(nodeID, newParentID)
|
||||
export const openFolder = (nodeID) => App.OpenFolder(nodeID)
|
||||
export const validateName = (name) => App.ValidateName(name)
|
||||
|
||||
export const listActions = (nodeID) => App.ListActions(nodeID)
|
||||
export const runAction = (id) => App.RunAction(id)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ export function ListSections() {
|
|||
return window['go']['main']['App']['ListSections']();
|
||||
}
|
||||
|
||||
export function ListTodayView() {
|
||||
return window['go']['main']['App']['ListTodayView']();
|
||||
}
|
||||
|
||||
export function ListNodesBySection(arg1) {
|
||||
return window['go']['main']['App']['ListNodesBySection'](arg1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,16 +50,10 @@ func now() string {
|
|||
return time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
// --- CRUD ---
|
||||
|
||||
// Valid sections for root-level nodes.
|
||||
var validSections = map[string]struct{}{
|
||||
"clients": {}, "projects": {}, "recipes": {}, "documents": {}, "archive": {},
|
||||
}
|
||||
|
||||
// Create inserts a root or child node.
|
||||
// parentID may be empty for root-level nodes.
|
||||
// For root nodes, section determines sidebar placement (may be empty → inbox).
|
||||
// For root nodes, section determines sidebar placement (may be empty = inbox).
|
||||
// section must be a valid section (clients, projects, etc.) or empty for inbox.
|
||||
func (r *Repository) Create(parentID, typ, title, section string) (*Node, error) {
|
||||
if !IsValidType(typ) {
|
||||
return nil, fmt.Errorf("invalid node type: %s", typ)
|
||||
|
|
@ -67,6 +61,9 @@ func (r *Repository) Create(parentID, typ, title, section string) (*Node, error)
|
|||
if title == "" {
|
||||
return nil, errors.New("title is required")
|
||||
}
|
||||
if section != "" && !IsValidSection(section) {
|
||||
return nil, fmt.Errorf("invalid section: %s", section)
|
||||
}
|
||||
|
||||
n := &Node{
|
||||
ID: util.UUID7(),
|
||||
|
|
@ -182,6 +179,39 @@ func (r *Repository) ListRoots(includeDeleted bool, section string) ([]Node, err
|
|||
return scanNodes(rows)
|
||||
}
|
||||
|
||||
// todayBoundaries returns RFC3339 start and end strings for the current day
|
||||
// in the local timezone (the server's timezone, which should match the user's).
|
||||
// TODO: accept a user timezone offset when multi-user support is added.
|
||||
func todayBoundaries() (string, string) {
|
||||
now := time.Now()
|
||||
y, m, d := now.Date()
|
||||
start := time.Date(y, m, d, 0, 0, 0, 0, now.Location())
|
||||
end := start.Add(24 * time.Hour)
|
||||
return start.Format(time.RFC3339), end.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
// ListTodayNodes returns active root-level nodes created or updated today.
|
||||
// This is a dynamic view, not a section — it shows the day's activity.
|
||||
func (r *Repository) ListTodayNodes() ([]Node, error) {
|
||||
start, end := todayBoundaries()
|
||||
q := `SELECT id,parent_id,type,title,slug,path,section,sort_order,
|
||||
created_at,updated_at,deleted_at,revision,device_id
|
||||
FROM nodes
|
||||
WHERE deleted_at IS NULL
|
||||
AND (
|
||||
(created_at >= ? AND created_at < ?)
|
||||
OR
|
||||
(updated_at >= ? AND updated_at < ?)
|
||||
)
|
||||
ORDER BY updated_at DESC, created_at DESC`
|
||||
rows, err := r.db.Query(q, start, end, start, end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanNodes(rows)
|
||||
}
|
||||
|
||||
// UpdateTitle changes title (slug is recomputed).
|
||||
func (r *Repository) UpdateTitle(id, title string) error {
|
||||
if title == "" {
|
||||
|
|
|
|||
|
|
@ -35,12 +35,40 @@ var TypeSet = map[string]struct{}{
|
|||
TypeLink: {},
|
||||
}
|
||||
|
||||
// Valid sections for root-level nodes.
|
||||
// today and inbox are service sections, not stored in nodes.section.
|
||||
var validSections = map[string]struct{}{
|
||||
"clients": {},
|
||||
"projects": {},
|
||||
"recipes": {},
|
||||
"documents": {},
|
||||
"archive": {},
|
||||
}
|
||||
|
||||
// serviceSections are sidebar entries that are not stored as node sections.
|
||||
var serviceSections = map[string]struct{}{
|
||||
"today": {},
|
||||
"inbox": {},
|
||||
}
|
||||
|
||||
// IsValidType checks whether a type string is recognized.
|
||||
func IsValidType(t string) bool {
|
||||
_, ok := TypeSet[t]
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsValidSection returns true for sections that can be stored on a node.
|
||||
func IsValidSection(s string) bool {
|
||||
_, ok := validSections[s]
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsServiceSection returns true for sidebar-only sections (today, inbox).
|
||||
func IsServiceSection(s string) bool {
|
||||
_, ok := serviceSections[s]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Slugify converts a title into a filesystem-safe slug.
|
||||
// Examples:
|
||||
//
|
||||
|
|
|
|||
Loading…
Reference in New Issue