feat: client edit/delete/archive + time tracker + analytics time section
Schema: - clients.archived boolean (default false) - time_entries table (client_id, started_at, ended_at, duration_seconds) Client management: - /admin/clients/[id]/edit — form pre-compilato con nome, brand, brief - ClientActions: Modifica / Archivia / Elimina con doppia conferma - setClientArchived: toggle archiviazione senza perdere dati - deleteClient: elimina con cascade, redirect a /admin - Admin list: toggle "Mostra archiviati" via ?archived=1, righe archiviate opache Time tracker: - startTimer: crea sessione, ferma automaticamente quella precedente - stopTimer: chiude sessione, calcola duration_seconds - TimerCell: ▶/⏹ per ogni cliente, contatore live in secondi, totale cumulativo - Una sola sessione attiva alla volta su tutta la lista Analytics: - Sezione "Fatturato" (invariata) + sezione "Tempo tracciato" separata - Ore totali per anno + barre orizzontali per cliente - getTotalTrackedHours, getTimeByClient queries Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+17
-1
@@ -26,6 +26,7 @@ export const clients = pgTable("clients", {
|
||||
accepted_total: numeric("accepted_total", { precision: 10, scale: 2 }).default(
|
||||
"0"
|
||||
),
|
||||
archived: boolean("archived").notNull().default(false),
|
||||
created_at: timestamp("created_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
@@ -130,6 +131,19 @@ export const notes = pgTable("notes", {
|
||||
.defaultNow(),
|
||||
});
|
||||
|
||||
// ============ TIME ENTRIES (admin time tracking per client) ============
|
||||
export const time_entries = pgTable("time_entries", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
client_id: text("client_id")
|
||||
.notNull()
|
||||
.references(() => clients.id, { onDelete: "cascade" }),
|
||||
started_at: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
ended_at: timestamp("ended_at", { withTimezone: true }),
|
||||
duration_seconds: integer("duration_seconds"), // set on stop
|
||||
});
|
||||
|
||||
// ============ SERVICE CATALOG (admin-only, used for quote generation) ============
|
||||
export const service_catalog = pgTable("service_catalog", {
|
||||
id: text("id")
|
||||
@@ -242,4 +256,6 @@ export type NewNote = typeof notes.$inferInsert;
|
||||
export type ServiceCatalog = typeof service_catalog.$inferSelect;
|
||||
export type NewServiceCatalog = typeof service_catalog.$inferInsert;
|
||||
export type QuoteItem = typeof quote_items.$inferSelect;
|
||||
export type NewQuoteItem = typeof quote_items.$inferInsert;
|
||||
export type NewQuoteItem = typeof quote_items.$inferInsert;
|
||||
export type TimeEntry = typeof time_entries.$inferSelect;
|
||||
export type NewTimeEntry = typeof time_entries.$inferInsert;
|
||||
Reference in New Issue
Block a user