fix(bereich): Bereich-Dropdown im "Neues Projekt"-Dialog war leer
Zwei Ursachen behoben: - Frontend: areaId/Default wurden nur beim Mount aus dem (noch ladenden) areas-Prop berechnet → kein Default, keine Platzhalter-Option. Jetzt via useEffect Default setzen sobald Areas geladen sind + Platzhalter-Option. - Prod: Deploy seedet nicht → frische DB hatte keine Areas. Neue ensureBaseAreas() legt die Basis-Bereiche idempotent beim API-Start an (nach den Migrationen), analog zu runMigrations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
9c5207527a
commit
43eeabaea2
4 files changed
+51
-2
No files matched your search
+10
-1
@@ -1,7 +1,7 @@
|
||||
import Fastify from 'fastify';
|
||||
import cookie from '@fastify/cookie';
|
||||
import { fastifyTRPCPlugin, type FastifyTRPCPluginOptions } from '@trpc/server/adapters/fastify';
|
||||
import { runMigrations } from '@cockpit/db';
|
||||
import { runMigrations, ensureBaseAreas } from '@cockpit/db';
|
||||
import { loadEnv } from './env.js';
|
||||
import { logger } from './lib/logger.js';
|
||||
import { registerAuthMiddleware } from './auth/middleware.js';
|
||||
@@ -24,6 +24,15 @@ async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Basis-Bereiche sicherstellen (idempotent), damit der „Neues Projekt"-
|
||||
// Bereich-Dropdown auch auf einer nicht geseedeten Prod-DB nie leer ist.
|
||||
try {
|
||||
await ensureBaseAreas(env.DATABASE_URL);
|
||||
logger.info('base areas ensured');
|
||||
} catch (err) {
|
||||
logger.error({ err }, 'ensure base areas failed');
|
||||
}
|
||||
|
||||
const app = Fastify({
|
||||
logger: {
|
||||
level: env.LOG_LEVEL,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Link, NavLink, useParams } from 'react-router';
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { trpc } from '../lib/trpc';
|
||||
import { voice } from '../lib/voice';
|
||||
import { Card } from '../components/ui/Card';
|
||||
@@ -176,6 +176,15 @@ function ProjectCreateDialog({
|
||||
const [name, setName] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [areaId, setAreaId] = useState(defaultArea?.id ?? '');
|
||||
|
||||
// Areas werden asynchron geladen; sobald sie da sind und noch nichts gewählt
|
||||
// wurde, sinnvollen Default setzen (Dropdown war sonst leer).
|
||||
useEffect(() => {
|
||||
if (!areaId && areas.length > 0) {
|
||||
const fallback = areas.find((a) => a.slug === defaultSlug) ?? areas[0];
|
||||
if (fallback) setAreaId(fallback.id);
|
||||
}
|
||||
}, [areas, areaId, defaultSlug]);
|
||||
const [kind, setKind] = useState<'kundenprojekt' | 'internes_projekt'>('internes_projekt');
|
||||
const [arbeitsmodus, setArbeitsmodus] = useState<'cycle' | 'kanban'>('kanban');
|
||||
const [steckbrief, setSteckbrief] = useState('');
|
||||
@@ -241,6 +250,9 @@ function ProjectCreateDialog({
|
||||
onChange={(e) => setAreaId(e.target.value)}
|
||||
className="w-full rounded-[var(--radius-md)] border border-[var(--color-border)] bg-white px-3 py-2 text-sm"
|
||||
>
|
||||
<option value="" disabled>
|
||||
{areas.length ? '— Bereich wählen —' : 'Bereiche werden geladen…'}
|
||||
</option>
|
||||
{areas.map((a) => (
|
||||
<option key={a.id} value={a.id}>
|
||||
{a.name}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import pg from 'pg';
|
||||
import { area } from './schema.js';
|
||||
|
||||
// Fachliche Basis-Bereiche — Referenzdaten, die jede Instanz braucht (auch Prod,
|
||||
// die nicht geseedet wird). Werden idempotent beim API-Start sichergestellt.
|
||||
export const BASE_AREAS = [
|
||||
{ slug: 'entwicklung', name: 'Entwicklung', kind: 'dev' as const, order: 1 },
|
||||
{ slug: 'vertrieb', name: 'Vertrieb', kind: 'sales' as const, order: 2 },
|
||||
{ slug: 'marketing', name: 'Marketing', kind: 'marketing' as const, order: 3 },
|
||||
{ slug: 'allgemein', name: 'Allgemein', kind: 'allgemein' as const, order: 99 },
|
||||
];
|
||||
|
||||
/**
|
||||
* Legt die Basis-Bereiche an, falls sie fehlen. Idempotent (ON CONFLICT DO
|
||||
* NOTHING auf slug). Wird beim API-Start nach den Migrationen aufgerufen, damit
|
||||
* z. B. der „Neues Projekt"-Bereich-Dropdown nie leer ist.
|
||||
*/
|
||||
export async function ensureBaseAreas(connectionString: string): Promise<void> {
|
||||
const pool = new pg.Pool({ connectionString, max: 1 });
|
||||
try {
|
||||
const db = drizzle(pool);
|
||||
await db.insert(area).values(BASE_AREAS).onConflictDoNothing();
|
||||
} finally {
|
||||
await pool.end();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './schema.js';
|
||||
export { createDbClient } from './client.js';
|
||||
export { runMigrations } from './migrate.js';
|
||||
export { ensureBaseAreas, BASE_AREAS } from './base-data.js';
|
||||
export type { Db, CreateDbClientOptions } from './client.js';
|
||||
|
||||
export const DB_VERSION = '0.0.1';
|
||||
Reference in new issue
Block a user