chore(format): Prettier über das gesamte Repo
ci / quality (push) Failing after 45s
ci / build (push) Has been skipped
deploy / deploy (push) Successful in 53s

Behebt die rote CI (format:check schlug an 36 Alt-Bestand-Dateien fehl:
migrations/meta-Snapshots, seed.ts, schemas/lead.ts, README u. a.) und erzwingt
nebenbei einen frischen web-Build beim Auto-Deploy (apps/web-Dateien geaendert
-> COPY-Layer invalidiert -> Vite baut neu).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Raffael SheikhandClaude Opus 4.8 committed 2026-06-07 17:50:07 +02:00
1 parent b340b45110
commit a1cf6f3883
36 files changed
+1559 -3510

No files matched your search

@@ -120,7 +120,9 @@ export function LogoUpload({
{error ? (
<span className="text-xs text-[var(--color-ampel-red-fg)]">{error}</span>
) : (
<span className="text-xs text-[var(--color-ink-faint)]">Quadratisch, wird auf 256px verkleinert.</span>
<span className="text-xs text-[var(--color-ink-faint)]">
Quadratisch, wird auf 256px verkleinert.
</span>
)}
</div>
<input
@@ -85,7 +85,9 @@ export function TeamTimeSection() {
title="Über parallele Aufgaben getrackte Summe (zählt überlappende Zeit mehrfach)."
>
{' · getrackt '}
<span className="font-mono">{formatDurationCompact(it.trackedSeconds)}</span>
<span className="font-mono">
{formatDurationCompact(it.trackedSeconds)}
</span>
</span>
)}
</p>
@@ -131,18 +133,10 @@ function ActiveLine({
}
function IdleLine() {
return (
<p className="mt-3 text-xs text-[var(--color-ink-faint)]">Aktuell kein Timer.</p>
);
return <p className="mt-3 text-xs text-[var(--color-ink-faint)]">Aktuell kein Timer.</p>;
}
function Breakdown({
byCategory,
total,
}: {
byCategory: Record<string, number>;
total: number;
}) {
function Breakdown({ byCategory, total }: { byCategory: Record<string, number>; total: number }) {
if (total === 0) return null;
const ordered = TIME_CATEGORIES.map((c) => ({
key: c.value,
+3 -1
View File
@@ -243,7 +243,9 @@ function QuickPicker({
<button
key={c.value}
type="button"
onClick={() => onStart({ category: c.value, description: description.trim() || undefined })}
onClick={() =>
onStart({ category: c.value, description: description.trim() || undefined })
}
disabled={pending}
className="flex w-full items-center gap-2 rounded-[var(--radius-md)] px-2 py-1.5 text-left text-sm hover:bg-black/5"
>
+1 -4
View File
@@ -5,10 +5,7 @@ function Root({ className, ...rest }: HTMLAttributes<HTMLDivElement>) {
// Scharfe Kanten, Haarlinien-Rahmen ohne Schatten — ToolCTRL-Website-Stil.
return (
<div
className={clsx(
'bg-[var(--color-surface)] border border-[var(--color-border)]',
className,
)}
className={clsx('bg-[var(--color-surface)] border border-[var(--color-border)]', className)}
{...rest}
/>
);
+1 -4
View File
@@ -2,10 +2,7 @@
export type LeadStatus = 'cold' | 'warm' | 'hot' | 'won' | 'lost';
export const LEAD_STATUS_META: Record<
LeadStatus,
{ label: string; badge: string; dot: string }
> = {
export const LEAD_STATUS_META: Record<LeadStatus, { label: string; badge: string; dot: string }> = {
cold: {
label: 'Cold',
badge: 'bg-black/5 text-[var(--color-ink-muted)]',
+30 -7
View File
@@ -29,8 +29,14 @@ export const MODI: ProjectModus[] = ['relax', 'standard', 'eskalation', 'taskfor
export type Level = 'niedrig' | 'mittel' | 'hoch';
export const LEVEL_META: Record<Level, { label: string; badge: string }> = {
niedrig: { label: 'Niedrig', badge: 'bg-[var(--color-ampel-green-bg)] text-[var(--color-ampel-green-fg)]' },
mittel: { label: 'Mittel', badge: 'bg-[var(--color-ampel-amber-bg)] text-[var(--color-ampel-amber-fg)]' },
niedrig: {
label: 'Niedrig',
badge: 'bg-[var(--color-ampel-green-bg)] text-[var(--color-ampel-green-fg)]',
},
mittel: {
label: 'Mittel',
badge: 'bg-[var(--color-ampel-amber-bg)] text-[var(--color-ampel-amber-fg)]',
},
hoch: { label: 'Hoch', badge: 'bg-[var(--color-ampel-red-bg)] text-[var(--color-ampel-red-fg)]' },
};
export const LEVELS: Level[] = ['niedrig', 'mittel', 'hoch'];
@@ -66,7 +72,10 @@ export type ProjectPhase = 'definition' | 'planung' | 'umsetzung' | 'abschluss';
export const PHASES: ProjectPhase[] = ['definition', 'planung', 'umsetzung', 'abschluss'];
export const PHASE_META: Record<ProjectPhase, { label: string; gateZiel: string }> = {
definition: { label: 'Definition', gateZiel: 'Steckbrief abgestimmt und freigegeben' },
planung: { label: 'Planung', gateZiel: 'Plan steht — Backlog zerlegt, Meilensteine & Risiken erfasst' },
planung: {
label: 'Planung',
gateZiel: 'Plan steht — Backlog zerlegt, Meilensteine & Risiken erfasst',
},
umsetzung: { label: 'Umsetzung', gateZiel: 'Liefergegenstände abgenommen' },
abschluss: { label: 'Abschluss', gateZiel: 'Projekt beendet, Erkenntnisse gesichert' },
};
@@ -86,12 +95,26 @@ export const RISK_STRATEGIE_LABEL: Record<RiskStrategie, string> = {
mindern: 'Mindern',
akzeptieren: 'Akzeptieren',
};
export const RISK_STRATEGIEN: RiskStrategie[] = ['vermeiden', 'verlagern', 'mindern', 'akzeptieren'];
export const RISK_STRATEGIEN: RiskStrategie[] = [
'vermeiden',
'verlagern',
'mindern',
'akzeptieren',
];
export type ChangeEntscheidung = 'offen' | 'genehmigt' | 'abgelehnt';
export const CHANGE_ENTSCHEIDUNG_META: Record<ChangeEntscheidung, { label: string; badge: string }> = {
offen: { label: 'Offen', badge: 'bg-[var(--color-ampel-amber-bg)] text-[var(--color-ampel-amber-fg)]' },
genehmigt: { label: 'Genehmigt', badge: 'bg-[var(--color-ampel-green-bg)] text-[var(--color-ampel-green-fg)]' },
export const CHANGE_ENTSCHEIDUNG_META: Record<
ChangeEntscheidung,
{ label: string; badge: string }
> = {
offen: {
label: 'Offen',
badge: 'bg-[var(--color-ampel-amber-bg)] text-[var(--color-ampel-amber-fg)]',
},
genehmigt: {
label: 'Genehmigt',
badge: 'bg-[var(--color-ampel-green-bg)] text-[var(--color-ampel-green-fg)]',
},
abgelehnt: { label: 'Abgelehnt', badge: 'bg-black/5 text-[var(--color-ink-faint)]' },
};
export const CHANGE_ENTSCHEIDUNGEN: ChangeEntscheidung[] = ['offen', 'genehmigt', 'abgelehnt'];
+10 -3
View File
@@ -106,7 +106,9 @@ export function DashboardRoute() {
{m.ueberfaellig && ' · überfällig'}
</span>
<span className="text-[var(--color-ink-faint)]">·</span>
<span className="text-[var(--color-ink-muted)]">{relativeDays(m.due!, q.data.date)}</span>
<span className="text-[var(--color-ink-muted)]">
{relativeDays(m.due!, q.data.date)}
</span>
</div>
</div>
<Link
@@ -209,7 +211,10 @@ export function DashboardRoute() {
<ul className="space-y-1 text-sm">
{q.data.pflichtMinimumOffen.map((p) => (
<li key={p.id}>
<Link to={`/projekt/${p.id}`} className="inline-flex items-center gap-2 hover:underline">
<Link
to={`/projekt/${p.id}`}
className="inline-flex items-center gap-2 hover:underline"
>
<ProjectLogo url={p.logoUrl} name={p.name} size={20} />
{p.name}
</Link>
@@ -331,7 +336,9 @@ function TeamHeuteSection() {
</span>
</div>
{u.todos.length === 0 ? (
<p className="mt-2 text-xs text-[var(--color-ink-faint)]">Keine ToDos eingetragen.</p>
<p className="mt-2 text-xs text-[var(--color-ink-faint)]">
Keine ToDos eingetragen.
</p>
) : (
<ul className="mt-2 space-y-1.5 text-sm">
{u.todos.map((t) => (
+1 -3
View File
@@ -25,9 +25,7 @@ export function LoginRoute() {
<h1 className="inline-flex items-center gap-2 text-4xl font-bold">
everythingbutwork <ToolCtrlLogo size={32} />
</h1>
<p className="mt-3 text-[var(--color-ink-muted)]">
Internes ToolCTRL-Projektmanagement.
</p>
<p className="mt-3 text-[var(--color-ink-muted)]">Internes ToolCTRL-Projektmanagement.</p>
{error && (
<p
className="mt-4 rounded-[var(--radius-md)] border border-[var(--color-ampel-red-bg)] bg-[var(--color-ampel-red-bg)] px-4 py-2 text-sm text-[var(--color-ampel-red-fg)]"
+10 -3
View File
@@ -114,7 +114,10 @@ function layout(entries: RawEntry[], nowMs: number): { bars: Bar[]; rows: number
export function TeamViewRoute() {
const [date, setDate] = useState(todayStr);
const isToday = date === todayStr;
const q = trpc.time.teamTimeline.useQuery({ date }, { refetchInterval: isToday ? 30_000 : false });
const q = trpc.time.teamTimeline.useQuery(
{ date },
{ refetchInterval: isToday ? 30_000 : false },
);
// 1-Sekunden-Tick nur heute (für laufende Balken).
const [, setTick] = useState(0);
@@ -148,7 +151,8 @@ export function TeamViewRoute() {
let axisEnd = hasData ? ceilHourMs(Math.max(...allMs)) : axisStart + 2 * 3600_000;
if (axisEnd - axisStart < 2 * 3600_000) axisEnd = axisStart + 2 * 3600_000;
const range = axisEnd - axisStart;
const pct = (ms: number) => ((Math.min(Math.max(ms, axisStart), axisEnd) - axisStart) / range) * 100;
const pct = (ms: number) =>
((Math.min(Math.max(ms, axisStart), axisEnd) - axisStart) / range) * 100;
const hours: number[] = [];
for (let t = axisStart; t <= axisEnd; t += 3600_000) hours.push(t);
@@ -228,7 +232,10 @@ export function TeamViewRoute() {
const { bars, rows } = layout(lane.entries, nowMs);
const laneHeight = rows * BAR_H + (rows - 1) * GAP;
return (
<div key={lane.userId} className="flex items-stretch border-t border-[var(--color-border)] py-1.5">
<div
key={lane.userId}
className="flex items-stretch border-t border-[var(--color-border)] py-1.5"
>
<div className="flex w-40 shrink-0 flex-col justify-center pr-2">
<span className="truncate text-sm font-medium">{lane.name}</span>
<span className="text-[10px] text-[var(--color-ink-muted)]">
+6 -6
View File
@@ -8,9 +8,9 @@
font-weight: 300 700;
font-display: swap;
src: url(/fonts/space-grotesk-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6,
U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: 'Space Grotesk';
@@ -18,9 +18,9 @@
font-weight: 300 700;
font-display: swap;
src: url(/fonts/space-grotesk-latin-ext.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
unicode-range:
U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
U+A720-A7FF;
}