Einbau in HiveGrid vorbereiten: Anleitung, Ticket-Docs, i18n-Inventar
Die Uebergabe geht an einen Dev, der den Einbau mit einem Coding-Agenten macht. Dafuer reicht "hier ist das Repo" nicht. Vier neue Artefakte, alle gegen das Zielsystem geprueft, nicht aus dem Gedaechtnis geschrieben. **EINBAU.md** ist der Einstieg. Der wichtigste Satz darin steht ganz oben: Das Dashboard in HiveGrid ist kein leeres Blatt. Route, Widget-Registry, Rollen-Layouts, Drilldown, Export, Live-Updates und Layout-Speicherung sind gebaut. Wer dort eine neue Seite anlegt, wirft funktionierende Software weg. Das ist nicht meine Einschaetzung: Der Brief P30 im Zielsystem sagt woertlich "Delta-Phase, kein Neubau ... Dashboard/Widget-Registry voll gebaut". Analytics dagegen gibt es noch nicht und wird nach dem Muster von leads.analytics.tsx neu gebaut. Dazu eine Tabelle, welche Datei wohin wandert, und eine zweite, was ausdruecklich nicht mitwandert und warum. **docs/port/TICKET-ANALYTICS.md und TICKET-DASHBOARD.md** sind im Format von .planning/ticket-docs/ geschrieben, mit Header-Tabelle, Abhaengigkeiten, Definition of Done, Technischen Bausteinen, Quellen und Dev-Nachzug. Sie koennen dorthin kopiert werden; dann greift der bestehende Ablauf, statt dass wir einen zweiten danebenstellen. **docs/port/i18n-*.json plus TEXTE.md.** pnpm run verify:i18n ist in HiveGrid ein Gate: keine hartkodierten Strings. Dieser Prototyp ist zu 100 % hartkodiertes Deutsch. 185 Texte mit Schluesselvorschlag und Fundstelle, dazu derselbe Inhalt als einfuegbarer Baum. Erzeugt von scripts/i18n-inventar.py (npm run i18n:inventar), damit das Inventar nicht auseinanderlaeuft, wenn sich am Prototyp noch etwas aendert. Dabei gefunden: app.dashboard.widgets.* enthaelt bereits 17 Widgets, darunter genau fuenf, die dieser Prototyp gestaltet (receivables, orderIntake, daysToPayment, winRate, stageDistribution) und genau die drei, die er als reserviert markiert (myTasks, scheduleToday, hrRequests). Neue Schluessel dafuer waeren Dubletten; die acht sind in TEXTE.md einzeln aufgefuehrt. Die Anrede stimmt ebenfalls schon ueberein, HiveGrid duzt. **CLAUDE.md** als Einstiegspunkt, den Claude Code beim Oeffnen des Repos von selbst liest: was dieses Repo ist, die drei Dinge, an denen ein Einbau sonst scheitert, und wo was steht. Jeder in den Dokumenten genannte Pfad wurde in beiden Repos nachgeprueft. Ein Fehler dabei gefunden und behoben: der Designer-Brief heisst P11-suche-dashboard.md, nicht P11.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPeZNwSeU7qG3RRCwaxEZX
This commit is contained in:
1 parent
e6e5a105a8
commit
1896d2772b
11 files changed
+1809
-1
No files matched your search
@@ -0,0 +1,66 @@
|
||||
# Dashboard-Prototyp: Einstieg für Coding-Agenten
|
||||
|
||||
Dieses Repo ist **kein Produkt und keine Bibliothek**. Es ist eine
|
||||
Gestaltungsvorlage, die in `HiveGrid-Cloud` eingebaut wird.
|
||||
|
||||
## Wenn du hier etwas ändern sollst
|
||||
|
||||
Lies zuerst `README.md`, dann die Datei zum Thema in `docs/`. Vor jedem Commit:
|
||||
|
||||
```bash
|
||||
npx tsc --noEmit && npm run lint && npm run build
|
||||
npm run verify:carbon # Palette, Tooltip, Achsenraster gegen @carbon/charts
|
||||
npm run verify:layout # Textkollisionen, 7 Seiten x 9 Breiten (Server muss laufen)
|
||||
```
|
||||
|
||||
Beide `verify:*` sind keine Formsache. `verify:layout` hat in dieser Sitzung
|
||||
mehrfach Fehler gefunden, die auf einem Screenshot nicht zu sehen waren.
|
||||
|
||||
## Wenn du das hier in HiveGrid einbauen sollst
|
||||
|
||||
**→ `EINBAU.md` lesen, vollständig, vor der ersten Änderung.**
|
||||
|
||||
Die drei Dinge, an denen ein Einbau sonst scheitert:
|
||||
|
||||
1. **Das Dashboard existiert in HiveGrid bereits**, mit Route,
|
||||
Widget-Registry, Rollen-Layouts, Drilldown, Export und Live-Updates. Dieses
|
||||
Repo liefert die *Gestaltung*, nicht den Rahmen. Wer dort eine neue Seite
|
||||
anlegt, wirft funktionierende Software weg. Analytics dagegen gibt es noch
|
||||
nicht und wird neu gebaut.
|
||||
|
||||
2. **HiveGrid gewinnt bei allen globalen Regeln**: Schrift, Größen, Abstände,
|
||||
Farben, Ansprache. Was dieses Repo dazu setzt, existiert nur, damit es
|
||||
allein lauffähig ist. Verbindlich sind hier die *Diagrammregeln* und die
|
||||
*Kennzahl-Definitionen*.
|
||||
|
||||
3. **Alle Texte sind hartkodiertes Deutsch**, HiveGrid verlangt `t()` und
|
||||
prüft das mit `pnpm run verify:i18n`. Die Schlüsselliste liegt fertig unter
|
||||
`docs/port/`.
|
||||
|
||||
## Zwei Ticket-Docs liegen bereit
|
||||
|
||||
`docs/port/TICKET-ANALYTICS.md` und `docs/port/TICKET-DASHBOARD.md` sind im
|
||||
Format von `.planning/ticket-docs/` geschrieben und können dort hineinkopiert
|
||||
werden. Dann greift der normale HiveGrid-Ablauf.
|
||||
|
||||
## Wo finde ich was
|
||||
|
||||
| Was | Wo |
|
||||
|---|---|
|
||||
| **Einbau in HiveGrid** | `EINBAU.md` |
|
||||
| Ticket-Docs für den Einbau | `docs/port/TICKET-*.md` |
|
||||
| Texte und i18n-Schlüssel | `docs/port/TEXTE.md`, `i18n-de.json` |
|
||||
| Was übergeben wird, was Attrappe ist | `HANDOVER.md` |
|
||||
| Diagramm- und Farbregeln | `docs/DESIGN-SYSTEM.md` |
|
||||
| Jede Kennzahl mit Formel und Zeitbezug | `docs/KPI-KATALOG.md` |
|
||||
| Feldherkunft, Lücken im Modell | `docs/DATENMODELL.md` |
|
||||
| Rangfolge Prototyp ↔ HiveGrid | `docs/ZIELSYSTEM.md` Abschnitt 0 |
|
||||
| Zuschnitt Analytics ↔ Dashboard | `docs/SCOPE-ANALYTICS-DASHBOARD.md` |
|
||||
| Navigation und Seitenaufbau | `docs/NAVIGATION.md` |
|
||||
| Fertige vs. Interim-UI im Zielsystem | `docs/LEITPLANKEN.md` |
|
||||
| Woher die Gestaltung stammt | `docs/HERKUNFT.md` |
|
||||
| Fragen für die Abnahme | `docs/PRUEFFRAGEN.md` |
|
||||
|
||||
## Sprache
|
||||
Code-Kommentare, Doku und Oberfläche sind auf Deutsch. Die Anwendung duzt.
|
||||
Keine Geviertstriche.
|
||||
@@ -0,0 +1,172 @@
|
||||
# Einbau in HiveGrid Cloud
|
||||
|
||||
Diese Datei richtet sich an den Entwickler und den Coding-Agenten, die diesen
|
||||
Prototyp in `HiveGrid-Cloud` einbauen. Sie ist der Einstieg; alles andere in
|
||||
`docs/` vertieft nur.
|
||||
|
||||
**Reihenfolge zum Lesen:** diese Datei ganz → dann `docs/port/TICKET-ANALYTICS.md`
|
||||
oder `docs/port/TICKET-DASHBOARD.md`, je nachdem, was du baust.
|
||||
|
||||
---
|
||||
|
||||
## 0. Das Wichtigste zuerst: Es steht schon etwas da
|
||||
|
||||
**Das Dashboard in HiveGrid ist kein leeres Blatt.** Wer es als „neue Seite
|
||||
bauen" anfasst, zerstört funktionierende Software. Vorhanden sind:
|
||||
|
||||
| Was | Wo |
|
||||
|---|---|
|
||||
| Route mit Loader, Action, Export, Stream | `apps/web/app/routes/app.$tenant/dashboard.tsx` |
|
||||
| Widget-Registry mit Gating je Modul | `apps/web/app/services/dashboard-registry/registry.server.ts` |
|
||||
| Rollen-Layouts, Drilldown, Live-Updates, Layout-Speicherung | `apps/web/app/services/dashboard/dashboard.server.ts` |
|
||||
| Screen mit Widget-Renderer und 12-Spalten-Raster | `apps/web/app/components/screens/dashboard/DashboardPage.tsx` |
|
||||
| View-Model-Vertrag | `packages/view-models/src/dashboard/dashboard-page.vm.ts` |
|
||||
| Übersetzungen für 17 Widgets | `apps/web/public/locales/de/translations.json` → `app.dashboard.widgets.*` |
|
||||
|
||||
Dazu kommen Berechtigungen (`dashboard.read`), Audit-Events und der
|
||||
First-Steps-Checklist-Widget.
|
||||
|
||||
Das ist nicht meine Einschätzung, sondern die eurer eigenen Planung. Der Brief
|
||||
`.planning/designer-briefs/P30-dashboard-deluxe-querschnitt.md` sagt es
|
||||
wörtlich:
|
||||
|
||||
> **Delta-Phase, kein Neubau:** M20/M22/M30 sind großteils v1-fähig
|
||||
> (Dashboard/Widget-Registry voll gebaut, notify() vorhanden, i18n-Library +
|
||||
> DE-Locale da).
|
||||
|
||||
**Was dieser Prototyp beisteuert, ist Gestaltung, nicht Infrastruktur.**
|
||||
Konkret: wie eine Kachel aussieht, welche Kennzahlen zusammengehören, in
|
||||
welcher Reihenfolge sie stehen, wie ein Diagramm gezeichnet wird. Der Rahmen
|
||||
darum existiert und bleibt.
|
||||
|
||||
Für **Analytics** gilt das Gegenteil: Diesen Bereich gibt es noch nicht. Er
|
||||
wird neu gebaut, nach dem Muster von `leads.analytics.tsx`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Zwei Arbeitspakete, nicht eines
|
||||
|
||||
| | **Analytics** | **Dashboard** |
|
||||
|---|---|---|
|
||||
| Art | neuer Bereich | bestehenden Screen ergänzen |
|
||||
| Route | neu: `app.$tenant/analytics*` | vorhanden |
|
||||
| Screen | neu | vorhanden, wird erweitert |
|
||||
| Sidebar | ein neuer Eintrag | Eintrag existiert |
|
||||
| Aufwand | groß | klein bis mittel |
|
||||
| Ticket-Doc | `docs/port/TICKET-ANALYTICS.md` | `docs/port/TICKET-DASHBOARD.md` |
|
||||
|
||||
Sie sind unabhängig voneinander baubar. Analytics zuerst ist die einfachere
|
||||
Reihenfolge, weil das Dashboard Diagramme aus Analytics benutzt.
|
||||
|
||||
---
|
||||
|
||||
## 2. Was portiert wird und was nicht
|
||||
|
||||
### Portiert wird
|
||||
|
||||
| Aus diesem Repo | Wird in HiveGrid zu |
|
||||
|---|---|
|
||||
| `features/dashboard/data/calculations.ts` | Server-Selektoren, z. B. `apps/web/app/services/analytics/analytics.server.ts` |
|
||||
| `features/dashboard/view-models/*.vm.ts` | `packages/view-models/src/analytics/*.vm.ts` |
|
||||
| `features/dashboard/pages/*Seite.tsx` | `apps/web/app/components/screens/analytics/*Page.tsx` |
|
||||
| `features/dashboard/components/charts/*` | `apps/web/app/components/screens/analytics/charts/*` |
|
||||
| `features/dashboard/components/cards/*` | dito, `cards/` |
|
||||
| `features/dashboard/components/tables/DataTable.tsx` | dito, oder gegen eine vorhandene Tabelle tauschen |
|
||||
| `features/dashboard/utils/chartTheme.ts` | `apps/web/app/components/screens/analytics/chartTheme.ts` |
|
||||
| `features/dashboard/utils/formatters.ts` | prüfen: `packages/foundation` hat teilweise Entsprechungen |
|
||||
|
||||
### Portiert wird **nicht**
|
||||
|
||||
| Datei | Warum |
|
||||
|---|---|
|
||||
| `components/layout/AppShell.tsx` | Nachbau der Seitenleiste. In HiveGrid übernimmt `SubframeAppShell` + `AppSidebar`. |
|
||||
| `components/layout/AnalyticsInnerNav.tsx` | Nachbau. In HiveGrid ist das `SidebarIconsLayout`, das Leads, Termine, HR und Einstellungen schon benutzen. |
|
||||
| `components/layout/PageHeader.tsx` | HiveGrid hat `PageHeader` und `PageShell`. |
|
||||
| `app/globals.css`, Schriftdateien | Globale Regeln kommen ausschließlich aus HiveGrid. |
|
||||
| `data/seed.ts`, `data/nutzer.ts` | Attrappen. Ersetzt durch echte Abfragen und `requireMember`. |
|
||||
| `hooks/useAnalyseDaten.ts`, `context/FilterContext.tsx` | Prototyp-Verdrahtung. Ersetzt durch Loader und URL-Parameter. |
|
||||
| `pages/KomponentenSeite.tsx` | Bausteingalerie für die Abstimmung, kein Produktfeature. |
|
||||
| `routes/*Route.tsx` | Ersetzt durch die Loader der Software. |
|
||||
|
||||
**Die Trennung ist mit Absicht so gebaut:** Die Seiten unter `pages/` sind reine
|
||||
Komponenten mit der Signatur `({ vm })`. Sie holen sich nichts selbst. Deshalb
|
||||
lässt sich der Route-Container ersetzen, ohne die Seite anzufassen.
|
||||
|
||||
---
|
||||
|
||||
## 3. Der Stolperstein, der jede Datei betrifft: Texte
|
||||
|
||||
**`pnpm run verify:i18n` ist ein Gate in eurer CI: keine hartkodierten Strings.**
|
||||
Dieser Prototyp ist zu 100 % hartkodiertes Deutsch. Das ist der größte
|
||||
mechanische Posten am ganzen Einbau, und er ist hier vorbereitet:
|
||||
|
||||
| Datei | Inhalt |
|
||||
|---|---|
|
||||
| `docs/port/i18n-inventar.json` | 185 sichtbare Texte, je mit Schlüsselvorschlag und Fundstelle im Prototyp |
|
||||
| `docs/port/i18n-de.json` | dieselben Texte als verschachtelter Baum, direkt nach `apps/web/public/locales/de/translations.json` einfügbar |
|
||||
| `docs/port/TEXTE.md` | Erläuterung, Namensschema, und welche Schlüssel es in HiveGrid **schon gibt** |
|
||||
|
||||
**Vor dem Anlegen neuer Schlüssel: `app.dashboard.widgets.*` prüfen.** Dort
|
||||
stehen bereits 17 Widgets, darunter genau die, die dieser Prototyp gestaltet
|
||||
(`receivables`, `orderIntake`, `daysToPayment`, `winRate`, `stageDistribution`)
|
||||
und genau die drei, die er als reserviert markiert (`myTasks`, `hrRequests`,
|
||||
`scheduleToday`). Neue Schlüssel dafür wären Dubletten.
|
||||
|
||||
Die Anrede stimmt bereits überein: HiveGrid duzt („Termine und Buchungen für
|
||||
deinen Tag"), dieser Prototyp auch.
|
||||
|
||||
---
|
||||
|
||||
## 4. Rangfolge bei Widersprüchen
|
||||
|
||||
1. **HiveGrid gewinnt immer bei globalen Regeln.** Schrift, Schriftgrößen,
|
||||
Abstände, Farben, Control-Höhen, Ansprache. Dieser Prototyp setzt sie nur,
|
||||
damit er allein lauffähig ist.
|
||||
2. **Der Prototyp gewinnt bei Diagrammregeln**: Achsen, Palette, Tooltip,
|
||||
Kategoriengrenzen, Leerzustände. Das ist der Teil, den es in HiveGrid noch
|
||||
nicht gibt, und er ist gegen IBM Carbon geprüft (`npm run verify:carbon`).
|
||||
3. **Bei Kennzahl-Definitionen gilt `docs/KPI-KATALOG.md`**, aber nur, solange
|
||||
es dem Datenmodell nicht widerspricht. `.planning/ENTITY-MODEL.md` hat
|
||||
Vorrang (AD-20, projektzentrisch).
|
||||
|
||||
Ausführlich: `docs/ZIELSYSTEM.md` Abschnitt 0.
|
||||
|
||||
---
|
||||
|
||||
## 5. Ablauf
|
||||
|
||||
Beide Ticket-Docs sind im Format eurer `.planning/ticket-docs/` geschrieben und
|
||||
können dort hineinkopiert werden. Dann greift euer normaler Ablauf: Agent
|
||||
bekommt die Ticket-ID, liest die Doc, arbeitet die Checkliste ab
|
||||
(**in HiveGrid**: `docs/AGENT-CHECKLIST.md`).
|
||||
|
||||
```
|
||||
1. docs/port/TICKET-ANALYTICS.md → .planning/ticket-docs/
|
||||
2. docs/port/TICKET-DASHBOARD.md → .planning/ticket-docs/
|
||||
3. IDs in Plane anlegen, Docs verlinken
|
||||
4. Agent wie gewohnt starten
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Womit du prüfst, ob es stimmt
|
||||
|
||||
In HiveGrid, aus dessen `docs/AGENT-CHECKLIST.md`:
|
||||
|
||||
```bash
|
||||
pnpm run verify:i18n # keine hartkodierten Strings
|
||||
pnpm run lint
|
||||
pnpm run test
|
||||
pnpm run verify:push
|
||||
```
|
||||
|
||||
Zusätzlich aus diesem Repo übernehmbar, beide ohne HiveGrid-Abhängigkeit:
|
||||
|
||||
```bash
|
||||
npm run verify:carbon # Palette, Tooltip, Achsenraster gegen @carbon/charts
|
||||
npm run verify:layout # Textkollisionen über 7 Seiten x 9 Breiten
|
||||
```
|
||||
|
||||
`scripts/verify-layout.mjs` braucht nur eine laufende URL und eine Seitenliste.
|
||||
Er ist die billigste Absicherung dagegen, dass beim Einbau in andere Container
|
||||
Text ineinanderläuft, und lohnt die zehn Minuten Anpassung.
|
||||
@@ -9,6 +9,9 @@
|
||||
> Navigationsstruktur als Verabredung. Ausführlich:
|
||||
> [`docs/ZIELSYSTEM.md`](docs/ZIELSYSTEM.md) Abschnitt 0.
|
||||
>
|
||||
> **Der Einbau selbst steht in [`EINBAU.md`](EINBAU.md)**: Zielpfade,
|
||||
> Ticket-Docs im Format von `.planning/ticket-docs/`, i18n-Schlüssel.
|
||||
>
|
||||
> **Ebenfalls vor dem Einbau lesen:**
|
||||
> [`docs/SCOPE-ANALYTICS-DASHBOARD.md`](docs/SCOPE-ANALYTICS-DASHBOARD.md),
|
||||
> insbesondere Abschnitt 3a. Der Handlungsbedarf steht dort **absichtlich
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# HiveGrid Analytics Dashboard: UI/UX-Prototyp
|
||||
|
||||
> **Wenn du das hier einbaust: [`EINBAU.md`](EINBAU.md) zuerst lesen.**
|
||||
> Dort steht, was in HiveGrid schon existiert (das Dashboard ist kein leeres
|
||||
> Blatt), welche Datei wohin wandert, was ausdrücklich *nicht* mitwandert, und
|
||||
> wo die 185 Oberflächentexte mit fertigen i18n-Schlüsseln liegen.
|
||||
|
||||
Gestaltungsvorlage für die Analytics-Dashboards in HiveGrid, fachlich auf einen
|
||||
SHK-/Handwerksbetrieb zugeschnitten. **Ziel dieses Repos ist die Übergabe an die
|
||||
Entwicklung**, nicht der Produktivbetrieb.
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
# Texte und Übersetzungsschlüssel
|
||||
|
||||
`pnpm run verify:i18n` ist in HiveGrid ein Gate: keine hartkodierten Strings.
|
||||
Dieser Prototyp ist zu 100 % hartkodiertes Deutsch, weil er allein lauffähig
|
||||
sein muss. Damit ist die Übersetzung der größte mechanische Posten am Einbau.
|
||||
Diese drei Dateien nehmen ihn ab.
|
||||
|
||||
| Datei | Wofür |
|
||||
|---|---|
|
||||
| `i18n-inventar.json` | 185 Einträge: Schlüssel, deutscher Text, Fundstelle im Prototyp |
|
||||
| `i18n-de.json` | dieselben Texte als Baum, nach `apps/web/public/locales/de/translations.json` einfügbar |
|
||||
| diese Datei | Namensschema, Grenzen, und was in HiveGrid schon existiert |
|
||||
|
||||
---
|
||||
|
||||
## 1. Zuerst prüfen: vieles gibt es schon
|
||||
|
||||
`app.dashboard.widgets.*` in HiveGrid enthält bereits 17 Widgets. Fünf davon
|
||||
sind genau die Kacheln, die dieser Prototyp gestaltet, drei weitere genau die,
|
||||
die er als „kommt noch" markiert:
|
||||
|
||||
| Schlüssel in HiveGrid | Deutscher Text dort | Entspricht im Prototyp |
|
||||
|---|---|---|
|
||||
| `receivables` | Offene Posten | Kennzahl „Offene Forderungen", Finanzen-Diagramm |
|
||||
| `orderIntake` | Auftragseingang | Aufträge-Seite, Säulendiagramm |
|
||||
| `daysToPayment` | Zahlungsdauer | Kennzahl in der Statistikzeile |
|
||||
| `winRate` | Auftragsquote | Trichter-Übergangsquote |
|
||||
| `stageDistribution` | Offene Projekte nach Phase | Vertriebstrichter |
|
||||
| `myTasks` | Meine Aufgaben | reservierte Kachel |
|
||||
| `scheduleToday` | Heute | reservierte Kachel „Termine heute" |
|
||||
| `hrRequests` | HR-Anträge | reservierte Kachel |
|
||||
|
||||
**Für diese acht keine neuen Schlüssel anlegen.** Die Vorschläge im Inventar
|
||||
sind dann zu verwerfen und durch den bestehenden Schlüssel zu ersetzen.
|
||||
|
||||
Die Anrede passt bereits zusammen: HiveGrid duzt („Termine und Buchungen für
|
||||
deinen Tag"), dieser Prototyp seit dem 31.08. auch.
|
||||
|
||||
---
|
||||
|
||||
## 2. Namensschema der Vorschläge
|
||||
|
||||
```
|
||||
app.<bereich>.<gruppe>.<kurzform>
|
||||
|
||||
app.analytics.finanzen.umsatzNachGewerk
|
||||
app.dashboard.page.handlungsbedarf
|
||||
app.shared.chart.keineDatenGewaehlten
|
||||
```
|
||||
|
||||
`<bereich>` ist `analytics`, `dashboard` oder `shared`, `<gruppe>` die Seite
|
||||
oder der Baustein. Die Kurzform ist maschinell aus dem deutschen Text gebildet
|
||||
und **ausdrücklich ein Vorschlag**. Sie ist eindeutig und kollisionsfrei, aber
|
||||
kein Ersatz für einen Blick. Wo HiveGrid schon eine bessere Benennung hat, gilt
|
||||
die.
|
||||
|
||||
Verteilung:
|
||||
|
||||
| `app.dashboard.page.*` | 28 |
|
||||
| `app.analytics.auftraege.*` | 26 |
|
||||
| `app.analytics.kunden.*` | 26 |
|
||||
| `app.analytics.finanzen.*` | 24 |
|
||||
| `app.analytics.team.*` | 22 |
|
||||
| `app.analytics.alerts.*` | 13 |
|
||||
| `app.analytics.nav.*` | 12 |
|
||||
| `app.analytics.uebersicht.*` | 8 |
|
||||
| `app.shared.chart.*` | 6 |
|
||||
| `app.analytics.filter.*` | 5 |
|
||||
| `app.analytics.actions.*` | 5 |
|
||||
| `app.analytics.shell.*` | 4 |
|
||||
| `app.shared.table.*` | 3 |
|
||||
| `app.shared.alerts.*` | 1 |
|
||||
| `app.shared.kpi.*` | 1 |
|
||||
| `app.dashboard.widget.*` | 1 |
|
||||
| `app.shared.state.*` | 1 |
|
||||
|
||||
---
|
||||
|
||||
## 3. Was das Inventar nicht abdeckt
|
||||
|
||||
**Texte mit Zahlen darin.** Sätze wie „25 überfällige Rechnungen" oder
|
||||
„Ältester seit 99 Tagen fällig" entstehen im Prototyp durch
|
||||
String-Interpolation in `data/calculations.ts`. Sie brauchen in HiveGrid
|
||||
i18next-Platzhalter und teils Pluralformen:
|
||||
|
||||
```json
|
||||
"ueberfaelligeRechnungen": "{{count}} überfällige Rechnung",
|
||||
"ueberfaelligeRechnungen_other": "{{count}} überfällige Rechnungen"
|
||||
```
|
||||
|
||||
Im Inventar stehen sie mit der Musterzeichenkette, nicht mit Platzhaltern.
|
||||
Betroffen ist vor allem `app.analytics.alerts.*` (13 Einträge).
|
||||
|
||||
**Die Bausteingalerie.** `pages/KomponentenSeite.tsx` ist bewusst
|
||||
ausgenommen, sie wird nicht portiert.
|
||||
|
||||
**Zahlen- und Datumsformate.** Die stecken in `utils/formatters.ts` und sind
|
||||
keine Übersetzung, sondern Formatierung. HiveGrid hat dafür eigene Helfer unter
|
||||
`apps/web/app/locale/format.ts`; die des Prototyps sind zu vergleichen, nicht
|
||||
zu übernehmen.
|
||||
|
||||
---
|
||||
|
||||
## 4. Wie das Inventar entstanden ist
|
||||
|
||||
Maschinell, mit `npm run i18n:inventar` (`scripts/i18n-inventar.py`): alle
|
||||
Zeichenketten in den Text tragenden Props (`titel`, `untertitel`,
|
||||
`beschreibung`, `label`, `hinweis`, `zusatz`, `aktion`, …) und alle Textknoten
|
||||
in JSX. Kommentare, die Bausteingalerie und die Daten-Attrappen sind
|
||||
ausgenommen.
|
||||
|
||||
Der Befehl ist wiederholbar: Ändert sich am Prototyp noch etwas, erzeugt er
|
||||
beide Dateien neu, statt dass sie auseinanderlaufen.
|
||||
|
||||
Das heißt auch: **Vollständigkeit ist wahrscheinlich, aber nicht bewiesen.**
|
||||
Der verlässliche Nachweis ist `pnpm run verify:i18n` in HiveGrid. Was das
|
||||
Inventar übersieht, findet dieses Gate.
|
||||
@@ -0,0 +1,88 @@
|
||||
# ANA-1 · Analytics-Bereich aus dem Dashboard-Prototyp einbauen
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Typ** | Dev (UI + View-Models + Server-Selektoren) |
|
||||
| **Owner** | *(offen)* |
|
||||
| **Phase** | *(offen)* |
|
||||
| **Cycle/Fenster** | *(offen)* |
|
||||
| **Status** | geplant |
|
||||
|
||||
> **Vorlage:** `HiveGrid-Cloud`-fremdes Repo `toolctrl-labs/Dashboard`. Dort ist
|
||||
> der Bereich vollständig gestaltet und lauffähig. Diese Doc destilliert daraus,
|
||||
> was in HiveGrid entsteht. Einstieg dort: `EINBAU.md`.
|
||||
|
||||
## Abhängigkeiten & Parallelität
|
||||
- **Blocked by:** nichts. Alle benötigten Daten liegen im bestehenden Modell.
|
||||
- **Blocks:** `ANA-2` (Dashboard) benutzt vier Diagramme aus diesem Ticket.
|
||||
- **Läuft parallel zu:** allem außer `ANA-2`. Neuer Bereich, keine bestehende
|
||||
Datei wird umgebaut, mit **zwei Ausnahmen**: `AppSidebar.tsx` (ein Eintrag)
|
||||
und `uiRegistry.ts` (eine Nav-ID).
|
||||
|
||||
## Was wir bauen
|
||||
Einen Analytics-Bereich als **einzelnen Sidebar-Eintrag** mit fünf Analyse-Seiten,
|
||||
die über eine innere Leiste erreichbar sind: Übersicht, Finanzen, Aufträge,
|
||||
Kunden, Team. Ein Zeitraumfilter und fünf Dimensionsfilter wirken seitenweit.
|
||||
|
||||
Das Muster dafür existiert bereits zweimal im Haus: `leads.analytics.tsx` für
|
||||
Route und Loader, `SidebarIconsLayout` für die innere Leiste (Leads, Termine, HR
|
||||
und Einstellungen benutzen sie).
|
||||
|
||||
## Benötigte Screens
|
||||
Keine neuen Designs nötig. Der Prototyp liefert alle Seiten fertig gestaltet,
|
||||
gegen IBM Carbon geprüft und über neun Fensterbreiten auf Kollisionsfreiheit
|
||||
gemessen.
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Fünf Routen unter `apps/web/app/routes/app.$tenant/analytics*.tsx`, je mit
|
||||
Loader nach dem Muster von `leads.analytics.tsx`
|
||||
- [ ] Fünf Screens unter `apps/web/app/components/screens/analytics/`, rein
|
||||
(`{ vm }`), ohne eigene Datenbeschaffung
|
||||
- [ ] View-Models unter `packages/view-models/src/analytics/`, mit Tests wie
|
||||
`lead-analytics.vm.test.ts`
|
||||
- [ ] Server-Selektoren aus `data/calculations.ts` übersetzt, tenant-gescoped,
|
||||
RLS-konform
|
||||
- [ ] Innere Navigation über das vorhandene `SidebarIconsLayout`, **nicht** über
|
||||
den Nachbau aus dem Prototyp
|
||||
- [ ] Ein Sidebar-Eintrag in `AppSidebar.tsx` (`overviewItems`), Nav-ID in
|
||||
`uiRegistry.ts`, Berechtigung analog `dashboard.read`
|
||||
- [ ] Alle Texte über `t()`, Schlüssel aus `docs/port/i18n-de.json` des
|
||||
Prototyps; `pnpm run verify:i18n` grün
|
||||
- [ ] Alle fünf `VMState`-Zustände bedient (idle · loading · ready · empty · error)
|
||||
- [ ] `pnpm run lint`, `pnpm run test`, `pnpm run verify:push` grün
|
||||
|
||||
## Technische Bausteine
|
||||
| Baustein | Quelle im Prototyp | Ziel |
|
||||
|---|---|---|
|
||||
| Kennzahl-Berechnungen | `features/dashboard/data/calculations.ts` | `apps/web/app/services/analytics/analytics.server.ts` |
|
||||
| View-Models | `features/dashboard/view-models/*.vm.ts` | `packages/view-models/src/analytics/` |
|
||||
| Seiten | `features/dashboard/pages/*Seite.tsx` | `apps/web/app/components/screens/analytics/` |
|
||||
| Diagramme | `features/dashboard/components/charts/` | dito, `charts/` |
|
||||
| Chart-Theme (Carbon) | `features/dashboard/utils/chartTheme.ts` | dito |
|
||||
| Innere Leiste | *(nicht portieren)* | `~/components/ui/layouts/SidebarIconsLayout` |
|
||||
| Seitenrahmen | *(nicht portieren)* | `~/components/screens/shared/PageShell` |
|
||||
|
||||
**Filter:** Im Prototyp ein `localStorage`-Store, damit er ohne Server läuft.
|
||||
In HiveGrid gehören sie in die URL. Dann sind Ansichten teilbar und der Loader
|
||||
kann serverseitig filtern. `leads.analytics.tsx` macht das bereits vor
|
||||
(`parseLeadAnalyticsFilters(new URL(request.url), new Date())`).
|
||||
|
||||
**Datenmodell:** `docs/DATENMODELL.md` im Prototyp nennt je Kennzahl die
|
||||
Herkunft und markiert, wo der Prototyp mehr annimmt als das Modell hergibt.
|
||||
Bei Widerspruch gilt `.planning/ENTITY-MODEL.md` (AD-20).
|
||||
|
||||
## Quellen (granular)
|
||||
- `toolctrl-labs/Dashboard` → `EINBAU.md`, `docs/KPI-KATALOG.md` (jede Kennzahl
|
||||
mit Formel und Zeitbezug), `docs/DESIGN-SYSTEM.md` (Diagrammregeln),
|
||||
`docs/DATENMODELL.md` (Feldherkunft), `docs/NAVIGATION.md`
|
||||
- Muster im Haus: `apps/web/app/routes/app.$tenant/leads.analytics.tsx`,
|
||||
`packages/view-models/src/leads/lead-analytics.vm.ts`
|
||||
- `.planning/ENTITY-MODEL.md` → Vorrang bei Datenfragen
|
||||
- `.planning/designer-briefs/P11-suche-dashboard.md` → Suche und Dashboard
|
||||
|
||||
## Dev-Nachzug
|
||||
- **Team-Seite:** braucht Zeiterfassung. Die steht im v2-Backlog. Ohne sie
|
||||
bleibt die Seite ohne Datenquelle. Entweder mitziehen oder die Seite in
|
||||
diesem Ticket weglassen. Im Prototyp ist sie gestaltet und wartet.
|
||||
- **Übersichtsseite:** überschneidet sich inhaltlich mit dem Dashboard. Vor dem
|
||||
Bau entscheiden, siehe `docs/SCOPE-ANALYTICS-DASHBOARD.md` Abschnitt 5.1.
|
||||
@@ -0,0 +1,88 @@
|
||||
# ANA-2 · Dashboard-Gestaltung aus dem Prototyp übernehmen
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Typ** | Dev (UI, bestehenden Screen erweitern) |
|
||||
| **Owner** | *(offen)* |
|
||||
| **Phase** | *(offen)* |
|
||||
| **Cycle/Fenster** | *(offen)* |
|
||||
| **Status** | geplant |
|
||||
|
||||
> **Achtung, zuerst lesen:** Das Dashboard existiert bereits vollständig:
|
||||
> Route, Widget-Registry, Rollen-Layouts, Drilldown, Export, Live-Updates,
|
||||
> Layout-Speicherung, Berechtigungen. Dieses Ticket **gestaltet**, es baut
|
||||
> nicht neu. Wer hier eine neue Seite anlegt, wirft funktionierende Software weg.
|
||||
|
||||
## Abhängigkeiten & Parallelität
|
||||
- **Blocked by:** `ANA-1`, vier der Kacheln zeigen Diagramme aus Analytics.
|
||||
- **Blocks:** nichts.
|
||||
- **Läuft parallel zu:** allem. Berührt `DashboardPage.tsx` und die
|
||||
Widget-Definitionen, sonst nichts.
|
||||
|
||||
## Was wir bauen
|
||||
Die Kacheln des Begrüßungsbildschirms bekommen die Gestaltung aus dem
|
||||
Prototyp: Begrüßung, vier Kennzahlen mit Absprung in die Analyse,
|
||||
Handlungsbedarf über die volle Breite, vier Diagramme.
|
||||
|
||||
Das 12-Spalten-Raster bleibt unangetastet. Der Prototyp hat es aus
|
||||
`DashboardPage.tsx` übernommen; die Kachelbreiten dort (`breite`/`start`)
|
||||
sind eins zu eins `widget.size.w` und `widget.position.x`.
|
||||
|
||||
## Benötigte Screens
|
||||
Keine neuen Designs. Der Prototyp zeigt den fertigen Stand unter `/dashboard`.
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Kachelrahmen entspricht dem Prototyp: Titel, Beschreibung, Domänen-Icon,
|
||||
**Zeitraum im Kopf**, Absprung als Fußzeile
|
||||
- [ ] Die vier Kennzahlen sind Links in die zugehörige Analyse-Seite
|
||||
(`widget.href`, wie es die `StatsCard` heute schon kann)
|
||||
- [ ] Handlungsbedarf-Liste als Widget über die volle Breite
|
||||
- [ ] Vier Diagramme aus `ANA-1` als Widgets eingehängt
|
||||
- [ ] Kein seitenweiter Filter. Jede Kachel nennt ihren Zeitraum selbst
|
||||
(P11: „Edit = nur Widget-Filter/Zeitraum")
|
||||
- [ ] Bestehende Funktionen unverändert lauffähig: Rollenwechsel,
|
||||
Bearbeitungsmodus, Drilldown, Export, Live-Updates, Gating
|
||||
- [ ] `pnpm run verify:i18n`, `lint`, `test`, `verify:push` grün
|
||||
|
||||
## Technische Bausteine
|
||||
| Baustein | Quelle im Prototyp | Ziel |
|
||||
|---|---|---|
|
||||
| Kachelrahmen | `components/dashboard/WidgetKachel.tsx` | Widget-Renderer in `DashboardPage.tsx` |
|
||||
| Begrüßung (`hero`) | `components/dashboard/Begruessung.tsx` | `hero`-Zweig in `DashboardPage.tsx` |
|
||||
| Kachelraster | `components/dashboard/WidgetRaster.tsx` | **vorhanden**, nur abgleichen |
|
||||
| Kennzahl-Kachel | `components/cards/KPICard.tsx` | `StatsCard` bzw. deren Gestaltung |
|
||||
| Handlungsbedarf | `components/cards/AlertList.tsx` | neues `list`-Widget |
|
||||
|
||||
**Eine Zeile fehlt im Raster.** `dashboardWidgetGridClass` kennt keinen Fall
|
||||
`position.x === 9`, weil bisher keine Rolle vier Kacheln nebeneinander stellt.
|
||||
Die Kennzahlenreihe tut das. Ohne die Zeile fließt die vierte Kachel dank
|
||||
`grid-flow-row-dense` trotzdem richtig, nur ohne festen Anfang. Vermerkt in
|
||||
`components/dashboard/WidgetRaster.tsx` des Prototyps.
|
||||
|
||||
**Übersetzungen: erst suchen, dann anlegen.** `app.dashboard.widgets.*` enthält
|
||||
bereits `receivables`, `orderIntake`, `daysToPayment`, `winRate` und
|
||||
`stageDistribution`. Das sind genau die Kacheln aus diesem Ticket. Neue
|
||||
Schlüssel dafür wären Dubletten.
|
||||
|
||||
## Quellen (granular)
|
||||
- `toolctrl-labs/Dashboard` → `EINBAU.md`, `docs/SCOPE-ANALYTICS-DASHBOARD.md`
|
||||
(Abschnitt 3a: was gebaut wurde und warum), `docs/DESIGN-SYSTEM.md`
|
||||
- `.planning/designer-briefs/P11-suche-dashboard.md` → Rollen-Layouts GF / Büro / Monteur
|
||||
- `.planning/designer-briefs/P30-dashboard-deluxe-querschnitt.md` → bestätigt die
|
||||
Ausgangslage wörtlich: „Delta-Phase, kein Neubau … Dashboard/Widget-Registry
|
||||
voll gebaut"
|
||||
- Bestehender Code: `apps/web/app/components/screens/dashboard/DashboardPage.tsx`,
|
||||
`services/dashboard/dashboard.server.ts`, `services/dashboard-registry/registry.server.ts`
|
||||
|
||||
## Dev-Nachzug
|
||||
- **Handlungsbedarf steht doppelt.** Im Prototyp absichtlich: auf dem Dashboard
|
||||
und auf der Analytics-Übersicht. Das Entfernen hätte eine offene Produktfrage
|
||||
mitentschieden. **Vor dem Port auflösen, nicht übernehmen.** Siehe
|
||||
`docs/SCOPE-ANALYTICS-DASHBOARD.md` Abschnitt 3a.
|
||||
- **Drei Bereiche kommen später:** eigene Aufgaben, Termine des Tages,
|
||||
HR-Anträge. Alle drei haben in HiveGrid schon Widget-Domäne **und**
|
||||
Übersetzungsschlüssel (`myTasks`, `scheduleToday`, `hrRequests`). Im
|
||||
Prototyp sind sie gestaltet unter `/komponenten`; die Stelle im Raster ist in
|
||||
`pages/DashboardSeite.tsx` als Kommentarblock markiert.
|
||||
- **Rollen Büro und Monteur** sind nicht gestaltet. Sie entstehen aus denselben
|
||||
Kacheln in anderer Zusammenstellung; das ist eine Produktentscheidung.
|
||||
@@ -0,0 +1,229 @@
|
||||
{
|
||||
"app": {
|
||||
"shared": {
|
||||
"alerts": {
|
||||
"keineOffenenPunkte": "Keine offenen Punkte"
|
||||
},
|
||||
"kpi": {
|
||||
"vorperiode": "Vorperiode"
|
||||
},
|
||||
"chart": {
|
||||
"aktuellerZeitraum": "Aktueller Zeitraum",
|
||||
"vorjahr": "Vorjahr",
|
||||
"wert": "Wert",
|
||||
"keineDatenGewaehltenZeitraum": "Keine Daten im gewählten Zeitraum",
|
||||
"161616": "#161616",
|
||||
"sonstige": "Sonstige"
|
||||
},
|
||||
"state": {
|
||||
"kennzahlenBerechnet": "Kennzahlen werden berechnet …"
|
||||
},
|
||||
"table": {
|
||||
"vorherigeSeite": "Vorherige Seite",
|
||||
"naechsteSeite": "Nächste Seite",
|
||||
"keineEintraege": "Keine Einträge"
|
||||
}
|
||||
},
|
||||
"dashboard": {
|
||||
"widget": {
|
||||
"detailsAnsehen": "Details ansehen"
|
||||
},
|
||||
"page": {
|
||||
"handlungsbedarf": "Handlungsbedarf",
|
||||
"sortiertDringlichkeitJedeZeile": "Sortiert nach Dringlichkeit, jede Zeile führt in die zugehörige Arbeitsliste",
|
||||
"zahlungseingangZeitverlauf": "Zahlungseingang im Zeitverlauf",
|
||||
"aktuellerZeitraumGegenVorjahreszeitraum": "Aktueller Zeitraum gegen den Vorjahreszeitraum",
|
||||
"gezaehltTagZahlungseingangsNicht": "Gezählt wird der Tag des Zahlungseingangs, nicht das Rechnungsdatum. Dadurch entspricht die Kurve dem tatsächlichen Geldfluss.",
|
||||
"finanzenAuswerten": "In den Finanzen auswerten",
|
||||
"umsatzGewerk": "Umsatz nach Gewerk",
|
||||
"zahlungseingangLetzten30Tage": "Zahlungseingang der letzten 30 Tage",
|
||||
"fuenfGewerkeFesterRangfolge": "Fünf Gewerke in fester Rangfolge: dieselbe Kodierung wie auf der Finanzseite.",
|
||||
"angebotsgeschaeftAb6000": "Angebotsgeschäft ab 6.000 €, zum Stichtag",
|
||||
"nurVorhabenFuerSchriftliches": "Nur Vorhaben, für die ein schriftliches Angebot erstellt wird. Wartung und Notdienst laufen ohne Angebot und würden die Quoten verfälschen.",
|
||||
"auftraegeAuswerten": "Aufträge auswerten",
|
||||
"auslastungMonteure": "Auslastung Monteure",
|
||||
"produktiveStundenGegenVerfuegbare": "Produktive Stunden gegen verfügbare Stunden",
|
||||
"nurAuftraegeGebuchteStunden": "Nur auf Aufträge gebuchte Stunden zählen als produktiv. Über 100 % bedeutet Überstunden.",
|
||||
"ganzesTeamAnsehen": "Ganzes Team ansehen",
|
||||
"zahlungseingang": "Zahlungseingang",
|
||||
"beauftragtArbeit": "beauftragt oder in Arbeit",
|
||||
"meineAufgaben": "Meine Aufgaben",
|
||||
"wasHeuteAnstehtWer": "Was heute ansteht und wer zugewiesen ist.",
|
||||
"aufgaben": "Aufgaben",
|
||||
"termineHeute": "Termine heute",
|
||||
"einsaetzeAufmasseAbnahmenTages": "Einsätze, Aufmaße und Abnahmen des Tages.",
|
||||
"termine": "Termine",
|
||||
"hrAntraege": "HR-Anträge",
|
||||
"offeneUrlaubsZeitkorrekturantraege": "Offene Urlaubs- und Zeitkorrekturanträge.",
|
||||
"hrHub": "HR-Hub"
|
||||
}
|
||||
},
|
||||
"analytics": {
|
||||
"filter": {
|
||||
"gewerk": "Gewerk",
|
||||
"status": "Status",
|
||||
"quelle": "Quelle",
|
||||
"region": "Region",
|
||||
"mitarbeiter": "Mitarbeiter"
|
||||
},
|
||||
"shell": {
|
||||
"navigation": "Navigation",
|
||||
"suchen": "Suchen",
|
||||
"benachrichtigungen": "Benachrichtigungen",
|
||||
"hilfe": "Hilfe"
|
||||
},
|
||||
"actions": {
|
||||
"ansichtAktualisieren": "Ansicht aktualisieren",
|
||||
"aktualisieren": "Aktualisieren",
|
||||
"ansichtExportieren": "Ansicht exportieren",
|
||||
"export": "Export",
|
||||
"ansichtTeilen": "Ansicht teilen"
|
||||
},
|
||||
"nav": {
|
||||
"uebersicht": "Übersicht",
|
||||
"kennzahlenDenenBetriebGesteuert": "Die Kennzahlen, nach denen der Betrieb gesteuert wird: Umsatz, Auslastung, offene Posten und Handlungsbedarf.",
|
||||
"finanzen": "Finanzen",
|
||||
"umsatzZahlungseingangOffeneForderungen": "Umsatz, Zahlungseingang und offene Forderungen: die Sicht der Buchhaltung auf den Betrieb.",
|
||||
"anfrageBisAbnahmeWo": "Von der Anfrage bis zur Abnahme: wo die Vorgänge stehen, was gebunden ist und was über Termin läuft.",
|
||||
"kunden": "Kunden",
|
||||
"woherKundenKommenWie": "Woher Kunden kommen, wie viel sie bringen und wo Nachfassen lohnt.",
|
||||
"team": "Team",
|
||||
"auslastungProduktiveStundenAbschluesse": "Auslastung, produktive Stunden und Abschlüsse der Monteure: die Kennzahl hinter der Terminplanung.",
|
||||
"dashboard": "Dashboard",
|
||||
"analytics": "Analytics",
|
||||
"komponenten": "Komponenten"
|
||||
},
|
||||
"alerts": {
|
||||
"zahlungszielUeberschritten": "Zahlungsziel überschritten",
|
||||
"mahnlaufStarten": "Mahnlauf starten",
|
||||
"forderungenAelter90Tage": "Forderungen älter als 90 Tage",
|
||||
"einbringungsquoteSinktDeutlich": "Einbringungsquote sinkt deutlich",
|
||||
"inkassoPruefen": "Inkasso prüfen",
|
||||
"termineNeuPlanen": "Termine neu planen",
|
||||
"seitUeber21Tagen": "Seit über 21 Tagen offen",
|
||||
"nachfassen": "Nachfassen",
|
||||
"bescheidAusstehend": "Bescheid ausstehend",
|
||||
"statusPruefen": "Status prüfen",
|
||||
"bewilligt": "Bewilligt",
|
||||
"beantragt": "Beantragt",
|
||||
"nichtBeantragt": "Nicht beantragt"
|
||||
},
|
||||
"auftraege": {
|
||||
"neueVorgaenge": "Neue Vorgänge",
|
||||
"anlagedatumGezaehlt": "Nach Anlagedatum gezählt",
|
||||
"aktiveAuftraege": "Aktive Aufträge",
|
||||
"ueberTermin": "Über Termin",
|
||||
"abschlussquote": "Abschlussquote",
|
||||
"angebotAuftrag": "Aus Angebot wird Auftrag",
|
||||
"durchlaufzeit": "Ø Durchlaufzeit",
|
||||
"anfrageBisAbnahme": "Anfrage bis Abnahme",
|
||||
"auftragseingang": "Auftragseingang",
|
||||
"neuAngelegteVorgaengeZeitverlauf": "Neu angelegte Vorgänge im Zeitverlauf",
|
||||
"auftragswert": "Ø Auftragswert",
|
||||
"vertriebstrichter": "Vertriebstrichter",
|
||||
"angebotsgeschaeftAb6000": "Angebotsgeschäft ab 6.000 €, nach erreichter Stufe",
|
||||
"vorgaengeStatus": "Vorgänge nach Status",
|
||||
"alleVorgaengeSeitBeginn": "Alle Vorgänge seit Beginn, unabhängig vom Zeitraumfilter",
|
||||
"vorgaengeGewerk": "Vorgänge nach Gewerk",
|
||||
"anzahlSeitBeginnStichtag": "Anzahl seit Beginn, zum Stichtag",
|
||||
"foerderquoteBafaKfw": "Förderquote BAFA / KfW",
|
||||
"bezugsgroesseNurFoerderfaehigeLeistungen": "Bezugsgröße sind nur förderfähige Leistungen: Wärmepumpe, PV-Anlage, Batteriespeicher und Lüftungsanlage. Über alle Aufträge gerechnet wäre die Quote wertlos.",
|
||||
"foerdersumme": "Fördersumme",
|
||||
"jeAuftrag": "Ø je Auftrag",
|
||||
"beauftragtArbeit": "Beauftragt oder in Arbeit",
|
||||
"keineAktivenAuftraege": "Keine aktiven Aufträge",
|
||||
"auftrag": "Auftrag",
|
||||
"verantwortlich": "Verantwortlich",
|
||||
"termin": "Termin"
|
||||
},
|
||||
"finanzen": {
|
||||
"umsatzBezahlt": "Umsatz (bezahlt)",
|
||||
"zahlungseingangZeitraum": "Zahlungseingang im Zeitraum",
|
||||
"offeneForderungen": "Offene Forderungen",
|
||||
"zahlungsdauer": "Ø Zahlungsdauer",
|
||||
"rechnungBisZahlungseingang": "Rechnung bis Zahlungseingang",
|
||||
"deckungsbeitrag": "Deckungsbeitrag",
|
||||
"gewichtetUeberAbschluesse": "Gewichtet über Abschlüsse",
|
||||
"aktuellerZeitraumGegenVorjahr": "Aktueller Zeitraum gegen Vorjahr",
|
||||
"grundlageBezahlteAusgangsrechnungenZahlungsdatum": "Grundlage sind bezahlte Ausgangsrechnungen zum Zahlungsdatum.",
|
||||
"veraenderung": "Veränderung",
|
||||
"offenerBestandStatus": "Offener Bestand nach Status",
|
||||
"wieVielOffenenBestands": "Wie viel des offenen Bestands bereits überfällig ist",
|
||||
"bezugsgroesseAusschliesslichOffeneBestand": "Bezugsgröße ist ausschließlich der offene Bestand zum Stichtag. Bezahlte Rechnungen sind nicht enthalten, sie würden über 90 % des Rings belegen und die Aussage erdrücken.",
|
||||
"offeneForderungenAlter": "Offene Forderungen nach Alter",
|
||||
"alterAbFaelligkeitsdatumAb": "Alter ab Fälligkeitsdatum. Ab 60 Tagen sinkt die Einbringungsquote erfahrungsgemäß deutlich.",
|
||||
"aelter90Tage": "Älter als 90 Tage",
|
||||
"abschreibungsrisiko": "Abschreibungsrisiko",
|
||||
"ueberfaelligGesamt": "Überfällig gesamt",
|
||||
"sortiertFaelligkeitArbeitslisteFuer": "Sortiert nach Fälligkeit, die Arbeitsliste für den Mahnlauf",
|
||||
"keineOffenenRechnungen": "Keine offenen Rechnungen",
|
||||
"rechnung": "Rechnung",
|
||||
"gestellt": "Gestellt",
|
||||
"ueberfaellig": "Überfällig",
|
||||
"betrag": "Betrag"
|
||||
},
|
||||
"kunden": {
|
||||
"neueKunden": "Neue Kunden",
|
||||
"wiederkehrendeKunden": "Wiederkehrende Kunden",
|
||||
"mehrAuftrag": "Mehr als ein Auftrag",
|
||||
"kundenwert": "Ø Kundenwert",
|
||||
"jeKundeMindestensEinem": "Je Kunde mit mindestens einem Auftrag",
|
||||
"reaktivierbar": "Reaktivierbar",
|
||||
"ueber12MonateOhne": "Über 12 Monate ohne Auftrag",
|
||||
"neukundenZeitverlauf": "Neukunden im Zeitverlauf",
|
||||
"anlagedatumKundendatensatzes": "Anlagedatum des Kundendatensatzes",
|
||||
"kundenQuelle": "Kunden nach Quelle",
|
||||
"wieErstkontaktZustandeKam": "Wie der Erstkontakt zustande kam",
|
||||
"empfehlungenBestandskundenZusammenZeigen": "Empfehlungen und Bestandskunden zusammen zeigen, wie stark der Betrieb ohne Werbebudget wächst.",
|
||||
"kundenstruktur": "Kundenstruktur",
|
||||
"privatGewerbeVerwaltung": "Privat, Gewerbe, Verwaltung",
|
||||
"topKundenUmsatz": "Top-Kunden nach Umsatz",
|
||||
"bezahlterGesamtumsatz": "Bezahlter Gesamtumsatz",
|
||||
"kundenHistorieAberUeber": "Kunden mit Historie, aber über 12 Monate ohne Auftrag",
|
||||
"alleKundenWarenZuletzt": "Alle Kunden waren zuletzt aktiv",
|
||||
"durchschnittlichUeber30Tage": "Durchschnittlich über 30 Tage bis zum Zahlungseingang",
|
||||
"keineAuffaelligkeiten": "Keine Auffälligkeiten",
|
||||
"kunde": "Kunde",
|
||||
"auftraege": "Aufträge",
|
||||
"umsatz": "Umsatz",
|
||||
"zahlung": "Ø Zahlung",
|
||||
"letzterAuftrag": "Letzter Auftrag",
|
||||
"kundenGesamt": "Kunden gesamt"
|
||||
},
|
||||
"team": {
|
||||
"einsatz": "Im Einsatz",
|
||||
"auslastung": "Ø Auslastung",
|
||||
"produktiveStundenZuVerfuegbaren": "Produktive Stunden zu verfügbaren",
|
||||
"produktivquote": "Produktivquote",
|
||||
"anteilAuftragsbezogenerStunden": "Anteil auftragsbezogener Stunden",
|
||||
"erfassteStunden": "Erfasste Stunden",
|
||||
"abwesenheit": "Abwesenheit",
|
||||
"urlaubKrankheitFortbildung": "Urlaub, Krankheit, Fortbildung",
|
||||
"auslastungJeMitarbeiter": "Auslastung je Mitarbeiter",
|
||||
"produktiveStundenGegenVertraglich": "Produktive Stunden gegen vertraglich verfügbare Stunden",
|
||||
"werteUeber100Bedeuten": "Werte über 100 % bedeuten Überstunden. Dauerhaft über 95 % ist ein Warnsignal für Terminrisiken.",
|
||||
"abschluesseJeMitarbeiter": "Abschlüsse je Mitarbeiter",
|
||||
"anzahlGewaehltenZeitraumDahinter": "Anzahl im gewählten Zeitraum, dahinter der zugehörige Auftragswert",
|
||||
"abwesenheiten": "Abwesenheiten",
|
||||
"stundenGewaehltenZeitraum": "Stunden im gewählten Zeitraum",
|
||||
"mitarbeiteruebersicht": "Mitarbeiterübersicht",
|
||||
"alleGewerblichBeschaeftigtenKennzahlen": "Alle gewerblich Beschäftigten mit Kennzahlen",
|
||||
"stunden": "Stunden",
|
||||
"davonProduktiv": "davon produktiv",
|
||||
"auslastung2": "Auslastung",
|
||||
"abgeschlossen": "Abgeschlossen",
|
||||
"auftragswert": "Auftragswert"
|
||||
},
|
||||
"uebersicht": {
|
||||
"auslastungMonteure": "Ø Auslastung Monteure",
|
||||
"zahlungseingangGewaehltenZeitraum": "Zahlungseingang im gewählten Zeitraum",
|
||||
"fuenfGewerkeFesterRangfolge": "Fünf Gewerke in fester Rangfolge. Dieselbe Kodierung wie auf der Finanzseite. Ein Ring und eine Balkenliste für dieselbe Zahl wären zwei Bilder für einen Sachverhalt.",
|
||||
"nurVorhabenFuerSchriftliches": "Nur Vorhaben, für die ein schriftliches Angebot erstellt wird. Wartung und Notdienst laufen ohne Angebot und würden die Quoten verfälschen. Rechte Spalte: Übergang von der vorherigen Stufe.",
|
||||
"faelligkeitsdatumUeberschrittenNochNicht": "Fälligkeitsdatum überschritten, noch nicht abgeschlossen",
|
||||
"keinAuftragUeberTermin": "Kein Auftrag ist über Termin: sehr gut.",
|
||||
"faelligSeit": "Fällig seit",
|
||||
"umsatzZahlungseingang": "Umsatz (Zahlungseingang)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,927 @@
|
||||
[
|
||||
{
|
||||
"key": "app.shared.alerts.keineOffenenPunkte",
|
||||
"de": "Keine offenen Punkte",
|
||||
"quelle": "features/dashboard/components/cards/AlertList.tsx:25"
|
||||
},
|
||||
{
|
||||
"key": "app.shared.kpi.vorperiode",
|
||||
"de": "Vorperiode",
|
||||
"quelle": "features/dashboard/components/cards/KPICard.tsx:36"
|
||||
},
|
||||
{
|
||||
"key": "app.shared.chart.aktuellerZeitraum",
|
||||
"de": "Aktueller Zeitraum",
|
||||
"quelle": "features/dashboard/components/charts/AreaVerlauf.tsx:112"
|
||||
},
|
||||
{
|
||||
"key": "app.shared.chart.vorjahr",
|
||||
"de": "Vorjahr",
|
||||
"quelle": "features/dashboard/components/charts/AreaVerlauf.tsx:124"
|
||||
},
|
||||
{
|
||||
"key": "app.shared.chart.wert",
|
||||
"de": "Wert",
|
||||
"quelle": "features/dashboard/components/charts/BalkenVertikal.tsx:94"
|
||||
},
|
||||
{
|
||||
"key": "app.shared.chart.keineDatenGewaehltenZeitraum",
|
||||
"de": "Keine Daten im gewählten Zeitraum",
|
||||
"quelle": "features/dashboard/components/charts/ChartLeer.tsx:6"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.widget.detailsAnsehen",
|
||||
"de": "Details ansehen",
|
||||
"quelle": "features/dashboard/components/dashboard/WidgetKachel.tsx:51"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.filter.gewerk",
|
||||
"de": "Gewerk",
|
||||
"quelle": "features/dashboard/components/filters/AnalyticsToolbar.tsx:111"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.filter.status",
|
||||
"de": "Status",
|
||||
"quelle": "features/dashboard/components/filters/AnalyticsToolbar.tsx:121"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.filter.quelle",
|
||||
"de": "Quelle",
|
||||
"quelle": "features/dashboard/components/filters/AnalyticsToolbar.tsx:134"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.filter.region",
|
||||
"de": "Region",
|
||||
"quelle": "features/dashboard/components/filters/AnalyticsToolbar.tsx:144"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.filter.mitarbeiter",
|
||||
"de": "Mitarbeiter",
|
||||
"quelle": "features/dashboard/components/filters/AnalyticsToolbar.tsx:154"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.shell.navigation",
|
||||
"de": "Navigation",
|
||||
"quelle": "features/dashboard/components/layout/AppShell.tsx:265"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.shell.suchen",
|
||||
"de": "Suchen",
|
||||
"quelle": "features/dashboard/components/layout/AppShell.tsx:287"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.shell.benachrichtigungen",
|
||||
"de": "Benachrichtigungen",
|
||||
"quelle": "features/dashboard/components/layout/AppShell.tsx:294"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.shell.hilfe",
|
||||
"de": "Hilfe",
|
||||
"quelle": "features/dashboard/components/layout/AppShell.tsx:300"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.actions.ansichtAktualisieren",
|
||||
"de": "Ansicht aktualisieren",
|
||||
"quelle": "features/dashboard/components/layout/PageHeader.tsx:60"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.actions.aktualisieren",
|
||||
"de": "Aktualisieren",
|
||||
"quelle": "features/dashboard/components/layout/PageHeader.tsx:63"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.actions.ansichtExportieren",
|
||||
"de": "Ansicht exportieren",
|
||||
"quelle": "features/dashboard/components/layout/PageHeader.tsx:68"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.actions.export",
|
||||
"de": "Export",
|
||||
"quelle": "features/dashboard/components/layout/PageHeader.tsx:71"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.actions.ansichtTeilen",
|
||||
"de": "Ansicht teilen",
|
||||
"quelle": "features/dashboard/components/layout/PageHeader.tsx:76"
|
||||
},
|
||||
{
|
||||
"key": "app.shared.state.kennzahlenBerechnet",
|
||||
"de": "Kennzahlen werden berechnet …",
|
||||
"quelle": "features/dashboard/components/layout/SeitenZustand.tsx:29"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.nav.uebersicht",
|
||||
"de": "Übersicht",
|
||||
"quelle": "features/dashboard/components/layout/navigation.ts:108"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.nav.kennzahlenDenenBetriebGesteuert",
|
||||
"de": "Die Kennzahlen, nach denen der Betrieb gesteuert wird: Umsatz, Auslastung, offene Posten und Handlungsbedarf.",
|
||||
"quelle": "features/dashboard/components/layout/navigation.ts:110"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.nav.finanzen",
|
||||
"de": "Finanzen",
|
||||
"quelle": "features/dashboard/components/layout/navigation.ts:115"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.nav.umsatzZahlungseingangOffeneForderungen",
|
||||
"de": "Umsatz, Zahlungseingang und offene Forderungen: die Sicht der Buchhaltung auf den Betrieb.",
|
||||
"quelle": "features/dashboard/components/layout/navigation.ts:117"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.nav.anfrageBisAbnahmeWo",
|
||||
"de": "Von der Anfrage bis zur Abnahme: wo die Vorgänge stehen, was gebunden ist und was über Termin läuft.",
|
||||
"quelle": "features/dashboard/components/layout/navigation.ts:124"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.nav.kunden",
|
||||
"de": "Kunden",
|
||||
"quelle": "features/dashboard/components/layout/navigation.ts:129"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.nav.woherKundenKommenWie",
|
||||
"de": "Woher Kunden kommen, wie viel sie bringen und wo Nachfassen lohnt.",
|
||||
"quelle": "features/dashboard/components/layout/navigation.ts:131"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.nav.team",
|
||||
"de": "Team",
|
||||
"quelle": "features/dashboard/components/layout/navigation.ts:136"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.nav.auslastungProduktiveStundenAbschluesse",
|
||||
"de": "Auslastung, produktive Stunden und Abschlüsse der Monteure: die Kennzahl hinter der Terminplanung.",
|
||||
"quelle": "features/dashboard/components/layout/navigation.ts:138"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.nav.dashboard",
|
||||
"de": "Dashboard",
|
||||
"quelle": "features/dashboard/components/layout/navigation.ts:52"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.nav.analytics",
|
||||
"de": "Analytics",
|
||||
"quelle": "features/dashboard/components/layout/navigation.ts:60"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.nav.komponenten",
|
||||
"de": "Komponenten",
|
||||
"quelle": "features/dashboard/components/layout/navigation.ts:83"
|
||||
},
|
||||
{
|
||||
"key": "app.shared.table.vorherigeSeite",
|
||||
"de": "Vorherige Seite",
|
||||
"quelle": "features/dashboard/components/tables/DataTable.tsx:200"
|
||||
},
|
||||
{
|
||||
"key": "app.shared.table.naechsteSeite",
|
||||
"de": "Nächste Seite",
|
||||
"quelle": "features/dashboard/components/tables/DataTable.tsx:211"
|
||||
},
|
||||
{
|
||||
"key": "app.shared.table.keineEintraege",
|
||||
"de": "Keine Einträge",
|
||||
"quelle": "features/dashboard/components/tables/DataTable.tsx:37"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.alerts.zahlungszielUeberschritten",
|
||||
"de": "Zahlungsziel überschritten",
|
||||
"quelle": "features/dashboard/data/calculations.ts:478"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.alerts.mahnlaufStarten",
|
||||
"de": "Mahnlauf starten",
|
||||
"quelle": "features/dashboard/data/calculations.ts:480"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.alerts.forderungenAelter90Tage",
|
||||
"de": "Forderungen älter als 90 Tage",
|
||||
"quelle": "features/dashboard/data/calculations.ts:487"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.alerts.einbringungsquoteSinktDeutlich",
|
||||
"de": "Einbringungsquote sinkt deutlich",
|
||||
"quelle": "features/dashboard/data/calculations.ts:488"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.alerts.inkassoPruefen",
|
||||
"de": "Inkasso prüfen",
|
||||
"quelle": "features/dashboard/data/calculations.ts:490"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.alerts.termineNeuPlanen",
|
||||
"de": "Termine neu planen",
|
||||
"quelle": "features/dashboard/data/calculations.ts:500"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.alerts.seitUeber21Tagen",
|
||||
"de": "Seit über 21 Tagen offen",
|
||||
"quelle": "features/dashboard/data/calculations.ts:512"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.alerts.nachfassen",
|
||||
"de": "Nachfassen",
|
||||
"quelle": "features/dashboard/data/calculations.ts:514"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.alerts.bescheidAusstehend",
|
||||
"de": "Bescheid ausstehend",
|
||||
"quelle": "features/dashboard/data/calculations.ts:524"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.alerts.statusPruefen",
|
||||
"de": "Status prüfen",
|
||||
"quelle": "features/dashboard/data/calculations.ts:526"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.alerts.bewilligt",
|
||||
"de": "Bewilligt",
|
||||
"quelle": "features/dashboard/data/calculations.ts:563"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.alerts.beantragt",
|
||||
"de": "Beantragt",
|
||||
"quelle": "features/dashboard/data/calculations.ts:564"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.alerts.nichtBeantragt",
|
||||
"de": "Nicht beantragt",
|
||||
"quelle": "features/dashboard/data/calculations.ts:565"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.neueVorgaenge",
|
||||
"de": "Neue Vorgänge",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:102"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.anlagedatumGezaehlt",
|
||||
"de": "Nach Anlagedatum gezählt",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:104"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.aktiveAuftraege",
|
||||
"de": "Aktive Aufträge",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:110"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.ueberTermin",
|
||||
"de": "Über Termin",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:117"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.abschlussquote",
|
||||
"de": "Abschlussquote",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:129"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.angebotAuftrag",
|
||||
"de": "Aus Angebot wird Auftrag",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:131"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.durchlaufzeit",
|
||||
"de": "Ø Durchlaufzeit",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:136"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.anfrageBisAbnahme",
|
||||
"de": "Anfrage bis Abnahme",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:138"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.auftragseingang",
|
||||
"de": "Auftragseingang",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:148"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.neuAngelegteVorgaengeZeitverlauf",
|
||||
"de": "Neu angelegte Vorgänge im Zeitverlauf",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:149"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.auftragswert",
|
||||
"de": "Ø Auftragswert",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:158"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.vertriebstrichter",
|
||||
"de": "Vertriebstrichter",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:167"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.angebotsgeschaeftAb6000",
|
||||
"de": "Angebotsgeschäft ab 6.000 €, nach erreichter Stufe",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:168"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.vorgaengeStatus",
|
||||
"de": "Vorgänge nach Status",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:178"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.alleVorgaengeSeitBeginn",
|
||||
"de": "Alle Vorgänge seit Beginn, unabhängig vom Zeitraumfilter",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:179"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.vorgaengeGewerk",
|
||||
"de": "Vorgänge nach Gewerk",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:191"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.anzahlSeitBeginnStichtag",
|
||||
"de": "Anzahl seit Beginn, zum Stichtag",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:192"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.foerderquoteBafaKfw",
|
||||
"de": "Förderquote BAFA / KfW",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:204"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.bezugsgroesseNurFoerderfaehigeLeistungen",
|
||||
"de": "Bezugsgröße sind nur förderfähige Leistungen: Wärmepumpe, PV-Anlage, Batteriespeicher und Lüftungsanlage. Über alle Aufträge gerechnet wäre die Quote wertlos.",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:208"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.foerdersumme",
|
||||
"de": "Fördersumme",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:219"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.jeAuftrag",
|
||||
"de": "Ø je Auftrag",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:221"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.beauftragtArbeit",
|
||||
"de": "Beauftragt oder in Arbeit",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:232"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.keineAktivenAuftraege",
|
||||
"de": "Keine aktiven Aufträge",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:241"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.auftrag",
|
||||
"de": "Auftrag",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:42"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.verantwortlich",
|
||||
"de": "Verantwortlich",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:63"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.auftraege.termin",
|
||||
"de": "Termin",
|
||||
"quelle": "features/dashboard/pages/AuftraegeSeite.tsx:73"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.handlungsbedarf",
|
||||
"de": "Handlungsbedarf",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:100"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.sortiertDringlichkeitJedeZeile",
|
||||
"de": "Sortiert nach Dringlichkeit, jede Zeile führt in die zugehörige Arbeitsliste",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:101"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.zahlungseingangZeitverlauf",
|
||||
"de": "Zahlungseingang im Zeitverlauf",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:111"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.aktuellerZeitraumGegenVorjahreszeitraum",
|
||||
"de": "Aktueller Zeitraum gegen den Vorjahreszeitraum",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:112"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.gezaehltTagZahlungseingangsNicht",
|
||||
"de": "Gezählt wird der Tag des Zahlungseingangs, nicht das Rechnungsdatum. Dadurch entspricht die Kurve dem tatsächlichen Geldfluss.",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:113"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.finanzenAuswerten",
|
||||
"de": "In den Finanzen auswerten",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:117"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.umsatzGewerk",
|
||||
"de": "Umsatz nach Gewerk",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:140"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.zahlungseingangLetzten30Tage",
|
||||
"de": "Zahlungseingang der letzten 30 Tage",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:141"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.fuenfGewerkeFesterRangfolge",
|
||||
"de": "Fünf Gewerke in fester Rangfolge: dieselbe Kodierung wie auf der Finanzseite.",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:142"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.angebotsgeschaeftAb6000",
|
||||
"de": "Angebotsgeschäft ab 6.000 €, zum Stichtag",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:158"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.nurVorhabenFuerSchriftliches",
|
||||
"de": "Nur Vorhaben, für die ein schriftliches Angebot erstellt wird. Wartung und Notdienst laufen ohne Angebot und würden die Quoten verfälschen.",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:159"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.auftraegeAuswerten",
|
||||
"de": "Aufträge auswerten",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:163"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.auslastungMonteure",
|
||||
"de": "Auslastung Monteure",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:171"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.produktiveStundenGegenVerfuegbare",
|
||||
"de": "Produktive Stunden gegen verfügbare Stunden",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:172"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.nurAuftraegeGebuchteStunden",
|
||||
"de": "Nur auf Aufträge gebuchte Stunden zählen als produktiv. Über 100 % bedeutet Überstunden.",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:173"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.ganzesTeamAnsehen",
|
||||
"de": "Ganzes Team ansehen",
|
||||
"quelle": "features/dashboard/pages/DashboardSeite.tsx:177"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.umsatzBezahlt",
|
||||
"de": "Umsatz (bezahlt)",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:105"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.zahlungseingangZeitraum",
|
||||
"de": "Zahlungseingang im Zeitraum",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:107"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.offeneForderungen",
|
||||
"de": "Offene Forderungen",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:113"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.zahlungsdauer",
|
||||
"de": "Ø Zahlungsdauer",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:128"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.rechnungBisZahlungseingang",
|
||||
"de": "Rechnung bis Zahlungseingang",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:130"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.deckungsbeitrag",
|
||||
"de": "Deckungsbeitrag",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:136"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.gewichtetUeberAbschluesse",
|
||||
"de": "Gewichtet über Abschlüsse",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:138"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.aktuellerZeitraumGegenVorjahr",
|
||||
"de": "Aktueller Zeitraum gegen Vorjahr",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:147"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.grundlageBezahlteAusgangsrechnungenZahlungsdatum",
|
||||
"de": "Grundlage sind bezahlte Ausgangsrechnungen zum Zahlungsdatum.",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:148"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.veraenderung",
|
||||
"de": "Veränderung",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:171"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.offenerBestandStatus",
|
||||
"de": "Offener Bestand nach Status",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:180"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.wieVielOffenenBestands",
|
||||
"de": "Wie viel des offenen Bestands bereits überfällig ist",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:181"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.bezugsgroesseAusschliesslichOffeneBestand",
|
||||
"de": "Bezugsgröße ist ausschließlich der offene Bestand zum Stichtag. Bezahlte Rechnungen sind nicht enthalten, sie würden über 90 % des Rings belegen und die Aussage erdrücken.",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:182"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.offeneForderungenAlter",
|
||||
"de": "Offene Forderungen nach Alter",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:198"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.alterAbFaelligkeitsdatumAb",
|
||||
"de": "Alter ab Fälligkeitsdatum. Ab 60 Tagen sinkt die Einbringungsquote erfahrungsgemäß deutlich.",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:200"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.aelter90Tage",
|
||||
"de": "Älter als 90 Tage",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:213"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.abschreibungsrisiko",
|
||||
"de": "Abschreibungsrisiko",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:217"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.ueberfaelligGesamt",
|
||||
"de": "Überfällig gesamt",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:219"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.sortiertFaelligkeitArbeitslisteFuer",
|
||||
"de": "Sortiert nach Fälligkeit, die Arbeitsliste für den Mahnlauf",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:242"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.keineOffenenRechnungen",
|
||||
"de": "Keine offenen Rechnungen",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:249"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.rechnung",
|
||||
"de": "Rechnung",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:50"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.gestellt",
|
||||
"de": "Gestellt",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:65"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.ueberfaellig",
|
||||
"de": "Überfällig",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:71"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.finanzen.betrag",
|
||||
"de": "Betrag",
|
||||
"quelle": "features/dashboard/pages/FinanzenSeite.tsx:92"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.neueKunden",
|
||||
"de": "Neue Kunden",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:105"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.wiederkehrendeKunden",
|
||||
"de": "Wiederkehrende Kunden",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:113"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.mehrAuftrag",
|
||||
"de": "Mehr als ein Auftrag",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:115"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.kundenwert",
|
||||
"de": "Ø Kundenwert",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:120"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.jeKundeMindestensEinem",
|
||||
"de": "Je Kunde mit mindestens einem Auftrag",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:122"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.reaktivierbar",
|
||||
"de": "Reaktivierbar",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:127"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.ueber12MonateOhne",
|
||||
"de": "Über 12 Monate ohne Auftrag",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:129"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.neukundenZeitverlauf",
|
||||
"de": "Neukunden im Zeitverlauf",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:139"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.anlagedatumKundendatensatzes",
|
||||
"de": "Anlagedatum des Kundendatensatzes",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:140"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.kundenQuelle",
|
||||
"de": "Kunden nach Quelle",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:148"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.wieErstkontaktZustandeKam",
|
||||
"de": "Wie der Erstkontakt zustande kam",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:149"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.empfehlungenBestandskundenZusammenZeigen",
|
||||
"de": "Empfehlungen und Bestandskunden zusammen zeigen, wie stark der Betrieb ohne Werbebudget wächst.",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:150"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.kundenstruktur",
|
||||
"de": "Kundenstruktur",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:162"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.privatGewerbeVerwaltung",
|
||||
"de": "Privat, Gewerbe, Verwaltung",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:162"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.topKundenUmsatz",
|
||||
"de": "Top-Kunden nach Umsatz",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:174"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.bezahlterGesamtumsatz",
|
||||
"de": "Bezahlter Gesamtumsatz",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:175"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.kundenHistorieAberUeber",
|
||||
"de": "Kunden mit Historie, aber über 12 Monate ohne Auftrag",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:185"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.alleKundenWarenZuletzt",
|
||||
"de": "Alle Kunden waren zuletzt aktiv",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:192"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.durchschnittlichUeber30Tage",
|
||||
"de": "Durchschnittlich über 30 Tage bis zum Zahlungseingang",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:199"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.keineAuffaelligkeiten",
|
||||
"de": "Keine Auffälligkeiten",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:206"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.kunde",
|
||||
"de": "Kunde",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:35"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.auftraege",
|
||||
"de": "Aufträge",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:53"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.umsatz",
|
||||
"de": "Umsatz",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:60"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.zahlung",
|
||||
"de": "Ø Zahlung",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:67"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.letzterAuftrag",
|
||||
"de": "Letzter Auftrag",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:81"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.kunden.kundenGesamt",
|
||||
"de": "Kunden gesamt",
|
||||
"quelle": "features/dashboard/pages/KundenSeite.tsx:98"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.einsatz",
|
||||
"de": "Im Einsatz",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:112"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.auslastung",
|
||||
"de": "Ø Auslastung",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:119"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.produktiveStundenZuVerfuegbaren",
|
||||
"de": "Produktive Stunden zu verfügbaren",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:121"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.produktivquote",
|
||||
"de": "Produktivquote",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:126"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.anteilAuftragsbezogenerStunden",
|
||||
"de": "Anteil auftragsbezogener Stunden",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:128"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.erfassteStunden",
|
||||
"de": "Erfasste Stunden",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:132"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.abwesenheit",
|
||||
"de": "Abwesenheit",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:138"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.urlaubKrankheitFortbildung",
|
||||
"de": "Urlaub, Krankheit, Fortbildung",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:140"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.auslastungJeMitarbeiter",
|
||||
"de": "Auslastung je Mitarbeiter",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:148"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.produktiveStundenGegenVertraglich",
|
||||
"de": "Produktive Stunden gegen vertraglich verfügbare Stunden",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:149"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.werteUeber100Bedeuten",
|
||||
"de": "Werte über 100 % bedeuten Überstunden. Dauerhaft über 95 % ist ein Warnsignal für Terminrisiken.",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:150"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.abschluesseJeMitarbeiter",
|
||||
"de": "Abschlüsse je Mitarbeiter",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:165"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.anzahlGewaehltenZeitraumDahinter",
|
||||
"de": "Anzahl im gewählten Zeitraum, dahinter der zugehörige Auftragswert",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:166"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.abwesenheiten",
|
||||
"de": "Abwesenheiten",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:183"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.stundenGewaehltenZeitraum",
|
||||
"de": "Stunden im gewählten Zeitraum",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:184"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.mitarbeiteruebersicht",
|
||||
"de": "Mitarbeiterübersicht",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:197"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.alleGewerblichBeschaeftigtenKennzahlen",
|
||||
"de": "Alle gewerblich Beschäftigten mit Kennzahlen",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:198"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.stunden",
|
||||
"de": "Stunden",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:59"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.davonProduktiv",
|
||||
"de": "davon produktiv",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:66"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.auslastung2",
|
||||
"de": "Auslastung",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:73"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.abgeschlossen",
|
||||
"de": "Abgeschlossen",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:92"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.team.auftragswert",
|
||||
"de": "Auftragswert",
|
||||
"quelle": "features/dashboard/pages/TeamSeite.tsx:99"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.uebersicht.auslastungMonteure",
|
||||
"de": "Ø Auslastung Monteure",
|
||||
"quelle": "features/dashboard/pages/UebersichtSeite.tsx:111"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.uebersicht.zahlungseingangGewaehltenZeitraum",
|
||||
"de": "Zahlungseingang im gewählten Zeitraum",
|
||||
"quelle": "features/dashboard/pages/UebersichtSeite.tsx:173"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.uebersicht.fuenfGewerkeFesterRangfolge",
|
||||
"de": "Fünf Gewerke in fester Rangfolge. Dieselbe Kodierung wie auf der Finanzseite. Ein Ring und eine Balkenliste für dieselbe Zahl wären zwei Bilder für einen Sachverhalt.",
|
||||
"quelle": "features/dashboard/pages/UebersichtSeite.tsx:174"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.uebersicht.nurVorhabenFuerSchriftliches",
|
||||
"de": "Nur Vorhaben, für die ein schriftliches Angebot erstellt wird. Wartung und Notdienst laufen ohne Angebot und würden die Quoten verfälschen. Rechte Spalte: Übergang von der vorherigen Stufe.",
|
||||
"quelle": "features/dashboard/pages/UebersichtSeite.tsx:189"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.uebersicht.faelligkeitsdatumUeberschrittenNochNicht",
|
||||
"de": "Fälligkeitsdatum überschritten, noch nicht abgeschlossen",
|
||||
"quelle": "features/dashboard/pages/UebersichtSeite.tsx:214"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.uebersicht.keinAuftragUeberTermin",
|
||||
"de": "Kein Auftrag ist über Termin: sehr gut.",
|
||||
"quelle": "features/dashboard/pages/UebersichtSeite.tsx:221"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.uebersicht.faelligSeit",
|
||||
"de": "Fällig seit",
|
||||
"quelle": "features/dashboard/pages/UebersichtSeite.tsx:64"
|
||||
},
|
||||
{
|
||||
"key": "app.analytics.uebersicht.umsatzZahlungseingang",
|
||||
"de": "Umsatz (Zahlungseingang)",
|
||||
"quelle": "features/dashboard/pages/UebersichtSeite.tsx:88"
|
||||
},
|
||||
{
|
||||
"key": "app.shared.chart.161616",
|
||||
"de": "#161616",
|
||||
"quelle": "features/dashboard/utils/chartTheme.ts:149"
|
||||
},
|
||||
{
|
||||
"key": "app.shared.chart.sonstige",
|
||||
"de": "Sonstige",
|
||||
"quelle": "features/dashboard/utils/chartTheme.ts:232"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.zahlungseingang",
|
||||
"de": "Zahlungseingang",
|
||||
"quelle": "features/dashboard/view-models/dashboard.vm.ts:180"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.beauftragtArbeit",
|
||||
"de": "beauftragt oder in Arbeit",
|
||||
"quelle": "features/dashboard/view-models/dashboard.vm.ts:204"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.meineAufgaben",
|
||||
"de": "Meine Aufgaben",
|
||||
"quelle": "features/dashboard/view-models/dashboard.vm.ts:254"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.wasHeuteAnstehtWer",
|
||||
"de": "Was heute ansteht und wer zugewiesen ist.",
|
||||
"quelle": "features/dashboard/view-models/dashboard.vm.ts:255"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.aufgaben",
|
||||
"de": "Aufgaben",
|
||||
"quelle": "features/dashboard/view-models/dashboard.vm.ts:257"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.termineHeute",
|
||||
"de": "Termine heute",
|
||||
"quelle": "features/dashboard/view-models/dashboard.vm.ts:262"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.einsaetzeAufmasseAbnahmenTages",
|
||||
"de": "Einsätze, Aufmaße und Abnahmen des Tages.",
|
||||
"quelle": "features/dashboard/view-models/dashboard.vm.ts:263"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.termine",
|
||||
"de": "Termine",
|
||||
"quelle": "features/dashboard/view-models/dashboard.vm.ts:265"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.hrAntraege",
|
||||
"de": "HR-Anträge",
|
||||
"quelle": "features/dashboard/view-models/dashboard.vm.ts:270"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.offeneUrlaubsZeitkorrekturantraege",
|
||||
"de": "Offene Urlaubs- und Zeitkorrekturanträge.",
|
||||
"quelle": "features/dashboard/view-models/dashboard.vm.ts:271"
|
||||
},
|
||||
{
|
||||
"key": "app.dashboard.page.hrHub",
|
||||
"de": "HR-Hub",
|
||||
"quelle": "features/dashboard/view-models/dashboard.vm.ts:273"
|
||||
}
|
||||
]
|
||||
+2
-1
@@ -9,7 +9,8 @@
|
||||
"start": "next start -p 4300",
|
||||
"lint": "eslint",
|
||||
"verify:carbon": "node scripts/verify-carbon.mjs",
|
||||
"verify:layout": "node scripts/verify-layout.mjs"
|
||||
"verify:layout": "node scripts/verify-layout.mjs",
|
||||
"i18n:inventar": "python3 scripts/i18n-inventar.py"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Sammelt alle sichtbaren Oberflaechentexte und schlaegt i18n-Schluessel vor.
|
||||
|
||||
Warum es das gibt: HiveGrid prueft mit `pnpm run verify:i18n`, dass keine
|
||||
Strings hartkodiert sind. Dieser Prototyp ist zu 100 % hartkodiertes Deutsch,
|
||||
weil er allein lauffaehig sein muss. Das Inventar nimmt dem Einbau die
|
||||
Sucharbeit ab.
|
||||
|
||||
Erzeugt zwei Dateien:
|
||||
docs/port/i18n-inventar.json Schluessel, Text, Fundstelle
|
||||
docs/port/i18n-de.json derselbe Inhalt als Baum, einfuegbar nach
|
||||
apps/web/public/locales/de/translations.json
|
||||
|
||||
Aufruf: npm run i18n:inventar
|
||||
|
||||
Die Schluesselnamen sind maschinell aus dem deutschen Text gebildet und ein
|
||||
Vorschlag, kein Ergebnis. Wo HiveGrid schon einen Schluessel hat, gilt dessen
|
||||
Name; welche das sind, steht in docs/port/TEXTE.md Abschnitt 1.
|
||||
"""
|
||||
|
||||
import json
|
||||
import pathlib
|
||||
import re
|
||||
import unicodedata
|
||||
|
||||
# Props und Objektfelder, die im Prototyp sichtbaren Text tragen.
|
||||
PROPS = ("titel", "untertitel", "beschreibung", "label", "hinweis", "leerText",
|
||||
"zusatz", "aktion", "text", "detail", "titleKey", "absprungText",
|
||||
"trendLabel", "quelle", "lage", "anrede", "name", "leerHinweis")
|
||||
PROP_RE = re.compile(r'\b(' + "|".join(PROPS) + r')\s*[=:]\s*"([^"]{2,200})"')
|
||||
JSX_RE = re.compile(r'>\s*([A-ZÄÖÜ][^<>{}\n]{3,120}?)\s*<')
|
||||
|
||||
# Nicht portiert: die Bausteingalerie. Keine Oberflaechentexte: die Attrappen.
|
||||
UEBERSPRINGEN = {"KomponentenSeite.tsx", "nutzer.ts", "seed.ts"}
|
||||
|
||||
SEITE = {
|
||||
"UebersichtSeite": ("analytics", "uebersicht"), "FinanzenSeite": ("analytics", "finanzen"),
|
||||
"AuftraegeSeite": ("analytics", "auftraege"), "KundenSeite": ("analytics", "kunden"),
|
||||
"TeamSeite": ("analytics", "team"), "DashboardSeite": ("dashboard", "page"),
|
||||
"dashboard.vm": ("dashboard", "page"), "navigation": ("analytics", "nav"),
|
||||
"calculations": ("analytics", "alerts"), "AnalyticsToolbar": ("analytics", "filter"),
|
||||
"AppShell": ("analytics", "shell"), "PageHeader": ("analytics", "actions"),
|
||||
"DataTable": ("shared", "table"), "SeitenZustand": ("shared", "state"),
|
||||
"ChartLeer": ("shared", "chart"), "AlertList": ("shared", "alerts"),
|
||||
"KPICard": ("shared", "kpi"), "AreaVerlauf": ("shared", "chart"),
|
||||
"BalkenVertikal": ("shared", "chart"), "WidgetKachel": ("dashboard", "widget"),
|
||||
"chartTheme": ("shared", "chart"),
|
||||
}
|
||||
|
||||
STOPP = {"der", "die", "das", "und", "oder", "im", "in", "den", "dem", "des", "zum",
|
||||
"zur", "für", "von", "mit", "auf", "aus", "bei", "nach", "als", "ein",
|
||||
"eine", "einen", "ist", "sind", "wird", "werden"}
|
||||
|
||||
|
||||
def slug(text, laenge=4):
|
||||
"""Kurzform fuer den Schluesselnamen: die ersten Sinnwoerter in camelCase."""
|
||||
t = (text.replace("ä", "ae").replace("ö", "oe").replace("ü", "ue")
|
||||
.replace("ß", "ss").replace("Ä", "Ae").replace("Ö", "Oe").replace("Ü", "Ue"))
|
||||
t = unicodedata.normalize("NFKD", t).encode("ascii", "ignore").decode()
|
||||
woerter = [w for w in re.findall(r"[A-Za-z0-9]+", t) if w.lower() not in STOPP][:laenge]
|
||||
if not woerter:
|
||||
woerter = ["text"]
|
||||
return woerter[0].lower() + "".join(w.capitalize() for w in woerter[1:])
|
||||
|
||||
|
||||
def sammle():
|
||||
dateien = sorted(pathlib.Path("features/dashboard").rglob("*.tsx")) + \
|
||||
sorted(pathlib.Path("features/dashboard").rglob("*.ts"))
|
||||
treffer = {}
|
||||
for p in dateien:
|
||||
if p.name in UEBERSPRINGEN:
|
||||
continue
|
||||
for nr, zeile in enumerate(p.read_text().split("\n"), 1):
|
||||
if zeile.lstrip().startswith(("*", "//")):
|
||||
continue
|
||||
for m in PROP_RE.finditer(zeile):
|
||||
treffer.setdefault(m.group(2), []).append(f"{p}:{nr}")
|
||||
for m in JSX_RE.finditer(zeile):
|
||||
t = m.group(1).strip()
|
||||
if t and not t.startswith("{") and any(c.isalpha() for c in t):
|
||||
treffer.setdefault(t, []).append(f"{p}:{nr}")
|
||||
return dict(sorted(treffer.items()))
|
||||
|
||||
|
||||
def main():
|
||||
texte = sammle()
|
||||
zeilen, baum, benutzt = [], {}, set()
|
||||
for text, orte in sorted(texte.items(), key=lambda kv: kv[1][0]):
|
||||
datei = pathlib.Path(orte[0].split(":")[0]).stem
|
||||
bereich, gruppe = SEITE.get(datei, ("shared", "misc"))
|
||||
basis = f"app.{bereich}.{gruppe}.{slug(text)}"
|
||||
schluessel, n = basis, 2
|
||||
while schluessel in benutzt:
|
||||
schluessel, n = f"{basis}{n}", n + 1
|
||||
benutzt.add(schluessel)
|
||||
zeilen.append((schluessel, text, orte[0]))
|
||||
knoten = baum
|
||||
for teil in schluessel.split(".")[:-1]:
|
||||
knoten = knoten.setdefault(teil, {})
|
||||
knoten[schluessel.split(".")[-1]] = text
|
||||
|
||||
ziel = pathlib.Path("docs/port")
|
||||
ziel.mkdir(parents=True, exist_ok=True)
|
||||
(ziel / "i18n-de.json").write_text(json.dumps(baum, ensure_ascii=False, indent=2) + "\n")
|
||||
(ziel / "i18n-inventar.json").write_text(json.dumps(
|
||||
[{"key": k, "de": t, "quelle": o} for k, t, o in zeilen],
|
||||
ensure_ascii=False, indent=1) + "\n")
|
||||
print(f"{len(zeilen)} Texte, {len(zeilen)} Schluessel -> docs/port/")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user