Initial commit: everythingbutwork MVP
ci / quality (push) Has been cancelled
ci / build (push) Has been cancelled

Internes Project-Management-Tool für ToolCTRL — ergänzend zu Plane (Tickets)
und Outline (Doku). Umfasst Puls-Übersicht, Tagesrhythmus (Lock-in + Reflexion),
Bereichs-Detail-Views (Entwicklung/Vertrieb/Marketing), Pflicht-Minimum-Check
und Blocker-Lifecycle gemäß Projektmanagement-Leitfaden v3.0.

Stack: pnpm-Monorepo · Vite + React 18 + Tailwind · Fastify + tRPC v11 +
Drizzle ORM · Postgres 16 · Authentik OIDC (Phase 2, Dev-Auto-Login aktiv).

Planungs-Artefakte unter _bmad-output/planning-artifacts/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Opus 4.7 committed 2026-05-25 22:47:26 +02:00
commit 4f33509a0f
131 files changed
+34339

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
import { createBrowserRouter, RouterProvider } from 'react-router';
import { lazy, Suspense } from 'react';
import { AppShell } from '../components/layout/AppShell';
import { SkeletonCard } from '../components/ui/SkeletonCard';
const Puls = lazy(() => import('../routes/puls').then((m) => ({ default: m.PulsRoute })));
const Heute = lazy(() =>
import('../routes/heute').then((m) => ({ default: m.HeuteRoute })),
);
const Reflexion = lazy(() =>
import('../routes/reflexion').then((m) => ({ default: m.ReflexionRoute })),
);
const Bereich = lazy(() => import('../routes/bereich').then((m) => ({ default: m.BereichRoute })));
const Projekt = lazy(() => import('../routes/projekt').then((m) => ({ default: m.ProjektRoute })));
const Archiv = lazy(() => import('../routes/archiv').then((m) => ({ default: m.ArchivRoute })));
const NotFound = lazy(() =>
import('../routes/not-found').then((m) => ({ default: m.NotFoundRoute })),
);
const Login = lazy(() => import('../routes/login').then((m) => ({ default: m.LoginRoute })));
function withSuspense(node: React.ReactNode) {
return (
<Suspense fallback={<SkeletonCard className="mx-auto mt-12 max-w-2xl" />}>{node}</Suspense>
);
}
const router = createBrowserRouter([
{
path: '/login',
element: withSuspense(<Login />),
},
{
path: '/',
element: <AppShell />,
children: [
{ index: true, element: withSuspense(<Puls />) },
{ path: 'puls', element: withSuspense(<Puls />) },
{ path: 'heute', element: withSuspense(<Heute />) },
{ path: 'reflexion', element: withSuspense(<Reflexion />) },
{ path: 'bereich', element: withSuspense(<Bereich />) },
{ path: 'bereich/:slug', element: withSuspense(<Bereich />) },
{ path: 'projekt/:id', element: withSuspense(<Projekt />) },
{ path: 'archiv', element: withSuspense(<Archiv />) },
{ path: '*', element: withSuspense(<NotFound />) },
],
},
]);
export function Router() {
return <RouterProvider router={router} />;
}
+51
View File
@@ -0,0 +1,51 @@
import { useState } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpBatchLink } from '@trpc/client';
import { toast } from 'sonner';
import { trpc } from './trpc';
import { voice } from './voice';
export function TrpcProvider({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000,
retry: 1,
},
mutations: {
onError(err) {
const msg =
err &&
typeof err === 'object' &&
'message' in err &&
typeof err.message === 'string'
? err.message
: voice.error.generic;
toast(msg);
},
},
},
}),
);
const [trpcClient] = useState(() =>
trpc.createClient({
links: [
httpBatchLink({
url: '/trpc',
fetch(url, options) {
return fetch(url, { ...options, credentials: 'include' });
},
}),
],
}),
);
return (
<trpc.Provider client={trpcClient} queryClient={queryClient}>
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
</trpc.Provider>
);
}
+4
View File
@@ -0,0 +1,4 @@
import { createTRPCReact } from '@trpc/react-query';
import type { AppRouter } from '@cockpit/api/router';
export const trpc = createTRPCReact<AppRouter>();
+59
View File
@@ -0,0 +1,59 @@
// Zentrale UI-Mikrocopy — NFR-12/13 Voice & Tone.
// Locker, direkt, deutsch. Keine Anglizismen, keine Corporate-Floskeln.
export const voice = {
appName: 'everythingbutwork',
caret: '^',
toast: {
checkedIn: 'Locked in.',
reflected: 'Reflektiert.',
blockerOpened: 'Blocker offen.',
blockerResolved: 'Blocker erledigt.',
projectCreated: 'Projekt angelegt.',
projectArchived: 'Projekt archiviert.',
saved: 'Gespeichert.',
strong: 'Stark.',
},
cta: {
checkIn: 'Lock in',
reflect: 'Reflektieren',
newBlocker: 'Neuer Blocker',
newProject: 'Neues Projekt',
},
hint: {
notCheckedInToday: 'Du hast heute noch nicht gepingt.',
reflectionPending: 'Reflexion offen',
notReflectedYet: 'Du hast heute noch nicht reflektiert.',
howIsItGoing: 'Wie läuft es?',
},
empty: {
feed: 'Hier erscheinen die Updates des Tages, sobald jemand reflektiert hat.',
blockers: 'Keine offenen Blocker.',
projects: 'Noch keine Projekte. Leg eines an.',
archive: 'Noch nichts archiviert.',
},
error: {
generic: 'Da ist etwas schiefgelaufen. Bitte nochmal versuchen.',
notLoggedIn: 'Du bist nicht angemeldet.',
notAllowed: 'Dafür hast du keine Berechtigung.',
notFound: 'Nicht gefunden.',
},
label: {
bereich: 'Bereich',
bezug: 'Bezug',
ampel: {
green: 'Im Plan',
amber: 'Gefährdet',
red: 'Blockiert',
},
trend: {
better: 'Besser',
flat: 'Gleich',
worse: 'Schlechter',
},
projektart: {
kundenprojekt: 'Kundenprojekt',
internesProjekt: 'Internes Projekt',
},
},
} as const;