fix: Bug-Review-Fixes + README aktualisiert
Backend: - Vortags-Feed (dashboard.overview + dayEntry.feed) sortierte aufsteigend mit limit(50) → zeigte die ÄLTESTEN statt neuesten Einträge. Jetzt desc. - deal.updateStage: wonValueEur wurde beim Gewinnen mit der Wahrscheinlichkeit gewichtet → zu niedriger Won-Umsatz. Jetzt voller Auftragswert. - Pflicht-Minimum „Status aktuell" wurde global (letzte Reflexion irgendwo) statt pro Projekt berechnet. Jetzt je Projekt aus dem jüngsten Statusbericht. - time.listToday/teamToday nutzten fixen +02:00-Offset → im Winter (CET) um 1h verschobene Tagesgrenze. Jetzt DST-sicher via AT TIME ZONE 'Europe/Berlin'. Frontend: - lead.tsx + projekt.tsx: Datumsanzeigen nutzten UTC (toISOString) → abends Off-by-one. Jetzt Berlin-lokal formatiert. - reflexion.tsx: Submit mappte nur den initialen ToDo-Snapshot → mitten am Tag ergänzte ToDos verloren ihren Status. Jetzt über die aktuelle Liste. - leads.tsx: CSV-Zeilenzähler konnte „-1" zeigen (Math.max). - TimerPill: Label ohne Control (a11y-Lint-Error, war repo-weit rot) + unused clsx-Import behoben → pnpm lint jetzt 0 Errors. DB: seed.ts unbedingten updatedAt-Bump aller User entfernt (Debug-Rest). README: vollständig auf aktuellen Stand gebracht (Leads/CRM, CSV-Import, PM-Tiefe, Phasen/Status/Risiken/Stakeholder/Änderungen, Tabellen bis 0011, tRPC-Router, staleTime 0, Auto-Deploy-Runner + Startup-Migrationen). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
19db774c4c
commit
22f79460b2
12 files changed
+110
-79
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { and, eq, ne, isNotNull, isNull, count } from 'drizzle-orm';
|
||||
import { and, eq, ne, isNotNull, isNull, count, desc } from 'drizzle-orm';
|
||||
import { area, project, blocker, dayEntry, user, risk, type Area, type Project } from '@cockpit/db';
|
||||
import { router, protectedProcedure } from '../trpc.js';
|
||||
import { todayIso, previousWerktag } from '../lib/werktag.js';
|
||||
@@ -31,7 +31,7 @@ export const dashboardRouter = router({
|
||||
.from(dayEntry)
|
||||
.innerJoin(user, eq(user.id, dayEntry.userId))
|
||||
.where(and(isNotNull(dayEntry.publishedAt), eq(dayEntry.status, 'full')))
|
||||
.orderBy(dayEntry.publishedAt)
|
||||
.orderBy(desc(dayEntry.publishedAt))
|
||||
.limit(50),
|
||||
ctx.db
|
||||
.select({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { and, eq, isNull, isNotNull, lt, asc, inArray } from 'drizzle-orm';
|
||||
import { and, eq, isNull, isNotNull, lt, asc, desc, inArray } from 'drizzle-orm';
|
||||
import { dayEntry, todo, blocker, type Todo } from '@cockpit/db';
|
||||
import { DayEntrySchemas } from '@cockpit/shared';
|
||||
import { router, protectedProcedure } from '../trpc.js';
|
||||
@@ -375,7 +375,7 @@ export const dayEntryRouter = router({
|
||||
.select()
|
||||
.from(dayEntry)
|
||||
.where(isNotNull(dayEntry.publishedAt))
|
||||
.orderBy(dayEntry.publishedAt)
|
||||
.orderBy(desc(dayEntry.publishedAt))
|
||||
.limit(50);
|
||||
return entries;
|
||||
}),
|
||||
|
||||
@@ -64,10 +64,9 @@ export const dealRouter = router({
|
||||
stage: input.stage,
|
||||
enteredStageAt: new Date(),
|
||||
wonAt: input.stage === 'abschluss' ? new Date() : null,
|
||||
// Gewonnener Deal = voller Auftragswert (nicht mit Wahrscheinlichkeit gewichtet).
|
||||
wonValueEur:
|
||||
input.stage === 'abschluss'
|
||||
? sql`COALESCE(${deal.valueEur} * ${deal.probability} / 100, 0)`
|
||||
: null,
|
||||
input.stage === 'abschluss' ? sql`COALESCE(${deal.valueEur}, 0)` : null,
|
||||
})
|
||||
.where(eq(deal.id, input.dealId))
|
||||
.returning();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TRPCError } from '@trpc/server';
|
||||
import { z } from 'zod';
|
||||
import { and, eq, gte, isNull, lte, desc } from 'drizzle-orm';
|
||||
import { and, eq, isNull, desc, sql } from 'drizzle-orm';
|
||||
import { timeEntry, dayEntry, type TimeEntry } from '@cockpit/db';
|
||||
import { TimeEntrySchemas } from '@cockpit/shared';
|
||||
import { router, protectedProcedure } from '../trpc.js';
|
||||
@@ -129,16 +129,14 @@ export const timeRouter = router({
|
||||
*/
|
||||
listToday: protectedProcedure.query(async ({ ctx }) => {
|
||||
const today = todayIso();
|
||||
const startOfDay = new Date(`${today}T00:00:00+02:00`);
|
||||
const startOfTomorrow = new Date(startOfDay.getTime() + 24 * 60 * 60 * 1000);
|
||||
const rows = await ctx.db
|
||||
.select()
|
||||
.from(timeEntry)
|
||||
.where(
|
||||
and(
|
||||
eq(timeEntry.userId, ctx.user.id),
|
||||
gte(timeEntry.startedAt, startOfDay),
|
||||
lte(timeEntry.startedAt, startOfTomorrow),
|
||||
// Tagesgrenze TZ-korrekt (DST-sicher), statt fixem +02:00-Offset.
|
||||
sql`(${timeEntry.startedAt} AT TIME ZONE 'Europe/Berlin')::date = ${today}`,
|
||||
),
|
||||
)
|
||||
.orderBy(desc(timeEntry.startedAt));
|
||||
@@ -243,17 +241,14 @@ export const timeRouter = router({
|
||||
*/
|
||||
teamToday: protectedProcedure.query(async ({ ctx }) => {
|
||||
const today = todayIso();
|
||||
const startOfDay = new Date(`${today}T00:00:00+02:00`);
|
||||
const startOfTomorrow = new Date(startOfDay.getTime() + 24 * 60 * 60 * 1000);
|
||||
const { todo } = await import('@cockpit/db');
|
||||
const { inArray } = await import('drizzle-orm');
|
||||
|
||||
const rows = await ctx.db
|
||||
.select()
|
||||
.from(timeEntry)
|
||||
.where(
|
||||
and(gte(timeEntry.startedAt, startOfDay), lte(timeEntry.startedAt, startOfTomorrow)),
|
||||
);
|
||||
// Tagesgrenze TZ-korrekt (DST-sicher), statt fixem +02:00-Offset.
|
||||
.where(sql`(${timeEntry.startedAt} AT TIME ZONE 'Europe/Berlin')::date = ${today}`);
|
||||
|
||||
const todoIds = Array.from(
|
||||
new Set(rows.map((r) => r.todoId).filter((x): x is string => Boolean(x))),
|
||||
|
||||
Reference in new issue
Block a user