Projektlogo: quadratisches Logo hochladen & anzeigen
Logo kann bei der Projekterstellung und nachträglich im Projekt-Header hochgeladen werden. Client-seitiger Center-Crop auf 256px WebP, gespeichert als Data-URL in neuer Spalte project.logo_url (Migration 0016). Anzeige in der Bereichs-Projektliste (Card) und im Detail-Header, mit Initialen- Platzhalter wenn kein Logo gesetzt ist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
45a87711fb
commit
1206dc5e0f
9 files changed
+3652
-119
No files matched your search
@@ -0,0 +1 @@
|
||||
ALTER TABLE "project" ADD COLUMN "logo_url" text;
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,118 +1,125 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1779736070490,
|
||||
"tag": "0000_init",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1779737202483,
|
||||
"tag": "0001_day_rhythm",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1779737584310,
|
||||
"tag": "0002_areas_projects",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1779737764727,
|
||||
"tag": "0003_sales_marketing",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "7",
|
||||
"when": 1779739115697,
|
||||
"tag": "0004_is_admin",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "7",
|
||||
"when": 1779740456944,
|
||||
"tag": "0005_todo_carry_over",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "7",
|
||||
"when": 1780410287425,
|
||||
"tag": "0006_time_entry",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "7",
|
||||
"when": 1780415208906,
|
||||
"tag": "0007_project_description",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "7",
|
||||
"when": 1780577284868,
|
||||
"tag": "0008_lead_crm",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "7",
|
||||
"when": 1780669990802,
|
||||
"tag": "0009_pm_insights",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "7",
|
||||
"when": 1780672129310,
|
||||
"tag": "0010_leitfaden_phases",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "7",
|
||||
"when": 1780754557002,
|
||||
"tag": "0011_lead_project_link",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 12,
|
||||
"version": "7",
|
||||
"when": 1780761840614,
|
||||
"tag": "0012_todo_unplanned",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 13,
|
||||
"version": "7",
|
||||
"when": 1780763141548,
|
||||
"tag": "0013_parallel_timers",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 14,
|
||||
"version": "7",
|
||||
"when": 1780764129593,
|
||||
"tag": "0014_checkout_kimai",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 15,
|
||||
"version": "7",
|
||||
"when": 1780765384409,
|
||||
"tag": "0015_delete_break_entries",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1779736070490,
|
||||
"tag": "0000_init",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1779737202483,
|
||||
"tag": "0001_day_rhythm",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1779737584310,
|
||||
"tag": "0002_areas_projects",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1779737764727,
|
||||
"tag": "0003_sales_marketing",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "7",
|
||||
"when": 1779739115697,
|
||||
"tag": "0004_is_admin",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "7",
|
||||
"when": 1779740456944,
|
||||
"tag": "0005_todo_carry_over",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "7",
|
||||
"when": 1780410287425,
|
||||
"tag": "0006_time_entry",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "7",
|
||||
"when": 1780415208906,
|
||||
"tag": "0007_project_description",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "7",
|
||||
"when": 1780577284868,
|
||||
"tag": "0008_lead_crm",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "7",
|
||||
"when": 1780669990802,
|
||||
"tag": "0009_pm_insights",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "7",
|
||||
"when": 1780672129310,
|
||||
"tag": "0010_leitfaden_phases",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "7",
|
||||
"when": 1780754557002,
|
||||
"tag": "0011_lead_project_link",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 12,
|
||||
"version": "7",
|
||||
"when": 1780761840614,
|
||||
"tag": "0012_todo_unplanned",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 13,
|
||||
"version": "7",
|
||||
"when": 1780763141548,
|
||||
"tag": "0013_parallel_timers",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 14,
|
||||
"version": "7",
|
||||
"when": 1780764129593,
|
||||
"tag": "0014_checkout_kimai",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 15,
|
||||
"version": "7",
|
||||
"when": 1780765384409,
|
||||
"tag": "0015_delete_break_entries",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 16,
|
||||
"version": "7",
|
||||
"when": 1780771242601,
|
||||
"tag": "0016_project_logo_url",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -125,6 +125,8 @@ export const project = pgTable(
|
||||
.references(() => area.id, { onDelete: 'restrict' }),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
// Quadratisches Projektlogo als Data-URL (client-seitig auf 256px verkleinert).
|
||||
logoUrl: text('logo_url'),
|
||||
// Maxime / roter Faden (ein Satz) — Leitsatz fürs ganze Team (Leitfaden: Strategie).
|
||||
maxime: text('maxime'),
|
||||
// Definition (Leitfaden Phase 1) — Kurzfassung; Volltext-Steckbrief lebt in Outline.
|
||||
|
||||
@@ -3,10 +3,19 @@ import { z } from 'zod';
|
||||
const ampelSchema = z.enum(['green', 'amber', 'red']);
|
||||
const trendSchema = z.enum(['besser', 'gleich', 'schlechter']);
|
||||
|
||||
// Quadratisches Logo als Data-URL (client verkleinert auf 256px ≈ <100 KB).
|
||||
const logoUrlSchema = z
|
||||
.string()
|
||||
.max(500_000)
|
||||
.refine((s) => s.startsWith('data:image/') || /^https?:\/\//.test(s), {
|
||||
message: 'Logo muss ein Bild (Data-URL) oder eine http(s)-URL sein.',
|
||||
});
|
||||
|
||||
export const createProjectInput = z.object({
|
||||
areaId: z.string().uuid(),
|
||||
name: z.string().min(1).max(200),
|
||||
description: z.string().max(2000).optional(),
|
||||
logoUrl: logoUrlSchema.optional(),
|
||||
kind: z.enum(['kundenprojekt', 'internes_projekt']),
|
||||
verantwortlicheUserId: z.string().uuid().optional(),
|
||||
arbeitsmodus: z.enum(['cycle', 'kanban']).optional(),
|
||||
@@ -104,6 +113,7 @@ export const updateProjectInput = z.object({
|
||||
nichtZiele: z.string().max(2000).nullable().optional(),
|
||||
liefergegenstaende: z.string().max(2000).nullable().optional(),
|
||||
zieldreieckFlex: zieldreieckFlexSchema.nullable().optional(),
|
||||
logoUrl: logoUrlSchema.nullable().optional(),
|
||||
outlineSteckbriefUrl: z.string().url().nullable().optional(),
|
||||
planeProjectUrl: z.string().url().nullable().optional(),
|
||||
risikolisteUrl: z.string().url().nullable().optional(),
|
||||
|
||||
Reference in new issue
Block a user