Initial commit: everythingbutwork MVP
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:
commit
4f33509a0f
131 files changed
+34339
No files matched your search
@@ -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} />;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createTRPCReact } from '@trpc/react-query';
|
||||
import type { AppRouter } from '@cockpit/api/router';
|
||||
|
||||
export const trpc = createTRPCReact<AppRouter>();
|
||||
@@ -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;
|
||||
Reference in new issue
Block a user