From 5a545182f2f83aced58dbbc5bcaf053eeda6b0a3 Mon Sep 17 00:00:00 2001 From: Stefan Mogeritsch Date: Tue, 24 Mar 2026 09:59:59 +0100 Subject: [PATCH] feat(docs, ui): restructure frontend documentation & introduce Vision directories for modularity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Moved existing FIGMA-related files into `Vison_01` and `Vision_02` folders to better support versioning and collaboration. - Added PostCSS configuration for extending plugins in Tailwind CSS. - Introduced new style guidelines, theme configurations, and modular imports for `Vision_02`. - Documented detailed ÖTO tournament structures and parameters for CSN/CDN inclusions. - Enhanced routing and UI files for future scalability, including new `theme.tsx` and `routes.tsx`. Signed-off-by: Stefan Mogeritsch --- .../FIGMA/{ => Vision_02}/ATTRIBUTIONS.md | 0 docs/06_Frontend/FIGMA/Vision_02/README.md | 1024 ++++++++++ .../FIGMA/Vision_02/guidelines.zip | Bin 0 -> 2683 bytes .../{ => Vision_02}/guidelines/Guidelines.md | 0 docs/06_Frontend/FIGMA/Vision_02/package.json | 90 + .../FIGMA/{ => Vision_02}/postcss.config.mjs | 0 docs/06_Frontend/FIGMA/Vision_02/src.zip | Bin 0 -> 437373 bytes .../FIGMA/Vision_02/src/app/App.tsx | 16 + .../src/app/components/Bewerbsliste.tsx | 0 .../src/app/components/Dashboard.tsx | 445 +++++ .../Vision_02/src/app/components/Login.tsx | 223 +++ .../src/app/components/NennungenTabelle.tsx | 0 .../src/app/components/NennungsMaske.tsx | 0 .../src/app/components/PferdReiterEingabe.tsx | 558 ++++++ .../src/app/components/TurnierAnsicht.tsx | 130 ++ .../src/app/components/TurnierErstellen.tsx | 145 ++ .../src/app/components/VerkaufBuchungen.tsx | 0 .../components/figma/ImageWithFallback.tsx | 0 .../src/app/components/turnier/BewerbeTab.tsx | 1751 +++++++++++++++++ .../components/turnier/FunktionaereTab.tsx | 398 ++++ .../components/turnier/OrganisationTab.tsx | 411 ++++ .../app/components/turnier/PreislisteTab.tsx | 345 ++++ .../app/components/turnier/StammdatenTab.tsx | 831 ++++++++ .../app/components/turnier/TransferTab.tsx | 325 +++ .../turnier/VeranstaltungUebersicht.tsx | 347 ++++ .../src/app/components/ui/accordion.tsx | 0 .../src/app/components/ui/alert-dialog.tsx | 0 .../src/app/components/ui/alert.tsx | 0 .../src/app/components/ui/aspect-ratio.tsx | 0 .../src/app/components/ui/avatar.tsx | 0 .../src/app/components/ui/badge.tsx | 0 .../src/app/components/ui/breadcrumb.tsx | 0 .../src/app/components/ui/button.tsx | 0 .../src/app/components/ui/calendar.tsx | 0 .../src/app/components/ui/card.tsx | 0 .../src/app/components/ui/carousel.tsx | 0 .../src/app/components/ui/chart.tsx | 0 .../src/app/components/ui/checkbox.tsx | 0 .../src/app/components/ui/collapsible.tsx | 0 .../src/app/components/ui/command.tsx | 0 .../src/app/components/ui/context-menu.tsx | 0 .../src/app/components/ui/dialog.tsx | 0 .../src/app/components/ui/drawer.tsx | 0 .../src/app/components/ui/dropdown-menu.tsx | 0 .../src/app/components/ui/form.tsx | 0 .../src/app/components/ui/hover-card.tsx | 0 .../src/app/components/ui/input-otp.tsx | 0 .../src/app/components/ui/input.tsx | 0 .../src/app/components/ui/label.tsx | 0 .../src/app/components/ui/menubar.tsx | 0 .../src/app/components/ui/navigation-menu.tsx | 0 .../src/app/components/ui/pagination.tsx | 0 .../src/app/components/ui/popover.tsx | 0 .../src/app/components/ui/progress.tsx | 0 .../src/app/components/ui/radio-group.tsx | 0 .../src/app/components/ui/resizable.tsx | 0 .../src/app/components/ui/scroll-area.tsx | 0 .../src/app/components/ui/select.tsx | 0 .../src/app/components/ui/separator.tsx | 0 .../src/app/components/ui/sheet.tsx | 0 .../src/app/components/ui/sidebar.tsx | 0 .../src/app/components/ui/skeleton.tsx | 0 .../src/app/components/ui/slider.tsx | 0 .../src/app/components/ui/sonner.tsx | 0 .../src/app/components/ui/switch.tsx | 0 .../src/app/components/ui/table.tsx | 0 .../src/app/components/ui/tabs.tsx | 0 .../src/app/components/ui/textarea.tsx | 0 .../src/app/components/ui/toggle-group.tsx | 0 .../src/app/components/ui/toggle.tsx | 0 .../src/app/components/ui/tooltip.tsx | 0 .../src/app/components/ui/use-mobile.ts | 0 .../src/app/components/ui/utils.ts | 0 .../FIGMA/Vision_02/src/app/routes.tsx | 28 + .../FIGMA/Vision_02/src/app/theme.tsx | 56 + .../FIGMA/Vision_02/src/imports/26128.md | 71 + .../FIGMA/Vision_02/src/imports/26129.md | 70 + .../Detail-Bewerbe-Springen-Dressur.md | 128 ++ ...ruktur_Turnier-Ausschreibung-gemäß-OETO.md | 77 + .../meldestelle-desktop-screens.md | 0 .../pasted_text/nennungs-maske-design.md | 0 .../{ => Vision_02}/src/styles/fonts.css | 0 .../{ => Vision_02}/src/styles/index.css | 0 .../{ => Vision_02}/src/styles/tailwind.css | 0 .../{ => Vision_02}/src/styles/theme.css | 0 .../FIGMA/{ => Vision_02}/vite.config.ts | 0 .../FIGMA/Vison_01/ATTRIBUTIONS.md | 5 + .../FIGMA/{ => Vison_01}/README.md | 0 .../FIGMA/{ => Vison_01}/guidelines.zip | Bin .../FIGMA/Vison_01/guidelines/Guidelines.md | 61 + .../FIGMA/{ => Vison_01}/package.json | 0 .../FIGMA/Vison_01/postcss.config.mjs | 15 + docs/06_Frontend/FIGMA/{ => Vison_01}/src.zip | Bin .../FIGMA/{ => Vison_01}/src/app/App.tsx | 0 .../src/app/components/Bewerbsliste.tsx | 139 ++ .../src/app/components/NennungenTabelle.tsx | 129 ++ .../src/app/components/NennungsMaske.tsx | 115 ++ .../src/app/components/PferdReiterEingabe.tsx | 0 .../src/app/components/VerkaufBuchungen.tsx | 213 ++ .../components/figma/ImageWithFallback.tsx | 27 + .../src/app/components/ui/accordion.tsx | 67 + .../src/app/components/ui/alert-dialog.tsx | 157 ++ .../Vison_01/src/app/components/ui/alert.tsx | 66 + .../src/app/components/ui/aspect-ratio.tsx | 11 + .../Vison_01/src/app/components/ui/avatar.tsx | 53 + .../Vison_01/src/app/components/ui/badge.tsx | 46 + .../src/app/components/ui/breadcrumb.tsx | 109 + .../Vison_01/src/app/components/ui/button.tsx | 58 + .../src/app/components/ui/calendar.tsx | 75 + .../Vison_01/src/app/components/ui/card.tsx | 92 + .../src/app/components/ui/carousel.tsx | 241 +++ .../Vison_01/src/app/components/ui/chart.tsx | 353 ++++ .../src/app/components/ui/checkbox.tsx | 32 + .../src/app/components/ui/collapsible.tsx | 33 + .../src/app/components/ui/command.tsx | 178 ++ .../src/app/components/ui/context-menu.tsx | 252 +++ .../Vison_01/src/app/components/ui/dialog.tsx | 136 ++ .../Vison_01/src/app/components/ui/drawer.tsx | 133 ++ .../src/app/components/ui/dropdown-menu.tsx | 257 +++ .../Vison_01/src/app/components/ui/form.tsx | 168 ++ .../src/app/components/ui/hover-card.tsx | 44 + .../src/app/components/ui/input-otp.tsx | 77 + .../Vison_01/src/app/components/ui/input.tsx | 21 + .../Vison_01/src/app/components/ui/label.tsx | 24 + .../src/app/components/ui/menubar.tsx | 276 +++ .../src/app/components/ui/navigation-menu.tsx | 168 ++ .../src/app/components/ui/pagination.tsx | 127 ++ .../src/app/components/ui/popover.tsx | 48 + .../src/app/components/ui/progress.tsx | 31 + .../src/app/components/ui/radio-group.tsx | 45 + .../src/app/components/ui/resizable.tsx | 56 + .../src/app/components/ui/scroll-area.tsx | 58 + .../Vison_01/src/app/components/ui/select.tsx | 189 ++ .../src/app/components/ui/separator.tsx | 28 + .../Vison_01/src/app/components/ui/sheet.tsx | 140 ++ .../src/app/components/ui/sidebar.tsx | 726 +++++++ .../src/app/components/ui/skeleton.tsx | 13 + .../Vison_01/src/app/components/ui/slider.tsx | 63 + .../Vison_01/src/app/components/ui/sonner.tsx | 25 + .../Vison_01/src/app/components/ui/switch.tsx | 31 + .../Vison_01/src/app/components/ui/table.tsx | 116 ++ .../Vison_01/src/app/components/ui/tabs.tsx | 66 + .../src/app/components/ui/textarea.tsx | 18 + .../src/app/components/ui/toggle-group.tsx | 73 + .../Vison_01/src/app/components/ui/toggle.tsx | 47 + .../src/app/components/ui/tooltip.tsx | 62 + .../src/app/components/ui/use-mobile.ts | 21 + .../Vison_01/src/app/components/ui/utils.ts | 6 + .../meldestelle-desktop-screens.md | 163 ++ .../pasted_text/nennungs-maske-design.md | 170 ++ .../FIGMA/Vison_01/src/styles/fonts.css | 0 .../FIGMA/Vison_01/src/styles/index.css | 3 + .../FIGMA/Vison_01/src/styles/tailwind.css | 5 + .../FIGMA/Vison_01/src/styles/theme.css | 180 ++ .../06_Frontend/FIGMA/Vison_01/vite.config.ts | 22 + 155 files changed, 13832 insertions(+) rename docs/06_Frontend/FIGMA/{ => Vision_02}/ATTRIBUTIONS.md (100%) create mode 100644 docs/06_Frontend/FIGMA/Vision_02/README.md create mode 100644 docs/06_Frontend/FIGMA/Vision_02/guidelines.zip rename docs/06_Frontend/FIGMA/{ => Vision_02}/guidelines/Guidelines.md (100%) create mode 100644 docs/06_Frontend/FIGMA/Vision_02/package.json rename docs/06_Frontend/FIGMA/{ => Vision_02}/postcss.config.mjs (100%) create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src.zip create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/App.tsx rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/Bewerbsliste.tsx (100%) create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/components/Dashboard.tsx create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/components/Login.tsx rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/NennungenTabelle.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/NennungsMaske.tsx (100%) create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/components/PferdReiterEingabe.tsx create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/components/TurnierAnsicht.tsx create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/components/TurnierErstellen.tsx rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/VerkaufBuchungen.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/figma/ImageWithFallback.tsx (100%) create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/BewerbeTab.tsx create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/FunktionaereTab.tsx create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/OrganisationTab.tsx create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/PreislisteTab.tsx create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/StammdatenTab.tsx create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/TransferTab.tsx create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/VeranstaltungUebersicht.tsx rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/accordion.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/alert-dialog.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/alert.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/aspect-ratio.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/avatar.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/badge.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/breadcrumb.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/button.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/calendar.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/card.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/carousel.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/chart.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/checkbox.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/collapsible.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/command.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/context-menu.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/dialog.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/drawer.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/dropdown-menu.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/form.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/hover-card.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/input-otp.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/input.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/label.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/menubar.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/navigation-menu.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/pagination.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/popover.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/progress.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/radio-group.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/resizable.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/scroll-area.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/select.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/separator.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/sheet.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/sidebar.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/skeleton.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/slider.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/sonner.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/switch.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/table.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/tabs.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/textarea.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/toggle-group.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/toggle.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/tooltip.tsx (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/use-mobile.ts (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/app/components/ui/utils.ts (100%) create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/routes.tsx create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/app/theme.tsx create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/imports/26128.md create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/imports/26129.md create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/imports/Detail-Bewerbe-Springen-Dressur.md create mode 100644 docs/06_Frontend/FIGMA/Vision_02/src/imports/Struktur_Turnier-Ausschreibung-gemäß-OETO.md rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/imports/pasted_text/meldestelle-desktop-screens.md (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/imports/pasted_text/nennungs-maske-design.md (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/styles/fonts.css (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/styles/index.css (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/styles/tailwind.css (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/src/styles/theme.css (100%) rename docs/06_Frontend/FIGMA/{ => Vision_02}/vite.config.ts (100%) create mode 100644 docs/06_Frontend/FIGMA/Vison_01/ATTRIBUTIONS.md rename docs/06_Frontend/FIGMA/{ => Vison_01}/README.md (100%) rename docs/06_Frontend/FIGMA/{ => Vison_01}/guidelines.zip (100%) create mode 100644 docs/06_Frontend/FIGMA/Vison_01/guidelines/Guidelines.md rename docs/06_Frontend/FIGMA/{ => Vison_01}/package.json (100%) create mode 100644 docs/06_Frontend/FIGMA/Vison_01/postcss.config.mjs rename docs/06_Frontend/FIGMA/{ => Vison_01}/src.zip (100%) rename docs/06_Frontend/FIGMA/{ => Vison_01}/src/app/App.tsx (100%) create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/Bewerbsliste.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/NennungenTabelle.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/NennungsMaske.tsx rename docs/06_Frontend/FIGMA/{ => Vison_01}/src/app/components/PferdReiterEingabe.tsx (100%) create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/VerkaufBuchungen.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/figma/ImageWithFallback.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/accordion.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/alert-dialog.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/alert.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/aspect-ratio.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/avatar.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/badge.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/breadcrumb.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/button.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/calendar.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/card.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/carousel.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/chart.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/checkbox.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/collapsible.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/command.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/context-menu.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/dialog.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/drawer.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/dropdown-menu.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/form.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/hover-card.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/input-otp.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/input.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/label.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/menubar.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/navigation-menu.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/pagination.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/popover.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/progress.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/radio-group.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/resizable.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/scroll-area.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/select.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/separator.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/sheet.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/sidebar.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/skeleton.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/slider.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/sonner.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/switch.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/table.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/tabs.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/textarea.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/toggle-group.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/toggle.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/tooltip.tsx create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/use-mobile.ts create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/utils.ts create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/imports/pasted_text/meldestelle-desktop-screens.md create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/imports/pasted_text/nennungs-maske-design.md create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/styles/fonts.css create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/styles/index.css create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/styles/tailwind.css create mode 100644 docs/06_Frontend/FIGMA/Vison_01/src/styles/theme.css create mode 100644 docs/06_Frontend/FIGMA/Vison_01/vite.config.ts diff --git a/docs/06_Frontend/FIGMA/ATTRIBUTIONS.md b/docs/06_Frontend/FIGMA/Vision_02/ATTRIBUTIONS.md similarity index 100% rename from docs/06_Frontend/FIGMA/ATTRIBUTIONS.md rename to docs/06_Frontend/FIGMA/Vision_02/ATTRIBUTIONS.md diff --git a/docs/06_Frontend/FIGMA/Vision_02/README.md b/docs/06_Frontend/FIGMA/Vision_02/README.md new file mode 100644 index 00000000..2ea935d7 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/README.md @@ -0,0 +1,1024 @@ +# Turnierverwaltungs-Anwendung - Frontend Prototyp + +## Projektübersicht + +Dies ist ein professioneller Prototyp einer Turnierverwaltungs-Anwendung für den österreichischen Pferdesportverband ( +ÖPS). Die Anwendung ist als **Desktop-First-Anwendung** konzipiert und bietet eine kompakte, tastaturoptimierte +Benutzeroberfläche zur Verwaltung von Veranstaltungen, Turnieren und Bewerben im Pferdesport. + +### Hauptmerkmale + +- **Desktop-optimierte UI**: Fokus auf kompakte Layouts und effiziente Datenerfassung +- **Hierarchische Datenstruktur**: Veranstaltungen → Turniere → Bewerbe +- **Material Design 3**: Moderne UI mit Primärfarbe Indigo (#3F51B5) +- **Tastaturoptimiert**: Effiziente Navigation und Dateneingabe +- **OETO-Ausschreibungs-Standard**: Tab-Struktur folgt österreichischen Richtlinien + +--- + +## Technologie-Stack + +### Core Technologies + +- **React 18** - UI Framework +- **TypeScript** - Type-safe JavaScript +- **React Router** (Data Mode) - Client-side Routing +- **Material-UI (MUI) v6** - Component Library +- **Vite** - Build Tool & Development Server + +### Styling + +- **Material-UI System** - Sx Props für Styling +- **Tailwind CSS v4** - Utility Classes (sekundär) +- **Material Design 3** - Design Language + +### Package Manager + +- **pnpm** - Fast, disk space efficient package manager + +--- + +## Projektstruktur + +``` +/ +├── src/ +│ ├── app/ +│ │ ├── components/ +│ │ │ ├── veranstaltung/ +│ │ │ │ ├── StammdatenTab.tsx # A-Satz / Stammdaten +│ │ │ │ ├── OrganisationTab.tsx # Funktionäre & Plätze +│ │ │ │ ├── PreislisteTab.tsx # Preisliste +│ │ │ │ └── UebersichtTab.tsx # Transfer/Übersicht +│ │ │ ├── turnier/ +│ │ │ │ └── BewerbeTab.tsx # Bewerbe-Verwaltung (Hauptseite) +│ │ │ ├── AdminDrawer.tsx # Haupt-Navigation +│ │ │ ├── VeranstaltungAnsicht.tsx # Veranstaltungs-View +│ │ │ └── TurnierAnsicht.tsx # Turnier-View +│ │ ├── routes.tsx # React Router Konfiguration +│ │ └── App.tsx # Root Component +│ ├── styles/ +│ │ ├── theme.css # CSS Variables & Theme +│ │ └── fonts.css # Font Imports +│ └── main.tsx # Entry Point +├── package.json +└── README.md +``` + +--- + +## Installation & Setup + +### Voraussetzungen + +- **Node.js** >= 18.x +- **pnpm** >= 8.x (empfohlen) oder npm + +### Installation + +```bash +# Repository klonen +git clone +cd turnierverwaltung + +# Dependencies installieren +pnpm install + +# Development Server starten +pnpm dev + +# Build für Production +pnpm build + +# Preview Production Build +pnpm preview +``` + +### Verfügbare Scripts + +```json +{ + "dev": "vite", // Development Server auf http://localhost:5173 + "build": "vite build", // Production Build + "preview": "vite preview" // Preview Production Build +} +``` + +--- + +## Architektur & Konzepte + +### 1. Routing-System (React Router Data Mode) + +Die Anwendung verwendet React Router's Data Mode Pattern mit einer klar definierten Route-Hierarchie: + +```typescript +// src/app/routes.tsx +const router = createBrowserRouter([ + { + path: "/", + Component: Root, + children: [ + // Neue Veranstaltung + { + path: "veranstaltung/neu", + Component: VeranstaltungAnsicht + }, + + // Bestehende Veranstaltung + { + path: "veranstaltung/:id", + Component: VeranstaltungAnsicht + }, + + // Neues Turnier in Veranstaltung + { + path: "veranstaltung/:veranstaltungId/turnier/neu", + Component: TurnierAnsicht + }, + + // Bestehendes Turnier + { + path: "veranstaltung/:veranstaltungId/turnier/:nr", + Component: TurnierAnsicht + }, + + // 404 Fallback + { + path: "*", + Component: NotFound + } + ] + } +]); +``` + +**Wichtig**: Verwenden Sie immer das `react-router` Package (nicht `react-router-dom`), da die Anwendung in einer +speziellen Umgebung läuft. + +--- + +### 2. Navigation & Benutzerfluss + +#### Hauptnavigation: AdminDrawer + +Die Anwendung verwendet eine **Drawer-Navigation** (links) mit folgenden Bereichen: + +``` +Admin - Verwaltung +├── Veranstaltungen +│ ├── Neue Veranstaltung → /veranstaltung/neu +│ └── [Liste Veranstaltungen] → /veranstaltung/:id +│ └── Turniere +│ ├── Neues Turnier → /veranstaltung/:id/turnier/neu +│ └── [Turnier-Liste] → /veranstaltung/:id/turnier/:nr +└── ... +``` + +#### Login-System + +- **Demo Credentials**: + - Username: `admin` + - Passwort: `Admin#1234` +- Login-State wird im `localStorage` gespeichert +- Keine Backend-Integration im Prototyp + +--- + +### 3. Tab-Struktur (OETO-Standard) + +#### Veranstaltungs-Tabs (Neue Veranstaltung) + +Bei einer **neuen Veranstaltung** sind alle 5 Tabs sichtbar: + +1. **Veranstaltung - Übersicht** (ehemals "Transfer") +2. **Stammdaten** (A-Satz) ← Standardtab beim Erstellen +3. **Organisation** (Funktionäre + Plätze) +4. **Bewerbe** (wird versteckt, da turnierspezifisch) +5. **Preisliste** + +#### Veranstaltungs-Tabs (Bestehende Veranstaltung) + +Bei einer **bestehenden Veranstaltung** wird nur der Übersicht-Tab angezeigt: + +1. **Veranstaltung - Übersicht** + +**Grund**: Turnierspezifische Daten (Stammdaten, Organisation, Bewerbe, Preisliste) werden nur auf Turnier-Ebene +bearbeitet. + +#### Turnier-Tabs + +Wenn ein Turnier geöffnet wird, sind alle 5 Tabs sichtbar: + +1. **Veranstaltung - Übersicht** (Read-only, zeigt Veranstaltungs-Info) +2. **Stammdaten** (A-Satz) +3. **Organisation** (Funktionäre + Plätze) +4. **Bewerbe** ⭐ **Wichtigste Seite der Anwendung** +5. **Preisliste** + +--- + +### 4. Bewerbe-Tab - Die Hauptseite + +Der **Bewerbe-Tab** ist die zentrale Konfigurationsseite des gesamten Systems. Er ist in 3 Bereiche aufgeteilt: + +``` +┌─────────────┬───────────────────────┬───────────────────────┐ +│ Aktionen │ Bewerbs-Übersicht │ Bewerb-Konfiguration │ +│ (150px) │ (50%) │ (50%) │ +└─────────────┴───────────────────────┴───────────────────────┘ +``` + +#### Links: Aktionen (150px Sidebar) + +Buttons für Bewerbs-Management: + +- **Änderungen Speichern** / **Änderungen Rückgängig** +- **Bewerb Einfügen** / **Bewerb Löschen** / **Bewerb Teilen** +- **Bewerb nach oben/unten verschieben** +- **Startliste Bearbeiten** / **Startliste Drucken** +- **Ergebnisliste Bearbeiten** / **Ergebnisliste Drucken** + +#### Mitte: Bewerbs-Übersicht (50%) + +**Toolbar**: + +- Button: Aktualisieren +- Button: X Bewerbe (zeigt Anzahl) +- Button: Filtern + +**Tabelle** mit folgenden Spalten: + +- **Tag** (Datum) +- **Platz** (Platz-Nummer) +- **Bewerb** (Bewerb-Nummer) +- **Beginn** (Uhrzeit) +- **Ende** (Uhrzeit) +- **Bewerbname** (mehrzeilig möglich) +- **ZNS** (Zusätzliche Nennung Startnummer) +- **Nennungen** (Anzahl Anmeldungen) + +**Features**: + +- Klickbare Zeilen zur Auswahl +- Hervorhebung: Bewerbe 5 & 6 haben gelben Hintergrund (`warning.50`) +- Selected State: Blau/Gelb-Orange je nach Bewerb + +#### Rechts: Bewerb-Konfiguration (50%) + +**4 Tabs** zur detaillierten Bewerbs-Konfiguration: + +##### Tab 1: Bewerb (Grunddaten) + +- Nummer +- Abteilung +- Typ (z.B. "Dressur") +- Name (z.B. "Dressurreiterprüfung") +- Bezeichnung (z.B. "Dressurreiterprüfung Reiterpass") +- Kategorie (Dropdown) +- Klasse (Dropdown) +- Lizenz (Dropdown) +- Maximal (Pferde je Reiter) +- Pferdealter (Dropdown) +- Zeile 1, 2, 3 (Zusatzinformationen wie "Pony Einsteiger Cup OÖ") +- Logo Bewerb (Dateipfad mit "..."-Button) + +##### Tab 2: Bewertung + +- Prüfung (z.B. "Dressurreiterprüfung") +- Richtverfahren (z.B. "A") +- Para-Grade +- Richteranzahl +- Aufgabe (z.B. "Aufgabe R") +- Aufgabennummer +- Maximalpunkte (Punkte je Richter) + +**Richter-Liste**: + +- Position (z.B. "C") +- Name (z.B. "Schuster Alexandra") +- Aktiv (Checkbox) + +##### Tab 3: Geldpreise + +**Section: Geldpreis** + +- Checkbox: Geldpreis +- Startgeld (z.B. "15,00") +- Auszahlung (Dropdown: fortführend, 1/3, 1/4, 1/5) + +**Section: Geldpreis für Kadererreiter** + +- Checkbox: Geldpreis für Kadererreiter +- Startgeld für Kadererreiter (z.B. "15,00") + +**Geldpreisvorlage wählen** (Dropdown) + +**Tabelle: Geldpreise** + +- Spalten: Nummer, Geldpreis +- Zeigt Anzahl der Geldpreise + +##### Tab 4: Ort/Zeit + +- Tag (Dropdown: Datum) +- Beginnzeit (Dropdown: "fix um", "nicht vor", "ca.") +- Zeit (Textfeld mit Format hh:mm) +- Reitdauer (Textfeld mit Format mm:ss) +- Umbau (Textfeld in Minuten) +- Besichtigung (Textfeld in Minuten) +- Stechen (Textfeld in Minuten) +- Platz (Dropdown: "Vorderer Turnierplatz", "Hauptplatz", etc.) + +--- + +## Datenstrukturen + +### Bewerb Interface + +```typescript +interface Bewerb { + id: number; + tag: string; // Tabellen-Datum + platz: number; // Platz-Nummer + bewerb: number; // Bewerb-Nummer + beginn: string; // Beginn-Zeit + ende: string; // End-Zeit + bewerbname: string; // Mehrzeiliger Name + zns: number; // ZNS + nennungen: number; // Anzahl Nennungen + + // Tab 1: Bewerb + nummer: string; + abteilung: string; + typ: string; + name: string; + bezeichnung: string; + kategorie: string; + klasse: string; + lizenz: string; + maximal: string; + pferdealter: string; + zeile1: string; + zeile2: string; + zeile3: string; + logoBewerbPfad: string; + + // Tab 2: Bewertung + prufung: string; + richtverfahren: string; + paraGrade: string; + richteranzahl: number; + aufgabe: string; + aufgabennr: string; + maximalPunkte: string; + richter: { + position: string; + name: string; + aktiv: boolean; + }[]; + + // Tab 3: Geldpreise + geldpreisAktiv: boolean; + startgeld: string; + auszahlung: string; + geldpreisKadererreiterAktiv: boolean; + startgeldKadererreiter: string; + geldpreisvorlage: string; + geldpreise: { + nummer: string; + betrag: string; + }[]; + + // Tab 4: Ort/Zeit + tagDatum: string; + beginnzeit: string; + beginnZeit: string; + reitdauer: string; + umbau: string; + besichtigung: string; + stechen: string; + platzName: string; +} +``` + +### Veranstaltung Interface + +```typescript +interface Veranstaltung { + id: string; + name: string; + von: string; // Datum von + bis: string; // Datum bis + ort: string; + status: string; + turniere: Turnier[]; +} +``` + +### Turnier Interface + +```typescript +interface Turnier { + nr: number; + name: string; + datum: string; + status: string; + bewerbe: Bewerb[]; +} +``` + +--- + +## Design-System + +### Farbschema (Material Design 3) + +**Primärfarbe**: Indigo (#3F51B5) + +```css +/* Theme Colors (src/styles/theme.css) */ +--primary-color: #3F51B5; +--primary-light: #757DE8; +--primary-dark: #002984; + +/* Semantic Colors */ +--background-default: #FAFAFA; +--background-paper: #FFFFFF; +--text-primary: rgba(0, 0, 0, 0.87); +--text-secondary: rgba(0, 0, 0, 0.60); +--divider: rgba(0, 0, 0, 0.12); + +/* Status Colors */ +--success-color: #4CAF50; +--warning-color: #FF9800; +--error-color: #F44336; +--info-color: #2196F3; +``` + +### Typografie + +- **Body Text**: 10px - 11px (sehr kompakt für Desktop) +- **Labels**: 10px, 600 Font Weight +- **Section Headers**: 11px - 13px, 600 Font Weight +- **Schriftart**: System Fonts (Roboto via MUI) + +### Spacing & Layout + +- **Kompakte Abstände**: 1-2 (8px - 16px) +- **Form-Felder**: + - Höhe: `small` size + - Padding: `py: 0.5` (4px) + - Font: 10px +- **Sidebar Width**: 150px (Aktionen-Sidebar im Bewerbe-Tab) +- **Drawer Width**: 280px (Haupt-Navigation) + +### Component-Sizing + +```typescript +// Standardgrößen +size="small" // Buttons, TextFields, Selects +sx={{ fontSize: '10px' }} // Text +sx={{ py: 0.5 }} // Input Padding +sx={{ gap: 1 }} // 8px Abstand +sx={{ gap: 1.5 }} // 12px Abstand +``` + +--- + +## MUI Theme Konfiguration + +Die Anwendung verwendet MUI's Default Theme mit angepasster Primärfarbe: + +```typescript +// src/main.tsx +import { createTheme, ThemeProvider } from '@mui/material/styles'; + +const theme = createTheme({ + palette: { + primary: { + main: '#3F51B5', // Indigo + }, + }, + components: { + MuiButton: { + styleOverrides: { + root: { + textTransform: 'none', // Keine Großbuchstaben + }, + }, + }, + }, +}); +``` + +--- + +## State Management + +### Aktuelle Implementierung (Prototyp) + +Der Prototyp verwendet **React Local State** mit `useState`: + +```typescript +// Beispiel: BewerbeTab.tsx +const [bewerbe, setBewerbe] = useState(mockBewerbe); +const [selectedBewerbId, setSelectedBewerbId] = useState(1); +const [detailTab, setDetailTab] = useState(0); +``` + +### Empfehlung für Production + +Für die Production-Version empfehlen wir: + +1. **React Context API** für globalen State (Login, aktuelle Veranstaltung/Turnier) +2. **Zustand** oder **Redux Toolkit** für komplexes State Management +3. **React Query** für Server-State und Caching +4. **localStorage/sessionStorage** für Persistenz + +Beispiel mit React Context: + +```typescript +// context/VeranstaltungContext.tsx +const VeranstaltungContext = createContext(null); + +export function VeranstaltungProvider({ children }: { children: ReactNode }) { + const [activeVeranstaltung, setActiveVeranstaltung] = useState(null); + const [activeTurnier, setActiveTurnier] = useState(null); + + return ( + + {children} + + ); +} +``` + +--- + +## Backend-Integration (TODO) + +### API Endpunkte (geplant) + +```typescript +// Veranstaltungen +GET /api/veranstaltungen +GET /api/veranstaltungen/:id +POST /api/veranstaltungen +PUT /api/veranstaltungen/:id +DELETE /api/veranstaltungen/:id + +// Turniere +GET /api/veranstaltungen/:veranstaltungId/turniere +GET /api/veranstaltungen/:veranstaltungId/turniere/:nr +POST /api/veranstaltungen/:veranstaltungId/turniere +PUT /api/veranstaltungen/:veranstaltungId/turniere/:nr +DELETE /api/veranstaltungen/:veranstaltungId/turniere/:nr + +// Bewerbe +GET /api/turniere/:turnierId/bewerbe +GET /api/turniere/:turnierId/bewerbe/:id +POST /api/turniere/:turnierId/bewerbe +PUT /api/turniere/:turnierId/bewerbe/:id +DELETE /api/turniere/:turnierId/bewerbe/:id + +// ÖPS Datasourcing +POST /api/ops/import/veranstaltung/:id +POST /api/ops/import/turnier/:id +``` + +### Authentifizierung + +```typescript +POST /api/auth/login +POST /api/auth/logout +GET /api/auth/me +POST /api/auth/refresh +``` + +--- + +## Entwicklungsrichtlinien + +### Code Style + +1. **TypeScript Strict Mode**: Aktiviert +2. **Naming Conventions**: + - Components: PascalCase (z.B. `BewerbeTab.tsx`) + - Functions: camelCase (z.B. `handleBewerbAendern`) + - Interfaces: PascalCase (z.B. `Bewerb`) + - CSS Classes: kebab-case (falls verwendet) + +3. **Component Structure**: + +```typescript +// 1. Imports +import React from 'react'; +import { Box, Button } from '@mui/material'; + +// 2. Interfaces/Types +interface Props { ... } + +// 3. Component +export function ComponentName({ prop1, prop2 }: Props) { + // 3.1 State + const [state, setState] = useState(); + + // 3.2 Handlers + const handleAction = () => { ... }; + + // 3.3 Effects + useEffect(() => { ... }, []); + + // 3.4 Render + return ( ... ); +} +``` + +### MUI Best Practices + +1. **Sx Props bevorzugen** statt styled components: + +```typescript +// ✅ Gut + + +// ❌ Vermeiden (im Prototyp) + +``` + +2. **Theme-basierte Werte verwenden**: + +```typescript +// ✅ Gut - Theme Colors +sx={{ color: 'primary.main', bgcolor: 'grey.50' }} + +// ❌ Vermeiden - Hardcoded +sx={{ color: '#3F51B5', bgcolor: '#FAFAFA' }} +``` + +3. **Responsive Werte** (für spätere mobile Version): + +```typescript +sx={{ + width: { xs: '100%', md: 300 }, + display: { xs: 'none', md: 'block' } +}} +``` + +### Performance-Optimierung + +1. **React.memo** für große Listen: + +```typescript +export const BewerbRow = React.memo(({ bewerb }: Props) => { ... }); +``` + +2. **useCallback** für Event Handlers in Listen: + +```typescript +const handleSelect = useCallback((id: number) => { ... }, []); +``` + +3. **Lazy Loading** für Tabs: + +```typescript +const BewerbeTab = lazy(() => import('./turnier/BewerbeTab')); +``` + +--- + +## Testing (geplant) + +### Unit Tests mit Vitest + +```typescript +// BewerbeTab.test.tsx +import { render, screen } from '@testing-library/react'; +import { BewerbeTab } from './BewerbeTab'; + +describe('BewerbeTab', () => { + it('renders 12 bewerbe', () => { + render(); + expect(screen.getByText('12 Bewerbe')).toBeInTheDocument(); + }); +}); +``` + +### E2E Tests mit Playwright + +```typescript +// e2e/bewerbe.spec.ts +test('can create new bewerb', async ({ page }) => { + await page.goto('/veranstaltung/1/turnier/1'); + await page.click('text=Bewerb Einfügen'); + await page.fill('input[name="nummer"]', '13'); + // ... +}); +``` + +--- + +## Browser-Unterstützung + +**Ziel-Browser** (Desktop): + +- Chrome/Edge >= 90 +- Firefox >= 88 +- Safari >= 14 + +**NICHT unterstützt**: + +- Internet Explorer +- Mobile Browser (vorerst) + +--- + +## Bekannte Einschränkungen (Prototyp) + +1. **Keine Backend-Integration**: Alle Daten sind Mock-Daten +2. **Keine Persistenz**: Änderungen gehen bei Page Refresh verloren +3. **Eingeschränkte Validierung**: Minimale Form-Validierung +4. **Keine Fehlerbehandlung**: Fehler-States nicht implementiert +5. **Mock-Login**: Demo-Credentials hart-kodiert +6. **Keine Exports**: Drucken/Exportieren nur als Placeholder-Buttons +7. **Keine Suche/Filter**: Filter-Funktionen nicht implementiert +8. **Keine Undo/Redo**: "Änderungen Rückgängig" nicht funktional + +--- + +## Nächste Schritte / Roadmap + +### Phase 1: Backend-Integration + +- [ ] REST API Implementation +- [ ] Authentifizierungs-System +- [ ] Datenbank-Schema (PostgreSQL empfohlen) +- [ ] ÖPS Datasourcing API-Integration + +### Phase 2: Erweiterte Features + +- [ ] Such- und Filter-Funktionen +- [ ] Sortierung in Tabellen +- [ ] Drag & Drop für Bewerbs-Reihenfolge +- [ ] Bulk-Operations (mehrere Bewerbe gleichzeitig bearbeiten) +- [ ] Undo/Redo-Funktionalität +- [ ] Auto-Save (mit Debouncing) + +### Phase 3: Export & Reporting + +- [ ] PDF-Export (Startlisten, Ergebnislisten) +- [ ] Excel-Export +- [ ] Druckvorlagen +- [ ] Berichts-Templates + +### Phase 4: Erweiterte Tabs + +- [ ] Organisation-Tab: Funktionäre-Verwaltung +- [ ] Organisation-Tab: Plätze-Verwaltung +- [ ] Preisliste-Tab: Vollständige Implementierung +- [ ] Übersicht-Tab: Dashboard mit Statistiken + +### Phase 5: Zusätzliche Module + +- [ ] Meisterschaften/Cups-Verwaltung +- [ ] Nennungs-System +- [ ] Starter-Verwaltung +- [ ] Pferde-Datenbank +- [ ] Reiter-Datenbank + +### Phase 6: Polish & Optimierung + +- [ ] Umfassendes Testing +- [ ] Performance-Optimierung +- [ ] Accessibility (WCAG 2.1 AA) +- [ ] Internationalisierung (i18n) +- [ ] Keyboard Shortcuts +- [ ] Offline-Modus (PWA) + +--- + +## Häufige Entwicklungs-Aufgaben + +### Neue Komponente hinzufügen + +```typescript +// src/app/components/MyComponent.tsx +import { Box, Typography } from '@mui/material'; + +interface MyComponentProps { + title: string; +} + +export function MyComponent({ title }: MyComponentProps) { + return ( + + + {title} + + + ); +} +``` + +### Neue Route hinzufügen + +```typescript +// src/app/routes.tsx +{ + path: "my-new-page", + Component: MyNewPage, +} +``` + +### Neuen Tab in Veranstaltung/Turnier hinzufügen + +```typescript +// In VeranstaltungAnsicht.tsx oder TurnierAnsicht.tsx +const tabs = [ + // ... bestehende Tabs + { label: 'Mein neuer Tab', component: } +]; +``` + +### MUI Component anpassen + +```typescript +// Global Theme Override +const theme = createTheme({ + components: { + MuiButton: { + styleOverrides: { + root: { + textTransform: 'none', + fontSize: '10px', + }, + }, + }, + }, +}); + +// Oder mit Sx Props + + + setSearchTerm(e.target.value)} + sx={{ + flex: 1, + maxWidth: 400, + '& .MuiInputBase-input': {fontSize: '11px'} + }} + InputProps={{ + startAdornment: ( + + + + ), + }} + /> + + + setStatusFilter('alle')} + color={statusFilter === 'alle' ? 'primary' : 'default'} + size="small" + sx={{fontSize: '10px'}} + /> + setStatusFilter('vorbereitung')} + color={statusFilter === 'vorbereitung' ? 'primary' : 'default'} + size="small" + sx={{fontSize: '10px'}} + /> + setStatusFilter('live')} + color={statusFilter === 'live' ? 'primary' : 'default'} + size="small" + sx={{fontSize: '10px'}} + /> + setStatusFilter('abgeschlossen')} + color={statusFilter === 'abgeschlossen' ? 'primary' : 'default'} + size="small" + sx={{fontSize: '10px'}} + /> + + + + {/* Veranstaltungs-Liste - volle Breite */} + + {filteredVeranstaltungen.map((v) => ( + + + {/* Header mit Status */} + + + {v.name} + + + + + {/* Ort und Datum */} + + + + + {v.ort} + + + + + + {v.datum} + + + + + {/* Turniere */} + + + + Turniere ({v.turniere.length}): + + + {v.turniere.map((t) => ( + + + + {t.name} ({t.bewerbeAnzahl} Bewerbe) + + + {t.kategorie === 'B' || t.kategorie === 'A' ? ( + t.znsStatus === 'geladen' ? ( + + ) : ( + + ) + ) : null} + + + ))} + + + + {/* Statistik */} + + + Nennungen: {v.nennungen} + + + Letzte Aktivität: {v.letzteAktivitaet} + + + + {/* Actions */} + + + + + + + + + ))} + + + {filteredVeranstaltungen.length === 0 && ( + + + Keine Veranstaltungen gefunden + + + )} + + + ); +} diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/app/components/Login.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/Login.tsx new file mode 100644 index 00000000..225a5f63 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/Login.tsx @@ -0,0 +1,223 @@ +import {useState, useEffect} from 'react'; +import {useNavigate} from 'react-router'; +import Box from '@mui/material/Box'; +import Paper from '@mui/material/Paper'; +import TextField from '@mui/material/TextField'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import IconButton from '@mui/material/IconButton'; +import InputAdornment from '@mui/material/InputAdornment'; +import Alert from '@mui/material/Alert'; +import CircularProgress from '@mui/material/CircularProgress'; +import Visibility from '@mui/icons-material/Visibility'; +import VisibilityOff from '@mui/icons-material/VisibilityOff'; +import WifiIcon from '@mui/icons-material/Wifi'; +import WifiOffIcon from '@mui/icons-material/WifiOff'; + +export function Login() { + const navigate = useNavigate(); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [showPassword, setShowPassword] = useState(false); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + const [isOnline, setIsOnline] = useState(navigator.onLine); + + // Internet-Verbindung überwachen + useEffect(() => { + const handleOnline = () => setIsOnline(true); + const handleOffline = () => setIsOnline(false); + + window.addEventListener('online', handleOnline); + window.addEventListener('offline', handleOffline); + + return () => { + window.removeEventListener('online', handleOnline); + window.removeEventListener('offline', handleOffline); + }; + }, []); + + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + setLoading(true); + + // Simulated login delay + await new Promise(resolve => setTimeout(resolve, 800)); + + // Hardcoded credentials für Phase 1 + if (username === 'admin' && password === 'Admin#1234') { + // Login erfolgreich + localStorage.setItem('isAuthenticated', 'true'); + localStorage.setItem('userRole', 'admin'); + localStorage.setItem('username', username); + navigate('/admin'); + } else { + setError('Ungültige Anmeldedaten. Bitte überprüfen Sie Benutzername und Passwort.'); + setLoading(false); + } + }; + + return ( + + {/* Internet-Status Anzeige */} + + {isOnline ? ( + <> + + Online + + ) : ( + <> + + Offline + + )} + + + + {/* Logo & Titel */} + + + Turnierverwaltung + + + Österreichischer Pferdesportverband + + + + {/* Fehler-Anzeige */} + {error && ( + + {error} + + )} + + {/* Login-Formular */} +
+ + setUsername(e.target.value)} + fullWidth + autoFocus + disabled={loading} + sx={{'& .MuiInputBase-input': {fontSize: '12px'}}} + /> + + setPassword(e.target.value)} + fullWidth + disabled={loading} + sx={{'& .MuiInputBase-input': {fontSize: '12px'}}} + InputProps={{ + endAdornment: ( + + setShowPassword(!showPassword)} + edge="end" + size="small" + > + {showPassword ? : } + + + ), + }} + /> + + + +
+ + {/* Hinweis */} + + + Demo-Zugang (Phase 1): + + + Benutzer: admin
+ Passwort: Admin#1234 +
+
+
+
+ ); +} diff --git a/docs/06_Frontend/FIGMA/src/app/components/NennungenTabelle.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/NennungenTabelle.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/NennungenTabelle.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/NennungenTabelle.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/NennungsMaske.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/NennungsMaske.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/NennungsMaske.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/NennungsMaske.tsx diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/app/components/PferdReiterEingabe.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/PferdReiterEingabe.tsx new file mode 100644 index 00000000..42488218 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/PferdReiterEingabe.tsx @@ -0,0 +1,558 @@ +import {useState, useEffect, useRef} from 'react'; +import Box from '@mui/material/Box'; +import TextField from '@mui/material/TextField'; +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Typography from '@mui/material/Typography'; +import List from '@mui/material/List'; +import ListItem from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import Chip from '@mui/material/Chip'; +import Badge from '@mui/material/Badge'; + +// Mock-Daten für Pferde +const mockPferde = [ + { + id: 1, + kopfnr: 'A123', + name: "Obora's Donna", + rasse: 'Hannoveraner', + farbe: 'Brauner', + besitzer: 'Franz Huber', + stall: 'Box 12' + }, + { + id: 2, + kopfnr: 'H597', + name: 'Weltmeyer', + rasse: 'Trakehner', + farbe: 'Schimmel', + besitzer: 'Maria Gruber', + stall: 'Box 8' + }, + { + id: 3, + kopfnr: '9939', + name: 'Rubinstein', + rasse: 'Westfale', + farbe: 'Fuchs', + besitzer: 'Johann Maier', + stall: 'Box 15' + }, + { + id: 4, + kopfnr: 'D456', + name: "Obora's Danilo", + rasse: 'Oldenburger', + farbe: 'Rappe', + besitzer: 'Anna Schmidt', + stall: 'Box 3' + }, + { + id: 5, + kopfnr: '4568', + name: 'Domino', + rasse: 'Holsteiner', + farbe: 'Brauner', + besitzer: 'Thomas Bauer', + stall: 'Box 5' + }, + { + id: 6, + kopfnr: 'B789', + name: "Obora's Dream", + rasse: 'Hannoveraner', + farbe: 'Fuchs', + besitzer: 'Franz Huber', + stall: 'Box 14' + }, +]; + +// Mock-Daten für Reiter +const mockReiter = [ + { + id: 1, + kopfnr: '201', + vorname: 'Anna', + nachname: 'Schneider', + verein: 'RV Wien', + lizenz: 'LNR-2024-4587', + lizenzGueltig: true, + kontoSaldo: 0, + geburtsjahr: 1995 + }, + { + id: 2, + kopfnr: '202', + vorname: 'Thomas', + nachname: 'Bauer', + verein: 'RC Graz', + lizenz: 'LNR-2023-1234', + lizenzGueltig: false, + kontoSaldo: -125.50, + geburtsjahr: 1998 + }, + { + id: 3, + kopfnr: '203', + vorname: 'Sophie', + nachname: 'Wagner', + verein: 'RFV Salzburg', + lizenz: 'LNR-2024-9876', + lizenzGueltig: true, + kontoSaldo: 50.00, + geburtsjahr: 1992 + }, + { + id: 4, + kopfnr: '204', + vorname: 'Michael', + nachname: 'Müller', + verein: 'RC Innsbruck', + lizenz: 'LNR-2024-5555', + lizenzGueltig: true, + kontoSaldo: 0, + geburtsjahr: 2001 + }, + { + id: 5, + kopfnr: '205', + vorname: 'Franz', + nachname: 'Huber', + verein: 'RV Linz', + lizenz: 'LNR-2024-7777', + lizenzGueltig: true, + kontoSaldo: 0, + geburtsjahr: 2002 + }, + { + id: 6, + kopfnr: '206', + vorname: 'Franz', + nachname: 'Huber', + verein: 'RC Wien', + lizenz: 'LNR-2024-8888', + lizenzGueltig: true, + kontoSaldo: 0, + geburtsjahr: 1998 + }, +]; + +// Mock-Daten für bereits getätigte Nennungen (IMS = Im System) +const turnieNennungen = [ + {reiterId: 2, pferdId: 5, bewerbNr: 3}, // Thomas Bauer mit Domino in Bewerb 3 + {reiterId: 1, pferdId: 1, bewerbNr: 2}, // Anna Schneider mit Obora's Donna in Bewerb 2 + {reiterId: 1, pferdId: 2, bewerbNr: 5}, // Anna Schneider mit Weltmeyer in Bewerb 5 +]; + +interface Props { + selectedPferd: any; + setSelectedPferd: (pferd: any) => void; + selectedReiter: any; + setSelectedReiter: (reiter: any) => void; +} + +export function PferdReiterEingabe({selectedPferd, setSelectedPferd, selectedReiter, setSelectedReiter}: Props) { + const [pferdSuche, setPferdSuche] = useState(''); + const [reiterSuche, setReiterSuche] = useState(''); + const [pferdErgebnisse, setPferdErgebnisse] = useState([]); + const [reiterErgebnisse, setReiterErgebnisse] = useState([]); + const [selectedPferdIndex, setSelectedPferdIndex] = useState(0); + const [selectedReiterIndex, setSelectedReiterIndex] = useState(0); + + const pferdInputRef = useRef(null); + const reiterInputRef = useRef(null); + + // Autofokus auf Pferd-Suchfeld beim Laden + useEffect(() => { + pferdInputRef.current?.focus(); + }, []); + + // Pferd-Suche + useEffect(() => { + if (pferdSuche.length > 0) { + // Normale Suche nach Eingabe + const results = mockPferde.filter(p => + p.kopfnr.toLowerCase().includes(pferdSuche.toLowerCase()) || + p.name.toLowerCase().includes(pferdSuche.toLowerCase()) + ); + setPferdErgebnisse(results); + setSelectedPferdIndex(0); + } else if (selectedReiter && !pferdSuche) { + // Cross-Reference: Zeige Pferde des ausgewählten Reiters + const reiterPferde = turnieNennungen + .filter(n => n.reiterId === selectedReiter.id) + .map(n => mockPferde.find(p => p.id === n.pferdId)) + .filter(Boolean); + setPferdErgebnisse(reiterPferde); + } else { + setPferdErgebnisse([]); + } + }, [pferdSuche, selectedReiter]); + + // Reiter-Suche + useEffect(() => { + if (reiterSuche.length > 0) { + // Normale Suche nach Eingabe + const results = mockReiter.filter(r => + r.vorname.toLowerCase().includes(reiterSuche.toLowerCase()) || + r.nachname.toLowerCase().includes(reiterSuche.toLowerCase()) || + `${r.vorname} ${r.nachname}`.toLowerCase().includes(reiterSuche.toLowerCase()) + ); + setReiterErgebnisse(results); + setSelectedReiterIndex(0); + } else if (selectedPferd && !reiterSuche) { + // Cross-Reference: Zeige Reiter des ausgewählten Pferdes + const pferdReiter = turnieNennungen + .filter(n => n.pferdId === selectedPferd.id) + .map(n => mockReiter.find(r => r.id === n.reiterId)) + .filter(Boolean); + setReiterErgebnisse(pferdReiter); + } else { + setReiterErgebnisse([]); + } + }, [reiterSuche, selectedPferd]); + + // Hilfsfunktion: Prüft ob Pferd im System ist (IMS) + const isPferdIMS = (pferdId: number) => { + return turnieNennungen.some(n => n.pferdId === pferdId); + }; + + // Hilfsfunktion: Prüft ob Reiter im System ist (IMS) + const isReiterIMS = (reiterId: number) => { + return turnieNennungen.some(n => n.reiterId === reiterId); + }; + + // Pferd auswählen + const handlePferdAuswahl = (pferd: any) => { + setSelectedPferd(pferd); + + // Cross-Reference: Zeige Reiter dieses Pferdes + const pferdReiter = turnieNennungen + .filter(n => n.pferdId === pferd.id) + .map(n => mockReiter.find(r => r.id === n.reiterId)) + .filter(Boolean); + + if (pferdReiter.length > 0) { + setReiterErgebnisse(pferdReiter); + } + + reiterInputRef.current?.focus(); + }; + + // Reiter auswählen + const handleReiterAuswahl = (reiter: any) => { + setSelectedReiter(reiter); + + // Cross-Reference: Zeige Pferde dieses Reiters + const reiterPferde = turnieNennungen + .filter(n => n.reiterId === reiter.id) + .map(n => mockPferde.find(p => p.id === n.pferdId)) + .filter(Boolean); + + if (reiterPferde.length > 0) { + setPferdErgebnisse(reiterPferde); + } + }; + + // Keyboard Navigation für Pferd + const handlePferdKeyDown = (e: React.KeyboardEvent) => { + if (pferdErgebnisse.length === 0) return; + + if (e.key === 'ArrowDown') { + e.preventDefault(); + setSelectedPferdIndex(prev => Math.min(prev + 1, pferdErgebnisse.length - 1)); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + setSelectedPferdIndex(prev => Math.max(prev - 1, 0)); + } else if (e.key === 'Enter') { + e.preventDefault(); + if (pferdErgebnisse[selectedPferdIndex]) { + handlePferdAuswahl(pferdErgebnisse[selectedPferdIndex]); + } + } + }; + + // Keyboard Navigation für Reiter + const handleReiterKeyDown = (e: React.KeyboardEvent) => { + if (reiterErgebnisse.length === 0) return; + + if (e.key === 'ArrowDown') { + e.preventDefault(); + setSelectedReiterIndex(prev => Math.min(prev + 1, reiterErgebnisse.length - 1)); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + setSelectedReiterIndex(prev => Math.max(prev - 1, 0)); + } else if (e.key === 'Enter') { + e.preventDefault(); + if (reiterErgebnisse[selectedReiterIndex]) { + handleReiterAuswahl(reiterErgebnisse[selectedReiterIndex]); + } + } + }; + + const handlePferdLeeren = () => { + setPferdSuche(''); + setSelectedPferd(null); + setPferdErgebnisse([]); + pferdInputRef.current?.focus(); + }; + + const handleReiterLeeren = () => { + setReiterSuche(''); + setSelectedReiter(null); + setReiterErgebnisse([]); + reiterInputRef.current?.focus(); + }; + + return ( + + {/* Linke Hälfte: Pferd */} + + {/* Eingabefeld */} + + + Pferd: + + setPferdSuche(e.target.value)} + onKeyDown={handlePferdKeyDown} + sx={{ + flex: 1, + '& .MuiInputBase-input': {fontSize: '11px', py: 0.75}, + }} + /> + + + + + {/* Suchergebnisse - bleiben immer sichtbar */} + + + {pferdErgebnisse.length > 0 ? ( + (pferdSuche ? pferdErgebnisse : pferdErgebnisse.slice(0, 4)).map((pferd, idx) => { + const istIMS = isPferdIMS(pferd.id); + return ( + + handlePferdAuswahl(pferd)} + sx={{py: 0.25, display: 'flex', gap: 1}} + > + + {istIMS && ( + + )} + + + ); + }) + ) : ( + + + + )} + + + + {/* Pferd Details - erscheint nach Auswahl */} + {selectedPferd && ( + + + Pferd Details + + + Kopfnummer: {selectedPferd.kopfnr} + + + Name: {selectedPferd.name} + + + Rasse: {selectedPferd.rasse} + + + Farbe: {selectedPferd.farbe} + + + Besitzer: {selectedPferd.besitzer} + + + Stall: {selectedPferd.stall} + + + )} + + {/* Buttons */} + + + + + + + {/* Rechte Hälfte: Reiter */} + + {/* Eingabefeld */} + + + Reiter: + + setReiterSuche(e.target.value)} + onKeyDown={handleReiterKeyDown} + sx={{ + flex: 1, + '& .MuiInputBase-input': {fontSize: '11px', py: 0.75}, + }} + /> + + + + + {/* Suchergebnisse - bleiben immer sichtbar */} + + + {reiterErgebnisse.length > 0 ? ( + (reiterSuche ? reiterErgebnisse : reiterErgebnisse.slice(0, 4)).map((reiter, idx) => { + const istIMS = isReiterIMS(reiter.id); + return ( + + handleReiterAuswahl(reiter)} + sx={{py: 0.25, display: 'flex', gap: 1}} + > + + {istIMS && ( + + )} + + + ); + }) + ) : ( + + + + )} + + + + {/* Reiter Details - erscheint nach Auswahl */} + {selectedReiter && ( + + + Reiter Details + + + Name: {selectedReiter.vorname} {selectedReiter.nachname} + + + Verein: {selectedReiter.verein} + + + + Lizenz: {selectedReiter.lizenz} + + + + + Konto-Saldo: €{selectedReiter.kontoSaldo.toFixed(2)} + + + )} + + {/* Buttons */} + + + + + + + ); +} diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/app/components/TurnierAnsicht.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/TurnierAnsicht.tsx new file mode 100644 index 00000000..a3d1724f --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/TurnierAnsicht.tsx @@ -0,0 +1,130 @@ +import {useState} from 'react'; +import {useParams, useNavigate} from 'react-router'; +import Box from '@mui/material/Box'; +import Tabs from '@mui/material/Tabs'; +import Tab from '@mui/material/Tab'; +import AppBar from '@mui/material/AppBar'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; +import IconButton from '@mui/material/IconButton'; +import Breadcrumbs from '@mui/material/Breadcrumbs'; +import Link from '@mui/material/Link'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import HomeIcon from '@mui/icons-material/Home'; +import {StammdatenTab} from './turnier/StammdatenTab'; +import {OrganisationTab} from './turnier/OrganisationTab'; +import {BewerbeTab} from './turnier/BewerbeTab'; +import {PreislisteTab} from './turnier/PreislisteTab'; +import {veranstaltungenData} from './Dashboard'; + +export function TurnierAnsicht() { + const params = useParams(); + const navigate = useNavigate(); + const veranstaltungId = params.veranstaltungId; + const turnierNr = params.nr; + + // Bei neu: Direkt zu Stammdaten (Tab 0), sonst Stammdaten (Tab 0) + const [activeTab, setActiveTab] = useState(0); + + // Veranstaltung laden + const veranstaltung = veranstaltungId !== 'neu' + ? veranstaltungenData.find(v => v.id === parseInt(veranstaltungId || '0')) + : null; + + // Turnier laden (wenn nicht neu) + const turnier = turnierNr !== 'neu' && veranstaltung + ? veranstaltung.turniere.find(t => t.nr === turnierNr) + : null; + + const handleZurueck = () => { + navigate(`/veranstaltung/${veranstaltungId}`); + }; + + const handleToAdmin = () => { + navigate('/admin'); + }; + + return ( + + {/* Header mit Navigation */} + + + + + + + + + + Admin - Verwaltung + + + {veranstaltung?.name || 'Veranstaltung'} + + + {turnier ? `Turnier ${turnier.nr}` : 'Neues Turnier'} + + + + + + {/* Tab Navigation */} + setActiveTab(v)} + sx={{ + borderBottom: 1, + borderColor: 'divider', + bgcolor: 'background.paper', + '& .MuiTab-root': { + fontSize: '11px', + minHeight: 36, + py: 1, + } + }} + > + + + + + + + {/* Tab Content */} + + {activeTab === 0 && } + {activeTab === 1 && } + {activeTab === 2 && } + {activeTab === 3 && } + + + ); +} diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/app/components/TurnierErstellen.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/TurnierErstellen.tsx new file mode 100644 index 00000000..011cacef --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/TurnierErstellen.tsx @@ -0,0 +1,145 @@ +import {useState} from 'react'; +import {useParams, useNavigate} from 'react-router'; +import Box from '@mui/material/Box'; +import Tabs from '@mui/material/Tabs'; +import Tab from '@mui/material/Tab'; +import AppBar from '@mui/material/AppBar'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; +import IconButton from '@mui/material/IconButton'; +import Breadcrumbs from '@mui/material/Breadcrumbs'; +import Link from '@mui/material/Link'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import HomeIcon from '@mui/icons-material/Home'; +import {VeranstaltungUebersicht} from './turnier/VeranstaltungUebersicht'; +import {veranstaltungenData} from './Dashboard'; +import {StammdatenTab} from './turnier/StammdatenTab'; +import {OrganisationTab} from './turnier/OrganisationTab'; +import {BewerbeTab} from './turnier/BewerbeTab'; +import {PreislisteTab} from './turnier/PreislisteTab'; + +export function TurnierErstellen() { + const params = useParams(); + const navigate = useNavigate(); + const id = params.id; + + // Bei neu: Direkt zu Stammdaten (Tab 1), sonst Veranstaltung - Übersicht (Tab 0) + const [activeTab, setActiveTab] = useState(id === 'neu' ? 1 : 0); + + // Veranstaltung laden + const veranstaltung = id !== 'neu' + ? veranstaltungenData.find(v => v.id === parseInt(id || '0')) + : null; + + const handleZurueck = () => { + navigate('/admin'); + }; + + // Für bestehende Veranstaltungen: Nur "Veranstaltung - Übersicht" Tab + // Für neue Veranstaltungen: Alle Tabs anzeigen + const istNeueVeranstaltung = id === 'neu'; + const istBestehendeVeranstaltung = !istNeueVeranstaltung && veranstaltung; + + return ( + + {/* Header mit Navigation */} + + + + + + + + + + Admin - Verwaltung + + + {veranstaltung?.name || 'Neue Veranstaltung'} + + + + + + {/* Tab Navigation */} + {istBestehendeVeranstaltung ? ( + // Nur "Veranstaltung - Übersicht" für bestehende Veranstaltungen + + + + ) : ( + // Alle Tabs für neue Veranstaltungen + setActiveTab(v)} + sx={{ + borderBottom: 1, + borderColor: 'divider', + bgcolor: 'background.paper', + '& .MuiTab-root': { + fontSize: '11px', + minHeight: 36, + py: 1, + } + }} + > + + + + + + + )} + + {/* Tab Content */} + + {istBestehendeVeranstaltung ? ( + // Nur Veranstaltung - Übersicht für bestehende Veranstaltungen + + ) : ( + // Alle Tabs für neue Veranstaltungen + <> + {activeTab === 0 && } + {activeTab === 1 && } + {activeTab === 2 && } + {activeTab === 3 && } + {activeTab === 4 && } + + )} + + + ); +} diff --git a/docs/06_Frontend/FIGMA/src/app/components/VerkaufBuchungen.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/VerkaufBuchungen.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/VerkaufBuchungen.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/VerkaufBuchungen.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/figma/ImageWithFallback.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/figma/ImageWithFallback.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/figma/ImageWithFallback.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/figma/ImageWithFallback.tsx diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/BewerbeTab.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/BewerbeTab.tsx new file mode 100644 index 00000000..469d1918 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/BewerbeTab.tsx @@ -0,0 +1,1751 @@ +import {useState} from 'react'; +import Box from '@mui/material/Box'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import MenuItem from '@mui/material/MenuItem'; +import Select from '@mui/material/Select'; +import Tabs from '@mui/material/Tabs'; +import Tab from '@mui/material/Tab'; +import Divider from '@mui/material/Divider'; +import Checkbox from '@mui/material/Checkbox'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import SaveIcon from '@mui/icons-material/Save'; +import UndoIcon from '@mui/icons-material/Undo'; +import AddIcon from '@mui/icons-material/Add'; +import DeleteIcon from '@mui/icons-material/Delete'; +import ContentCutIcon from '@mui/icons-material/ContentCut'; +import ArrowUpwardIcon from '@mui/icons-material/ArrowUpward'; +import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward'; +import EditIcon from '@mui/icons-material/Edit'; +import PrintIcon from '@mui/icons-material/Print'; +import FolderOpenIcon from '@mui/icons-material/FolderOpen'; + +interface Bewerb { + id: number; + tag: string; + platz: number; + bewerb: number; + beginn: string; + ende: string; + bewerbname: string; + zns: number; + nennungen: number; + // Detail-Felder + nummer: string; + abteilung: string; + typ: string; + name: string; + bezeichnung: string; + kategorie: string; + klasse: string; + lizenz: string; + maximal: string; + pferdealter: string; + zeile1: string; + zeile2: string; + zeile3: string; + logoBewerbPfad: string; + // Bewertung-Felder + prufung: string; + richtverfahren: string; + paraGrade: string; + richteranzahl: number; + aufgabe: string; + aufgabennr: string; + maximalPunkte: string; + richter: { position: string; name: string; aktiv: boolean }[]; + // Geldpreis-Felder + geldpreisAktiv: boolean; + startgeld: string; + auszahlung: string; + geldpreisKadererreiterAktiv: boolean; + startgeldKadererreiter: string; + geldpreisvorlage: string; + geldpreise: { nummer: string; betrag: string }[]; + // Ort/Zeit-Felder + tagDatum: string; + beginnzeit: string; + beginnZeit: string; + reitdauer: string; + umbau: string; + besichtigung: string; + stechen: string; + platzName: string; +} + +const mockBewerbe: Bewerb[] = [ + { + id: 1, + tag: '28.05.2023', + platz: 1, + bewerb: 1, + beginn: '08:00', + ende: '08:00', + bewerbname: 'Dressurreiterprüfung Reiterpass (Aufgabe R 1)\\nPony Einsteiger Cup OÖ', + zns: 0, + nennungen: 0, + nummer: '1', + abteilung: '', + typ: 'Dressur', + name: 'Dressurreiterprüfung', + bezeichnung: 'Dressurreiterprüfung Reiterpass', + kategorie: '', + klasse: '', + lizenz: '', + maximal: '3', + pferdealter: '', + zeile1: 'Pony Einsteiger Cup OÖ', + zeile2: '', + zeile3: '', + logoBewerbPfad: '', + prufung: 'Dressurreiterprüfung', + richtverfahren: 'A', + paraGrade: '', + richteranzahl: 2, + aufgabe: 'Aufgabe R', + aufgabennr: '', + maximalPunkte: '', + richter: [ + {position: 'C', name: 'Schuster Alexandra', aktiv: true}, + {position: 'C', name: 'Vankova Kamila (CZ)', aktiv: true} + ], + geldpreisAktiv: false, + startgeld: '15,00', + auszahlung: 'fortführend', + geldpreisKadererreiterAktiv: false, + startgeldKadererreiter: '15,00', + geldpreisvorlage: '', + geldpreise: [], + tagDatum: '28.05.2023', + beginnzeit: 'fix um', + beginnZeit: '08:00', + reitdauer: '02:00', + umbau: '10', + besichtigung: '10', + stechen: '', + platzName: 'Vorderer Turnierplatz' + }, + { + id: 2, + tag: '28.05.2023', + platz: 1, + bewerb: 2, + beginn: '08:20', + ende: '08:20', + bewerbname: 'Dressurreiterprüfung Reitenadel (Aufgabe R 4)\nPony Einsteiger Cup OÖ', + zns: 0, + nennungen: 0, + nummer: '2', + abteilung: '', + typ: 'Dressur', + name: 'Dressurreiterprüfung', + bezeichnung: 'Dressurreiterprüfung Reitenadel', + kategorie: '', + klasse: '', + lizenz: '', + maximal: '3', + pferdealter: '', + zeile1: 'Pony Einsteiger Cup OÖ', + zeile2: '', + zeile3: '', + logoBewerbPfad: '', + prufung: 'Dressurreiterprüfung', + richtverfahren: 'Richtverfahren', + paraGrade: 'Para-Grade', + richteranzahl: 3, + aufgabe: 'Aufgabe R 4', + aufgabennr: '4', + maximalPunkte: '100', + richter: [ + {position: 'Richter 1', name: 'Max Mustermann', aktiv: true}, + {position: 'Richter 2', name: 'Anna Musterfrau', aktiv: true}, + {position: 'Richter 3', name: 'Peter Muster', aktiv: true} + ], + geldpreisAktiv: false, + startgeld: '', + auszahlung: '', + geldpreisKadererreiterAktiv: false, + startgeldKadererreiter: '', + geldpreisvorlage: '', + geldpreise: [], + tagDatum: '', + beginnzeit: '', + beginnZeit: '', + reitdauer: '', + umbau: '', + besichtigung: '', + stechen: '', + platzName: '' + }, + { + id: 3, + tag: '28.05.2023', + platz: 1, + bewerb: 3, + beginn: '08:40', + ende: '08:40', + bewerbname: 'Dressurreiterprüfung lsf. (Istzfrei) (Aufgabe LF 1)', + zns: 0, + nennungen: 0, + nummer: '3', + abteilung: '', + typ: 'Dressur', + name: 'Dressurreiterprüfung', + bezeichnung: 'Dressurreiterprüfung lsf. (Istzfrei)', + kategorie: '', + klasse: '', + lizenz: '', + maximal: '3', + pferdealter: '', + zeile1: '', + zeile2: '', + zeile3: '', + logoBewerbPfad: '', + prufung: 'Dressurreiterprüfung', + richtverfahren: 'Richtverfahren', + paraGrade: 'Para-Grade', + richteranzahl: 3, + aufgabe: 'Aufgabe LF 1', + aufgabennr: '1', + maximalPunkte: '100', + richter: [ + {position: 'Richter 1', name: 'Max Mustermann', aktiv: true}, + {position: 'Richter 2', name: 'Anna Musterfrau', aktiv: true}, + {position: 'Richter 3', name: 'Peter Muster', aktiv: true} + ], + geldpreisAktiv: false, + startgeld: '', + auszahlung: '', + geldpreisKadererreiterAktiv: false, + startgeldKadererreiter: '', + geldpreisvorlage: '', + geldpreise: [], + tagDatum: '', + beginnzeit: '', + beginnZeit: '', + reitdauer: '', + umbau: '', + besichtigung: '', + stechen: '', + platzName: '' + }, + { + id: 4, + tag: '28.05.2023', + platz: 1, + bewerb: 4, + beginn: '09:00', + ende: '09:00', + bewerbname: 'Dressurreiterprüfung lsf. (Lizenfrei) (Aufgabe LF 3)', + zns: 0, + nennungen: 0, + nummer: '4', + abteilung: '', + typ: 'Dressur', + name: 'Dressurreiterprüfung', + bezeichnung: 'Dressurreiterprüfung lsf. (Lizenfrei)', + kategorie: '', + klasse: '', + lizenz: '', + maximal: '3', + pferdealter: '', + zeile1: '', + zeile2: '', + zeile3: '', + logoBewerbPfad: '', + prufung: 'Dressurreiterprüfung', + richtverfahren: 'Richtverfahren', + paraGrade: 'Para-Grade', + richteranzahl: 3, + aufgabe: 'Aufgabe LF 3', + aufgabennr: '3', + maximalPunkte: '100', + richter: [ + {position: 'Richter 1', name: 'Max Mustermann', aktiv: true}, + {position: 'Richter 2', name: 'Anna Musterfrau', aktiv: true}, + {position: 'Richter 3', name: 'Peter Muster', aktiv: true} + ], + geldpreisAktiv: false, + startgeld: '', + auszahlung: '', + geldpreisKadererreiterAktiv: false, + startgeldKadererreiter: '', + geldpreisvorlage: '', + geldpreise: [], + tagDatum: '', + beginnzeit: '', + beginnZeit: '', + reitdauer: '', + umbau: '', + besichtigung: '', + stechen: '', + platzName: '' + }, + { + id: 5, + tag: '28.05.2023', + platz: 1, + bewerb: 5, + beginn: '09:20', + ende: '09:20', + bewerbname: 'Führzügelklasse\nOÖ Kids Cup', + zns: 0, + nennungen: 0, + nummer: '5', + abteilung: '', + typ: 'Dressur', + name: 'Führzügelklasse', + bezeichnung: 'Führzügelklasse', + kategorie: '', + klasse: '', + lizenz: '', + maximal: '3', + pferdealter: '', + zeile1: 'OÖ Kids Cup', + zeile2: '', + zeile3: '', + logoBewerbPfad: '', + prufung: 'Führzügelklasse', + richtverfahren: 'Richtverfahren', + paraGrade: 'Para-Grade', + richteranzahl: 3, + aufgabe: 'Aufgabe FZ 1', + aufgabennr: '1', + maximalPunkte: '100', + richter: [ + {position: 'Richter 1', name: 'Max Mustermann', aktiv: true}, + {position: 'Richter 2', name: 'Anna Musterfrau', aktiv: true}, + {position: 'Richter 3', name: 'Peter Muster', aktiv: true} + ], + geldpreisAktiv: false, + startgeld: '', + auszahlung: '', + geldpreisKadererreiterAktiv: false, + startgeldKadererreiter: '', + geldpreisvorlage: '', + geldpreise: [], + tagDatum: '', + beginnzeit: '', + beginnZeit: '', + reitdauer: '', + umbau: '', + besichtigung: '', + stechen: '', + platzName: '' + }, + { + id: 6, + tag: '28.05.2023', + platz: 1, + bewerb: 6, + beginn: '09:40', + ende: '09:40', + bewerbname: 'First Ridden\nOÖ Kids Cup', + zns: 0, + nennungen: 0, + nummer: '6', + abteilung: '', + typ: 'Dressur', + name: 'First Ridden', + bezeichnung: 'First Ridden', + kategorie: '', + klasse: '', + lizenz: '', + maximal: '3', + pferdealter: '', + zeile1: 'OÖ Kids Cup', + zeile2: '', + zeile3: '', + logoBewerbPfad: '', + prufung: 'First Ridden', + richtverfahren: 'Richtverfahren', + paraGrade: 'Para-Grade', + richteranzahl: 3, + aufgabe: 'Aufgabe FR 1', + aufgabennr: '1', + maximalPunkte: '100', + richter: [ + {position: 'Richter 1', name: 'Max Mustermann', aktiv: true}, + {position: 'Richter 2', name: 'Anna Musterfrau', aktiv: true}, + {position: 'Richter 3', name: 'Peter Muster', aktiv: true} + ], + geldpreisAktiv: false, + startgeld: '', + auszahlung: '', + geldpreisKadererreiterAktiv: false, + startgeldKadererreiter: '', + geldpreisvorlage: '', + geldpreise: [], + tagDatum: '', + beginnzeit: '', + beginnZeit: '', + reitdauer: '', + umbau: '', + besichtigung: '', + stechen: '', + platzName: '' + }, + { + id: 7, + tag: '28.05.2023', + platz: 1, + bewerb: 7, + beginn: '10:00', + ende: '10:00', + bewerbname: 'Pony Dressurprüfung Kl. A (Aufgabe P 1)', + zns: 0, + nennungen: 0, + nummer: '7', + abteilung: '', + typ: 'Dressur', + name: 'Pony Dressurprüfung', + bezeichnung: 'Pony Dressurprüfung Kl. A', + kategorie: '', + klasse: '', + lizenz: '', + maximal: '3', + pferdealter: '', + zeile1: '', + zeile2: '', + zeile3: '', + logoBewerbPfad: '', + prufung: 'Pony Dressurprüfung', + richtverfahren: 'Richtverfahren', + paraGrade: 'Para-Grade', + richteranzahl: 3, + aufgabe: 'Aufgabe P 1', + aufgabennr: '1', + maximalPunkte: '100', + richter: [ + {position: 'Richter 1', name: 'Max Mustermann', aktiv: true}, + {position: 'Richter 2', name: 'Anna Musterfrau', aktiv: true}, + {position: 'Richter 3', name: 'Peter Muster', aktiv: true} + ], + geldpreisAktiv: false, + startgeld: '', + auszahlung: '', + geldpreisKadererreiterAktiv: false, + startgeldKadererreiter: '', + geldpreisvorlage: '', + geldpreise: [], + tagDatum: '', + beginnzeit: '', + beginnZeit: '', + reitdauer: '', + umbau: '', + besichtigung: '', + stechen: '', + platzName: '' + }, + { + id: 8, + tag: '28.05.2023', + platz: 1, + bewerb: 8, + beginn: '10:20', + ende: '10:20', + bewerbname: 'Dressurreiterprüfung Kl. A (Aufgabe DRA 1)', + zns: 0, + nennungen: 0, + nummer: '8', + abteilung: '', + typ: 'Dressur', + name: 'Dressurreiterprüfung', + bezeichnung: 'Dressurreiterprüfung Kl. A', + kategorie: '', + klasse: '', + lizenz: '', + maximal: '3', + pferdealter: '', + zeile1: '', + zeile2: '', + zeile3: '', + logoBewerbPfad: '', + prufung: 'Dressurreiterprüfung', + richtverfahren: 'Richtverfahren', + paraGrade: 'Para-Grade', + richteranzahl: 3, + aufgabe: 'Aufgabe DRA 1', + aufgabennr: '1', + maximalPunkte: '100', + richter: [ + {position: 'Richter 1', name: 'Max Mustermann', aktiv: true}, + {position: 'Richter 2', name: 'Anna Musterfrau', aktiv: true}, + {position: 'Richter 3', name: 'Peter Muster', aktiv: true} + ], + geldpreisAktiv: false, + startgeld: '', + auszahlung: '', + geldpreisKadererreiterAktiv: false, + startgeldKadererreiter: '', + geldpreisvorlage: '', + geldpreise: [], + tagDatum: '', + beginnzeit: '', + beginnZeit: '', + reitdauer: '', + umbau: '', + besichtigung: '', + stechen: '', + platzName: '' + }, + { + id: 9, + tag: '28.05.2023', + platz: 1, + bewerb: 9, + beginn: '10:40', + ende: '10:40', + bewerbname: 'Dressurreiterprüfung Kl. A (Aufgabe A 5)', + zns: 0, + nennungen: 0, + nummer: '9', + abteilung: '', + typ: 'Dressur', + name: 'Dressurreiterprüfung', + bezeichnung: 'Dressurreiterprüfung Kl. A', + kategorie: '', + klasse: '', + lizenz: '', + maximal: '3', + pferdealter: '', + zeile1: 'TS Erfolgreichstes Pony OÖ', + zeile2: '', + zeile3: '', + logoBewerbPfad: '', + prufung: 'Dressurreiterprüfung', + richtverfahren: 'Richtverfahren', + paraGrade: 'Para-Grade', + richteranzahl: 3, + aufgabe: 'Aufgabe A 5', + aufgabennr: '5', + maximalPunkte: '100', + richter: [ + {position: 'Richter 1', name: 'Max Mustermann', aktiv: true}, + {position: 'Richter 2', name: 'Anna Musterfrau', aktiv: true}, + {position: 'Richter 3', name: 'Peter Muster', aktiv: true} + ], + geldpreisAktiv: false, + startgeld: '', + auszahlung: '', + geldpreisKadererreiterAktiv: false, + startgeldKadererreiter: '', + geldpreisvorlage: '', + geldpreise: [], + tagDatum: '', + beginnzeit: '', + beginnZeit: '', + reitdauer: '', + umbau: '', + besichtigung: '', + stechen: '', + platzName: '' + }, + { + id: 10, + tag: '28.05.2023', + platz: 1, + bewerb: 10, + beginn: '11:00', + ende: '11:00', + bewerbname: 'Pony Dressurprüfung Kl. A (Aufgabe P 9)', + zns: 0, + nennungen: 0, + nummer: '10', + abteilung: '', + typ: 'Dressur', + name: 'Pony Dressurprüfung', + bezeichnung: 'Pony Dressurprüfung Kl. A', + kategorie: '', + klasse: '', + lizenz: '', + maximal: '3', + pferdealter: '', + zeile1: '', + zeile2: '', + zeile3: '', + logoBewerbPfad: '', + prufung: 'Pony Dressurprüfung', + richtverfahren: 'Richtverfahren', + paraGrade: 'Para-Grade', + richteranzahl: 3, + aufgabe: 'Aufgabe P 9', + aufgabennr: '9', + maximalPunkte: '100', + richter: [ + {position: 'Richter 1', name: 'Max Mustermann', aktiv: true}, + {position: 'Richter 2', name: 'Anna Musterfrau', aktiv: true}, + {position: 'Richter 3', name: 'Peter Muster', aktiv: true} + ], + geldpreisAktiv: false, + startgeld: '', + auszahlung: '', + geldpreisKadererreiterAktiv: false, + startgeldKadererreiter: '', + geldpreisvorlage: '', + geldpreise: [], + tagDatum: '', + beginnzeit: '', + beginnZeit: '', + reitdauer: '', + umbau: '', + besichtigung: '', + stechen: '', + platzName: '' + }, + { + id: 11, + tag: '28.05.2023', + platz: 1, + bewerb: 11, + beginn: '11:20', + ende: '11:20', + bewerbname: 'Dressurreiterprüfung Kl. L (Aufgabe DRL 1)', + zns: 0, + nennungen: 0, + nummer: '11', + abteilung: '', + typ: 'Dressur', + name: 'Dressurreiterprüfung', + bezeichnung: 'Dressurreiterprüfung Kl. L', + kategorie: '', + klasse: '', + lizenz: '', + maximal: '3', + pferdealter: '', + zeile1: '', + zeile2: '', + zeile3: '', + logoBewerbPfad: '', + prufung: 'Dressurreiterprüfung', + richtverfahren: 'Richtverfahren', + paraGrade: 'Para-Grade', + richteranzahl: 3, + aufgabe: 'Aufgabe DRL 1', + aufgabennr: '1', + maximalPunkte: '100', + richter: [ + {position: 'Richter 1', name: 'Max Mustermann', aktiv: true}, + {position: 'Richter 2', name: 'Anna Musterfrau', aktiv: true}, + {position: 'Richter 3', name: 'Peter Muster', aktiv: true} + ], + geldpreisAktiv: false, + startgeld: '', + auszahlung: '', + geldpreisKadererreiterAktiv: false, + startgeldKadererreiter: '', + geldpreisvorlage: '', + geldpreise: [], + tagDatum: '', + beginnzeit: '', + beginnZeit: '', + reitdauer: '', + umbau: '', + besichtigung: '', + stechen: '', + platzName: '' + }, + { + id: 12, + tag: '28.05.2023', + platz: 1, + bewerb: 12, + beginn: '11:40', + ende: '11:40', + bewerbname: 'Dressurprüfung Kl. L (Aufgabe L 3)', + zns: 0, + nennungen: 0, + nummer: '12', + abteilung: '', + typ: 'Dressur', + name: 'Dressurprüfung', + bezeichnung: 'Dressurprüfung Kl. L', + kategorie: '', + klasse: '', + lizenz: '', + maximal: '3', + pferdealter: '', + zeile1: '', + zeile2: '', + zeile3: '', + logoBewerbPfad: '', + prufung: 'Dressurprüfung', + richtverfahren: 'Richtverfahren', + paraGrade: 'Para-Grade', + richteranzahl: 3, + aufgabe: 'Aufgabe L 3', + aufgabennr: '3', + maximalPunkte: '100', + richter: [ + {position: 'Richter 1', name: 'Max Mustermann', aktiv: true}, + {position: 'Richter 2', name: 'Anna Musterfrau', aktiv: true}, + {position: 'Richter 3', name: 'Peter Muster', aktiv: true} + ], + geldpreisAktiv: false, + startgeld: '', + auszahlung: '', + geldpreisKadererreiterAktiv: false, + startgeldKadererreiter: '', + geldpreisvorlage: '', + geldpreise: [], + tagDatum: '', + beginnzeit: '', + beginnZeit: '', + reitdauer: '', + umbau: '', + besichtigung: '', + stechen: '', + platzName: '' + } +]; + +export function BewerbeTab() { + const [bewerbe] = useState(mockBewerbe); + const [selectedBewerbId, setSelectedBewerbId] = useState(1); + const [detailTab, setDetailTab] = useState(0); + + const selectedBewerb = bewerbe.find(b => b.id === selectedBewerbId); + + const handleSpeichern = () => { + console.log('Änderungen speichern'); + }; + + return ( + + {/* Linke Sidebar - Aktionen */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Mitte - Bewerbs-Übersicht Tabelle (50%) */} + + {/* Toolbar */} + + + + + + + {/* Tabelle */} + + + + + Tag + Platz + Bewerb + Beginn + Ende + Bewerbname + ZNS + Nennungen + + + + {bewerbe.map((bewerb) => ( + setSelectedBewerbId(bewerb.id)} + sx={{ + cursor: 'pointer', + bgcolor: bewerb.bewerb === 5 || bewerb.bewerb === 6 ? 'warning.50' : 'inherit', + '&.Mui-selected': { + bgcolor: bewerb.bewerb === 5 || bewerb.bewerb === 6 ? 'warning.100' : 'action.selected' + } + }} + > + {bewerb.tag} + {bewerb.platz} + {bewerb.bewerb} + {bewerb.beginn} + {bewerb.ende} + {bewerb.bewerbname} + {bewerb.zns} + {bewerb.nennungen} + + ))} + +
+
+
+ + {/* Rechts - Bewerb-Konfiguration (50%) */} + {selectedBewerb && ( + + {/* Detail-Tabs */} + setDetailTab(v)} + sx={{ + borderBottom: 1, + borderColor: 'divider', + bgcolor: 'background.paper', + '& .MuiTab-root': { + fontSize: '11px', + minHeight: 36, + py: 1, + textTransform: 'none' + } + }} + > + + + + + + + {/* Tab Content */} + + {/* TAB 0: Bewerb */} + {detailTab === 0 && ( + + {/* Nummer */} + + + Nummer: + + + + + {/* Abteilung */} + + + Abteilung: + + + + + {/* Typ */} + + + Typ: + + + + + {/* Name */} + + + Name: + + + + + {/* Bezeichnung */} + + + Bezeichnung: + + + + + {/* Kategorie */} + + + Kategorie: + + + + + {/* Klasse */} + + + Klasse: + + + + + {/* Lizenz */} + + + Lizenz: + + + + + {/* Maximal */} + + + Maximal: + + + + Pferde je Reiter + + + + {/* Pferdealter */} + + + Pferdealter: + + + + + {/* Zeile 1 */} + + + Zeile 1: + + + + + {/* Zeile 2 */} + + + Zeile 2: + + + + + {/* Zeile 3 */} + + + Zeile 3: + + + + + {/* Logo Bewerb */} + + + Logo Bewerb: + + + + + + )} + + {/* TAB 1: Bewertung */} + {detailTab === 1 && ( + + + Bewertungs-Konfiguration + + + {/* Prüfung */} + + + Prüfung: + + + + + {/* Richtverfahren */} + + + Richtverfahren: + + + + + {/* Para-Grade */} + + + Para-Grade: + + + + + {/* Richteranzahl */} + + + Richteranzahl: + + + + + {/* Aufgabe */} + + + Aufgabe: + + + + + {/* Aufgabennummer */} + + + Aufgabennummer: + + + + + {/* Maximalpunkte */} + + + Maximalpunkte: + + + + + {/* Richter */} + + + Richter + + {selectedBewerb.richter.map((richter, index) => ( + + + {richter.position}: + + + + + ))} + + + + )} + + {/* TAB 2: Geldpreise */} + {detailTab === 2 && ( + + {/* Geldpreis Section */} + + + Geldpreis + + + + {/* Geldpreis Checkbox */} + + + + Geldpreis + + + + {/* Startgeld */} + + + Startgeld: + + + + + {/* Auszahlung */} + + + Auszahlung: + + + + + + + {/* Geldpreis für Kadererreiter Section */} + + + Geldpreis für Kadererreiter + + + + {/* Geldpreis für Kadererreiter Checkbox */} + + + + Geldpreis für Kadererreiter + + + + {/* Startgeld für Kadererreiter */} + + + Startgeld für Kadererreiter: + + + + + + + {/* Geldpreisvorlage */} + + + Geldpreisvorlage wählen: + + + + + {/* Geldpreise Tabelle */} + + + + {selectedBewerb.geldpreise.length} Geldpreise + + + + + + + Nummer + Geldpreis + + + + {selectedBewerb.geldpreise.length === 0 && ( + + + + + )} + +
+
+
+
+ )} + + {/* TAB 3: Ort/Zeit */} + {detailTab === 3 && ( + + {/* Tag */} + + + Tag: + + + + + {/* Beginnzeit */} + + + Beginnzeit: + + + + + {/* Zeit */} + + + + + + (hh:mm) + + + + {/* Reitdauer */} + + + Reitdauer: + + + + (mm:ss) + + + + {/* Umbau */} + + + Umbau: + + + + (mm) + + + + {/* Besichtigung */} + + + Besichtigung: + + + + (mm) + + + + {/* Stechen */} + + + Stechen: + + + + (mm) + + + + {/* Platz */} + + + Platz: + + + + + )} +
+
+ )} +
+ ); +} diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/FunktionaereTab.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/FunktionaereTab.tsx new file mode 100644 index 00000000..08626363 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/FunktionaereTab.tsx @@ -0,0 +1,398 @@ +import {useState} from 'react'; +import Box from '@mui/material/Box'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import IconButton from '@mui/material/IconButton'; +import MenuItem from '@mui/material/MenuItem'; +import Select from '@mui/material/Select'; +import DeleteIcon from '@mui/icons-material/Delete'; +import AddIcon from '@mui/icons-material/Add'; +import SearchIcon from '@mui/icons-material/Search'; + +interface Richter { + id: number; + name: string; + qualifikation: string; + funktion: string; +} + +// Mock-Qualifikationen basierend auf OEPS-System +const qualifikationen = [ + 'D-E', 'D-A', 'D-L', 'D-M', 'D-S', 'D-GP', // Dressur + 'S-E', 'S-A', 'S-L', 'S-M', 'S-S', // Springen + 'V-E', 'V-A', 'V-L', 'V-M', 'V-S', // Vielseitigkeit + 'FEI Level 1', 'FEI Level 2', 'FEI Level 3' // International +]; + +const richterfunktionen = [ + 'Hauptrichter', + 'Beisitzer', + 'Richter bei C', + 'Richter bei H', + 'Richter bei M', + 'Richter bei B', + 'Richter bei E' +]; + +export function FunktionaereTab() { + // Einzelne Funktionäre + const [turnierleiter, setTurnierleiter] = useState(''); + const [turnierbeauftragter, setTurnierbeauftragter] = useState(''); + const [technischerDelegierter, setTechnischerDelegierter] = useState(''); + const [parcourschef, setParcourschef] = useState(''); + const [tierarzt, setTierarzt] = useState(''); + const [schmied, setSchmied] = useState(''); + const [steward, setSteward] = useState(''); + + // Richterkollegium (dynamische Liste) + const [richter, setRichter] = useState([ + {id: 1, name: 'Alexandra Schuster', qualifikation: 'D-GP', funktion: 'Hauptrichter'}, + {id: 2, name: 'Ulrike Knasmüller-Prinz', qualifikation: 'D-M', funktion: 'Beisitzer'}, + ]); + + const handleRichterHinzufuegen = () => { + const newId = Math.max(0, ...richter.map(r => r.id)) + 1; + setRichter([ + ...richter, + {id: newId, name: '', qualifikation: 'D-E', funktion: 'Beisitzer'} + ]); + }; + + const handleRichterLoeschen = (id: number) => { + setRichter(richter.filter(r => r.id !== id)); + }; + + const handleRichterAendern = (id: number, field: keyof Richter, value: string) => { + setRichter(richter.map(r => + r.id === id ? {...r, [field]: value} : r + )); + }; + + const handleSpeichern = () => { + console.log('Funktionäre speichern:', { + turnierleiter, + turnierbeauftragter, + technischerDelegierter, + parcourschef, + tierarzt, + schmied, + steward, + richter, + }); + // TODO: Backend Integration (C-Satz) + }; + + return ( + + + + Funktionäre & Offizielle (C-Satz) + + + {/* Turnier-Organisation */} + + + Turnier-Organisation + + + + + + Turnierleiter: + + setTurnierleiter(e.target.value)} + placeholder="z.B. Ursula Stroblmair" + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + InputProps={{ + endAdornment: ( + + + + ) + }} + /> + + + + + Turnierbeauftragte/r: + + setTurnierbeauftragter(e.target.value)} + placeholder="z.B. Rudi Kreupl" + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + InputProps={{ + endAdornment: ( + + + + ) + }} + /> + + + + + Technischer Delegierter (TD): + + setTechnischerDelegierter(e.target.value)} + placeholder="Optional (hauptsächlich Vielseitigkeit)" + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + InputProps={{ + endAdornment: ( + + + + ) + }} + /> + + + + + Steward: + + setSteward(e.target.value)} + placeholder="z.B. Barbara Hruschka" + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + InputProps={{ + endAdornment: ( + + + + ) + }} + /> + + + + + {/* Parcours & Technik */} + + + Parcours & Technik + + + + + + Parcourschef: + + setParcourschef(e.target.value)} + placeholder="z.B. Kurt Reitetschläger" + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + InputProps={{ + endAdornment: ( + + + + ) + }} + /> + + + + + {/* Medizinische Versorgung */} + + + Medizinische Versorgung + + + + + + Turniertierarzt: + + setTierarzt(e.target.value)} + placeholder="z.B. Dr. Sabine Ötschmaier" + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + InputProps={{ + endAdornment: ( + + + + ) + }} + /> + + + + + Schmied: + + setSchmied(e.target.value)} + placeholder="Name des Turnierschmieds" + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + InputProps={{ + endAdornment: ( + + + + ) + }} + /> + + + + + {/* Richterkollegium */} + + + + Richterkollegium + + + + + + + + + Name + Qualifikation + Funktion + + + + + {richter.map((r) => ( + + + handleRichterAendern(r.id, 'name', e.target.value)} + placeholder="Name des Richters" + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.5}}} + InputProps={{ + endAdornment: ( + + + + ) + }} + /> + + + + + + + + + handleRichterLoeschen(r.id)} + > + + + + + ))} + +
+
+ + {richter.length === 0 && ( + + + Keine Richter definiert + + + )} +
+ + {/* Hinweis */} + + + ℹ️ Hinweis zu Funktionären + + + Die Funktionäre werden im C-Satz der ZNS-Schnittstelle übermittelt. + Richter müssen entsprechende Qualifikationen für die jeweiligen Klassen besitzen (z.B. D-GP für Grand Prix + Dressur). + Bei internationalen Turnieren sind FEI-Lizenzen erforderlich. + + + + {/* Action Buttons */} + + + + +
+
+ ); +} diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/OrganisationTab.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/OrganisationTab.tsx new file mode 100644 index 00000000..7158b8e0 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/OrganisationTab.tsx @@ -0,0 +1,411 @@ +import {useState} from 'react'; +import Box from '@mui/material/Box'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import IconButton from '@mui/material/IconButton'; +import MenuItem from '@mui/material/MenuItem'; +import Select from '@mui/material/Select'; +import DeleteIcon from '@mui/icons-material/Delete'; +import AddIcon from '@mui/icons-material/Add'; +import Divider from '@mui/material/Divider'; + +interface Richter { + id: number; + name: string; + qualifikation: string; + funktion: string; +} + +interface Platz { + id: number; + sparte: string; + groesse: string; + bezeichnung: string; +} + +// Mock-Qualifikationen basierend auf OEPS-System +const qualifikationen = [ + 'D-E', 'D-A', 'D-L', 'D-M', 'D-S', 'D-GP', // Dressur + 'S-E', 'S-A', 'S-L', 'S-M', 'S-S', // Springen + 'V-E', 'V-A', 'V-L', 'V-M', 'V-S', // Vielseitigkeit + 'FEI Level 1', 'FEI Level 2', 'FEI Level 3' // International +]; + +const richterfunktionen = [ + 'Hauptrichter', + 'Beisitzer', + 'Richter bei C', + 'Richter bei H', + 'Richter bei M', + 'Richter bei B', + 'Richter bei E' +]; + +const sparten = ['Dressur', 'Springen', 'Vielseitigkeit']; + +const platzgroessen = [ + '20 x 40 m', + '20 x 60 m', + '25 x 60 m', + '30 x 60 m', + 'Springplatz' +]; + +export function OrganisationTab() { + // Einzelne Funktionäre + const [turnierleiter, setTurnierleiter] = useState(''); + const [turnierbeauftragter, setTurnierbeauftragter] = useState(''); + const [technischerDelegierter, setTechnischerDelegierter] = useState(''); + const [parcourschef, setParcourschef] = useState(''); + const [tierarzt, setTierarzt] = useState(''); + const [schmied, setSchmied] = useState(''); + const [steward, setSteward] = useState(''); + + // Richterkollegium (dynamische Liste) + const [richter, setRichter] = useState([ + {id: 1, name: 'Alexandra Schuster', qualifikation: 'D-GP', funktion: 'Hauptrichter'}, + {id: 2, name: 'Ulrike Knasmüller-Prinz', qualifikation: 'D-M', funktion: 'Beisitzer'}, + ]); + + // Plätze (dynamische Liste) + const [plaetze, setPlaetze] = useState([ + {id: 1, sparte: 'Dressur', groesse: '20 x 60 m', bezeichnung: 'Hauptplatz'}, + {id: 2, sparte: 'Dressur', groesse: '20 x 40 m', bezeichnung: 'Abreiteplatz 1'}, + ]); + + const handleRichterHinzufuegen = () => { + const newId = Math.max(0, ...richter.map(r => r.id)) + 1; + setRichter([ + ...richter, + {id: newId, name: '', qualifikation: 'D-E', funktion: 'Beisitzer'} + ]); + }; + + const handleRichterLoeschen = (id: number) => { + setRichter(richter.filter(r => r.id !== id)); + }; + + const handleRichterAendern = (id: number, field: keyof Richter, value: string) => { + setRichter(richter.map(r => + r.id === id ? {...r, [field]: value} : r + )); + }; + + const handlePlatzHinzufuegen = () => { + const newId = Math.max(0, ...plaetze.map(p => p.id)) + 1; + setPlaetze([ + ...plaetze, + {id: newId, sparte: 'Dressur', groesse: '20 x 60 m', bezeichnung: ''} + ]); + }; + + const handlePlatzLoeschen = (id: number) => { + setPlaetze(plaetze.filter(p => p.id !== id)); + }; + + const handlePlatzAendern = (id: number, field: keyof Platz, value: string) => { + setPlaetze(plaetze.map(p => + p.id === id ? {...p, [field]: value} : p + )); + }; + + const handleSpeichern = () => { + console.log('Organisation speichern:', { + turnierleiter, + turnierbeauftragter, + technischerDelegierter, + parcourschef, + tierarzt, + schmied, + steward, + richter, + plaetze, + }); + // TODO: Backend Integration (C-Satz) + }; + + return ( + + + {/* === FUNKTIONÄRE === */} + + Funktionäre & Offizielle (C-Satz) + + + {/* Turnier-Organisation */} + + + Turnier-Organisation + + + + Turnierleiter: + setTurnierleiter(e.target.value)} + placeholder="Name suchen..." + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + /> + + Turnierbeauftragter: + setTurnierbeauftragter(e.target.value)} + placeholder="Name suchen..." + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + /> + + Technischer Delegierter: + setTechnischerDelegierter(e.target.value)} + placeholder="Name suchen..." + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + /> + + Parcourschef: + setParcourschef(e.target.value)} + placeholder="Name suchen..." + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + /> + + + + {/* Support-Team */} + + + Support-Team + + + + Tierarzt: + setTierarzt(e.target.value)} + placeholder="Name suchen..." + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + /> + + Schmied: + setSchmied(e.target.value)} + placeholder="Name suchen..." + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + /> + + Steward: + setSteward(e.target.value)} + placeholder="Name suchen..." + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + /> + + + + {/* Richterkollegium */} + + + + Richterkollegium + + + + + + + + + Name + Qualifikation + Funktion + Aktion + + + + {richter.map((r) => ( + + + handleRichterAendern(r.id, 'name', e.target.value)} + placeholder="Name suchen..." + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.5}}} + /> + + + + + + + + + handleRichterLoeschen(r.id)} + sx={{color: 'error.main'}} + > + + + + + ))} + +
+
+
+ + + + {/* === PLÄTZE === */} + + Austragungsplätze + + + + + + Plätze & Anlagen + + + + + + + + + Sparte + Größe + Bezeichnung + Aktion + + + + {plaetze.map((p) => ( + + + + + + + + + handlePlatzAendern(p.id, 'bezeichnung', e.target.value)} + placeholder="z.B. Hauptplatz, Abreiteplatz 1..." + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.5}}} + /> + + + handlePlatzLoeschen(p.id)} + sx={{color: 'error.main'}} + > + + + + + ))} + +
+
+
+ + {/* Speichern Button */} + + + +
+
+ ); +} diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/PreislisteTab.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/PreislisteTab.tsx new file mode 100644 index 00000000..c6fec89d --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/PreislisteTab.tsx @@ -0,0 +1,345 @@ +import {useState} from 'react'; +import Box from '@mui/material/Box'; +import TextField from '@mui/material/TextField'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; +import Paper from '@mui/material/Paper'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import IconButton from '@mui/material/IconButton'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import DeleteIcon from '@mui/icons-material/Delete'; +import AddIcon from '@mui/icons-material/Add'; +import Divider from '@mui/material/Divider'; + +interface Gebuehr { + id: number; + bezeichnung: string; + betrag: string; + pflicht: boolean; +} + +export function PreislisteTab() { + // Nennungs- und Startgebühren + const [nenngebuehrProPferd, setNenngebuehrProPferd] = useState('0.00'); + const [startgebuehrProBewerb, setStartgebuehrProBewerb] = useState('15.00'); + const [sporteuro, setSporteuro] = useState('0.00'); + const [nachnennungsgebuehr, setNachnennungsgebuehr] = useState('0.00'); + const [nennungstauschgebuehr, setNennungstauschgebuehr] = useState('0.00'); + + // Stallungen & Boxen + const [boxenProTag, setBoxenProTag] = useState('0.00'); + const [einstreuErst, setEinstreuErst] = useState('0.00'); + const [einstreuNach, setEinstreuNach] = useState('0.00'); + const [paddockProTag, setPaddockProTag] = useState('0.00'); + + // Zusatzgebühren (dynamisch) + const [zusatzgebuehren, setZusatzgebuehren] = useState([ + {id: 1, bezeichnung: 'Stromanschluss pro Tag', betrag: '5.00', pflicht: false}, + {id: 2, bezeichnung: 'Camping pro Nacht', betrag: '10.00', pflicht: false}, + ]); + + const handleZusatzgebuehrHinzufuegen = () => { + const newId = Math.max(0, ...zusatzgebuehren.map(g => g.id)) + 1; + setZusatzgebuehren([ + ...zusatzgebuehren, + {id: newId, bezeichnung: '', betrag: '0.00', pflicht: false} + ]); + }; + + const handleZusatzgebuehrLoeschen = (id: number) => { + setZusatzgebuehren(zusatzgebuehren.filter(g => g.id !== id)); + }; + + const handleZusatzgebuehrAendern = (id: number, field: keyof Gebuehr, value: string | boolean) => { + setZusatzgebuehren(zusatzgebuehren.map(g => + g.id === id ? {...g, [field]: value} : g + )); + }; + + const handleSpeichern = () => { + console.log('Preisliste speichern:', { + nenngebuehrProPferd, + startgebuehrProBewerb, + sporteuro, + nachnennungsgebuehr, + nennungstauschgebuehr, + boxenProTag, + einstreuErst, + einstreuNach, + paddockProTag, + zusatzgebuehren, + }); + // TODO: Backend Integration + }; + + return ( + + + + Nennungen & Gebühren + + + {/* Nennungs- und Startgebühren */} + + + Nennungs- und Startgebühren + + + + + + Nenngebühr pro Pferd/Reiter: + + setNenngebuehrProPferd(e.target.value)} + sx={{width: 120, '& .MuiInputBase-input': {fontSize: '11px', py: 0.75, textAlign: 'right'}}} + InputProps={{endAdornment: '€'}} + /> + + (Grundgebühr unabhängig von Anzahl Bewerben) + + + + + + Startgebühr pro Bewerb: + + setStartgebuehrProBewerb(e.target.value)} + sx={{width: 120, '& .MuiInputBase-input': {fontSize: '11px', py: 0.75, textAlign: 'right'}}} + InputProps={{endAdornment: '€'}} + /> + + (Pro einzelner Prüfung) + + + + + + Sporteuro (Beitrag OEPS): + + setSporteuro(e.target.value)} + sx={{width: 120, '& .MuiInputBase-input': {fontSize: '11px', py: 0.75, textAlign: 'right'}}} + InputProps={{endAdornment: '€'}} + /> + + + + + + + Nachnennungsgebühr: + + setNachnennungsgebuehr(e.target.value)} + sx={{width: 120, '& .MuiInputBase-input': {fontSize: '11px', py: 0.75, textAlign: 'right'}}} + InputProps={{endAdornment: '€'}} + /> + + (Nach Nennschluss) + + + + + + Nennungstausch-Gebühr: + + setNennungstauschgebuehr(e.target.value)} + sx={{width: 120, '& .MuiInputBase-input': {fontSize: '11px', py: 0.75, textAlign: 'right'}}} + InputProps={{endAdornment: '€'}} + /> + + (Pferd- oder Reiter-Wechsel) + + + + + + {/* Stallungen & Boxen */} + + + Stallungen & Boxen + + + + + + Box pro Tag: + + setBoxenProTag(e.target.value)} + sx={{width: 120, '& .MuiInputBase-input': {fontSize: '11px', py: 0.75, textAlign: 'right'}}} + InputProps={{endAdornment: '€'}} + /> + + + + + Einstreu (Erst-Einstreu): + + setEinstreuErst(e.target.value)} + sx={{width: 120, '& .MuiInputBase-input': {fontSize: '11px', py: 0.75, textAlign: 'right'}}} + InputProps={{endAdornment: '€'}} + /> + + + + + Einstreu (Nachlegen): + + setEinstreuNach(e.target.value)} + sx={{width: 120, '& .MuiInputBase-input': {fontSize: '11px', py: 0.75, textAlign: 'right'}}} + InputProps={{endAdornment: '€'}} + /> + + + + + Paddock pro Tag: + + setPaddockProTag(e.target.value)} + sx={{width: 120, '& .MuiInputBase-input': {fontSize: '11px', py: 0.75, textAlign: 'right'}}} + InputProps={{endAdornment: '€'}} + /> + + + + + {/* Zusatzgebühren */} + + + + Zusatzgebühren + + + + + + + + + Bezeichnung + Betrag + Pflicht + + + + + {zusatzgebuehren.map((gebuehr) => ( + + + handleZusatzgebuehrAendern(gebuehr.id, 'bezeichnung', e.target.value)} + placeholder="z.B. Stromanschluss" + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.5}}} + /> + + + handleZusatzgebuehrAendern(gebuehr.id, 'betrag', e.target.value)} + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.5, textAlign: 'right'}}} + InputProps={{endAdornment: '€'}} + /> + + + handleZusatzgebuehrAendern(gebuehr.id, 'pflicht', e.target.checked)} + /> + } + label={Pflicht} + /> + + + handleZusatzgebuehrLoeschen(gebuehr.id)} + > + + + + + ))} + +
+
+ + {zusatzgebuehren.length === 0 && ( + + + Keine Zusatzgebühren definiert + + + )} +
+ + {/* Hinweis */} + + + ℹ️ Hinweis zur Preisliste + + + Die Gebührenstruktur wird in der offiziellen Ausschreibung veröffentlicht und ist für alle Teilnehmer + verbindlich. Bei nationalen Turnieren der Kategorie C-Neu sind oft reduzierte Gebühren oder + Gebührenbefreiungen + üblich (z.B. kein Nenngeld, kein Sporteuro). + + + + {/* Action Buttons */} + + + + +
+
+ ); +} diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/StammdatenTab.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/StammdatenTab.tsx new file mode 100644 index 00000000..79c52070 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/StammdatenTab.tsx @@ -0,0 +1,831 @@ +import {useState} from 'react'; +import Box from '@mui/material/Box'; +import TextField from '@mui/material/TextField'; +import Button from '@mui/material/Button'; +import Radio from '@mui/material/Radio'; +import RadioGroup from '@mui/material/RadioGroup'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import FormControl from '@mui/material/FormControl'; +import Checkbox from '@mui/material/Checkbox'; +import FormGroup from '@mui/material/FormGroup'; +import Typography from '@mui/material/Typography'; +import Paper from '@mui/material/Paper'; +import MenuItem from '@mui/material/MenuItem'; +import Select from '@mui/material/Select'; +import Autocomplete from '@mui/material/Autocomplete'; +import Dialog from '@mui/material/Dialog'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import DialogActions from '@mui/material/DialogActions'; +import {DatePicker} from '@mui/x-date-pickers/DatePicker'; +import {LocalizationProvider} from '@mui/x-date-pickers/LocalizationProvider'; +import {AdapterDateFns} from '@mui/x-date-pickers/AdapterDateFns'; +import {de} from 'date-fns/locale'; +import FolderOpenIcon from '@mui/icons-material/FolderOpen'; + +// Kategorien basierend auf Screenshot +const kategorienDressur = [ + 'CDN-A', 'CDN-A*', 'CDN-B', 'CDN-B*', 'CDN-C', 'CDN-C-Neu', 'CDNP-B', 'CDNP-C', 'CDNP-C-Neu' +]; + +const kategorienSpringen = [ + 'CSN-A', 'CSN-A*', 'CSN-B', 'CSN-B*', 'CSN-C', 'CSN-C-Neu', 'CSNP-A', 'CSNP-B', 'CSNP-C', 'CSNP-C-Neu' +]; + +// Mock-Daten für Vereine (später vom Backend) +const mockVereine = [ + 'RFV Neumarkt/Hausruck', + 'Reitclub Wien', + 'Reitverein Salzburg', + 'Pferdesportverband OÖ', + 'RC Linz', +]; + +interface StammdatenTabProps { + turnierId?: string; +} + +export function StammdatenTab({turnierId}: StammdatenTabProps) { + const [turniernummer, setTurniernummer] = useState(''); + const [turniernummerError, setTurniernummerError] = useState(''); + const [turnierTitel, setTurnierTitel] = useState(''); + const [kommentar, setKommentar] = useState(''); + const [typ, setTyp] = useState('national'); + const [sprache, setSprache] = useState('deutsch'); + + // Sparten (kombinierbar) + const [sparteDressur, setSparteDressur] = useState(false); + const [sparteSpringen, setSparteSpringen] = useState(false); + + // Klassen (kombinierbar!) + const [klasseC, setKlasseC] = useState(false); + const [klasseB, setKlasseB] = useState(false); + const [klasseA, setKlasseA] = useState(false); + + // Kategorien (Mehrfachauswahl!) + const [selectedKategorien, setSelectedKategorien] = useState([]); + + const [datumVon, setDatumVon] = useState(null); + const [datumBis, setDatumBis] = useState(null); + const [verein, setVerein] = useState(null); + const [logo, setLogo] = useState(''); + + // Vorschau nach Speichern + const [showVorschau, setShowVorschau] = useState(false); + + // Bestätigungsdialog für Initialisierung + const [showInitDialog, setShowInitDialog] = useState(false); + + // Initialisierungs-Status: Turnier ist initialisiert, wenn eine Turnier-Nr. vorhanden ist + const istNeu = turnierId === 'neu'; + const [turniernummerBestaetigt, setTurniernummerBestaetigt] = useState(false); + const istInitialisiert = !istNeu || turniernummerBestaetigt; + + // Turniernummer validieren + const validateTurniernummer = (value: string) => { + if (value.length === 0) { + setTurniernummerError(''); + return; + } + if (!/^\d+$/.test(value)) { + setTurniernummerError('Nur Zahlen erlaubt'); + return; + } + if (value.length !== 5) { + setTurniernummerError('Muss 5-stellig sein'); + return; + } + setTurniernummerError(''); + }; + + // Verfügbare Kategorien basierend auf Sparte UND Klasse + const verfuegbareKategorien = (() => { + const kategorien: string[] = []; + + // Sparte bestimmt die Basis-Kategorien + const basisKategorien: string[] = []; + if (sparteDressur) basisKategorien.push(...kategorienDressur); + if (sparteSpringen) basisKategorien.push(...kategorienSpringen); + + // Filter nach Klassen (C, B, A) + const selectedKlassen: string[] = []; + if (klasseC) selectedKlassen.push('C', 'C-Neu'); + if (klasseB) selectedKlassen.push('B', 'B*'); + if (klasseA) selectedKlassen.push('A', 'A*'); + + if (selectedKlassen.length > 0 && basisKategorien.length > 0) { + return basisKategorien.filter(kat => { + // Extrahiere die Klasse aus der Kategorie (z.B. "CSN-C-Neu" -> "C-Neu") + const match = kat.match(/-(C-Neu|C|B\*|B|A\*|A)$/i); + if (match) { + const katKlasse = match[1].toUpperCase(); + return selectedKlassen.some(k => k.toUpperCase() === katKlasse); + } + return false; + }); + } + + return []; + })(); + + const handleKategorieToggle = (kategorie: string) => { + if (!istInitialisiert) return; + setSelectedKategorien(prev => + prev.includes(kategorie) + ? prev.filter(k => k !== kategorie) + : [...prev, kategorie] + ); + }; + + const handleInitialisieren = () => { + if (turniernummer.trim().length !== 5 || turniernummerError) return; + console.log('Turnier initialisieren mit Nr.:', turniernummer); + // TODO: Backend-Call zur Datenbank-Initialisierung + setTurniernummerBestaetigt(true); + }; + + const handleZuruecksetzen = () => { + setTurniernummer(''); + setTurniernummerError(''); + setTurnierTitel(''); + setKommentar(''); + setTyp('national'); + setSprache('deutsch'); + setSparteDressur(false); + setSparteSpringen(false); + setKlasseC(false); + setKlasseB(false); + setKlasseA(false); + setSelectedKategorien([]); + setDatumVon(null); + setDatumBis(null); + setVerein(null); + setLogo(''); + setShowVorschau(false); + }; + + const handleSpeichern = () => { + console.log('Turnier speichern:', { + turniernummer, + turnierTitel, + kommentar, + typ, + sprache, + sparteDressur, + sparteSpringen, + klasseC, + klasseB, + klasseA, + selectedKategorien, + datumVon, + datumBis, + verein, + logo, + }); + // TODO: Backend Integration + setShowVorschau(true); + }; + + return ( + + + {/* Vorschau nach Speichern (oben zentral) */} + {showVorschau && ( + + + + ✅ Turnier gespeichert + + + + + + {/* Turniernummer Badge */} + + + {turniernummer} + + + {selectedKategorien.slice(0, 5).map((kat, idx) => ( + + {kat} + + ))} + {selectedKategorien.length > 5 && ( + + +{selectedKategorien.length - 5} + + )} + + + + {/* Turnier-Titel */} + + {turnierTitel || 'Frühjahrs-Turnier 2026'} + + + {/* Sparten & Klassen */} + + {sparteDressur && ( + + 🏇 Dressur + + )} + {sparteSpringen && ( + + 🐴 Springen + + )} + {klasseC && ( + + Klasse C + + )} + {klasseB && ( + + Klasse B + + )} + {klasseA && ( + + Klasse A + + )} + + + {/* Details */} + + {(datumVon || datumBis) && ( + + 📅 {datumVon?.toLocaleDateString('de-DE') || '...'} - {datumBis?.toLocaleDateString('de-DE') || '...'} + + )} + {verein && ( + + 🏛️ {verein} + + )} + + + {/* Kommentar */} + {kommentar && ( + + {kommentar} + + )} + + + )} + + {/* Formular (volle Breite) */} + + {/* Hinweis für neue Veranstaltung */} + {istNeu && !istInitialisiert && ( + + + 🔑 Turnier-Nummer erforderlich + + + Bitte geben Sie zuerst eine 5-stellige Turnier-Nummer ein und klicken Sie auf + "Initialisieren". + Diese eindeutige Nummer wird vom ÖPSS vergeben und dient als Schlüssel für die + Datenbank-Initialisierung. + + + )} + + {/* Turniernummer mit Initialisieren-Button */} + + + Turnier-Nr.: {istNeu && !istInitialisiert && *} + + + { + const value = e.target.value; + // Nur Zahlen erlauben, maximal 5 Stellen + if (value === '' || (/^\d+$/.test(value) && value.length <= 5)) { + setTurniernummer(value); + validateTurniernummer(value); + } + }} + placeholder="z.B. 26128" + autoFocus={istNeu} + disabled={istInitialisiert && istNeu} + error={!!turniernummerError} + helperText={turniernummerError} + sx={{ + width: 150, + '& .MuiInputBase-input': { + fontSize: '11px', + py: 0.75, + fontWeight: istNeu && !istInitialisiert ? 600 : 400, + }, + '& .MuiOutlinedInput-root': { + bgcolor: istNeu && !istInitialisiert ? 'info.50' : 'background.paper' + } + }} + /> + {istNeu && !istInitialisiert && ( + + )} + + + + {/* Typ */} + + + Typ: + + + setTyp(e.target.value)} + > + } + label={National} + /> + } + label={International} + disabled + /> + + + + (kommt später) + + + + {/* Sprache */} + + + Sprache: + + + setSprache(e.target.value)} + > + } + label={Deutsch} + /> + } + label={English} + disabled + /> + + + + (kommt später) + + + + {/* Sparten */} + + + Sparten: + + + { + setSparteDressur(e.target.checked); + setSelectedKategorien([]); + }} + disabled={!istInitialisiert} + /> + } + label={Dressur} + /> + { + setSparteSpringen(e.target.checked); + setSelectedKategorien([]); + }} + disabled={!istInitialisiert} + /> + } + label={Springen} + /> + + + + {/* Klassen */} + + + Klassen: + + + { + setKlasseC(e.target.checked); + setSelectedKategorien([]); + }} + disabled={!istInitialisiert} + /> + } + label={C} + /> + { + setKlasseB(e.target.checked); + setSelectedKategorien([]); + }} + disabled={!istInitialisiert} + /> + } + label={B} + /> + { + setKlasseA(e.target.checked); + setSelectedKategorien([]); + }} + disabled={!istInitialisiert} + /> + } + label={A} + /> + + + + {/* Kategorien (Mehrfachauswahl) */} + + + Kategorien: + + + {verfuegbareKategorien.length > 0 ? ( + + {verfuegbareKategorien.map((kategorie) => ( + handleKategorieToggle(kategorie)} + disabled={!istInitialisiert} + /> + } + label={{kategorie}} + sx={{mb: 0.25}} + /> + ))} + + ) : ( + + {!sparteDressur && !sparteSpringen + ? 'Bitte Sparte(n) auswählen' + : !klasseC && !klasseB && !klasseA + ? 'Bitte Klasse(n) auswählen' + : 'Keine Kategorien verfügbar'} + + )} + + + + {/* Datum von und bis in einer Zeile */} + + + Datum: + + + setDatumVon(newValue)} + disabled={!istInitialisiert} + slotProps={{ + textField: { + size: 'small', + placeholder: 'von', + sx: {width: 160, '& .MuiInputBase-input': {fontSize: '11px', py: 0.75}} + } + }} + /> + bis + setDatumBis(newValue)} + disabled={!istInitialisiert} + minDate={datumVon || undefined} + slotProps={{ + textField: { + size: 'small', + placeholder: 'bis', + sx: {width: 160, '& .MuiInputBase-input': {fontSize: '11px', py: 0.75}} + } + }} + /> + + + + {/* Verein */} + + + Verein: + + setVerein(newValue)} + disabled={!istInitialisiert} + options={mockVereine} + renderInput={(params) => ( + + )} + /> + + + {/* Logo */} + + + Logo: + + setLogo(e.target.value)} + disabled={!istInitialisiert} + placeholder="Logo-Datei auswählen..." + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + /> + + + + {/* Turnier-Titel */} + + + Turnier-Titel: + + setTurnierTitel(e.target.value)} + disabled={!istInitialisiert} + placeholder="Frühjahrs-Turnier 2026" + sx={{'& .MuiInputBase-input': {fontSize: '11px', py: 0.75}}} + /> + + + {/* Kommentar */} + + + Kommentar: + + setKommentar(e.target.value)} + disabled={!istInitialisiert} + placeholder="z.B. KIDS CUP • PONY EINSTEIGER CUP OÖ" + sx={{'& .MuiInputBase-input': {fontSize: '11px'}}} + /> + + + {/* Action Buttons */} + + + + + + + {/* Bestätigungsdialog für Initialisierung */} + setShowInitDialog(false)} + maxWidth="sm" + fullWidth + > + + Turnier-Nummer bestätigen + + + + + ⚠️ Wichtig + + + Die Turnier-Nummer kann nach der Initialisierung nicht mehr geändert werden. + + + Bitte überprüfen Sie die eingegebene Nummer sorgfältig. + + + + + + Turnier-Nr.: + + + {turniernummer} + + + + + Ist diese Turnier-Nummer korrekt? + + + + + + + + + + ); +} diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/TransferTab.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/TransferTab.tsx new file mode 100644 index 00000000..eb751831 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/TransferTab.tsx @@ -0,0 +1,325 @@ +import {useState} from 'react'; +import {useParams} from 'react-router'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import Paper from '@mui/material/Paper'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import Chip from '@mui/material/Chip'; +import IconButton from '@mui/material/IconButton'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import Divider from '@mui/material/Divider'; +import SaveIcon from '@mui/icons-material/Save'; +import FolderOpenIcon from '@mui/icons-material/FolderOpen'; +import AddIcon from '@mui/icons-material/Add'; +import UploadIcon from '@mui/icons-material/Upload'; +import DownloadIcon from '@mui/icons-material/Download'; +import UsbIcon from '@mui/icons-material/Usb'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import CloudUploadIcon from '@mui/icons-material/CloudUpload'; +import CloudDownloadIcon from '@mui/icons-material/CloudDownload'; +import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; +import CalendarTodayIcon from '@mui/icons-material/CalendarToday'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import WarningIcon from '@mui/icons-material/Warning'; +import {veranstaltungenData} from '../Dashboard'; + +export function TransferTab() { + const {id} = useParams(); + const [anchorEl, setAnchorEl] = useState(null); + const [selectedTurnierId, setSelectedTurnierId] = useState(null); + + // Veranstaltung laden + const veranstaltung = id !== 'neu' + ? veranstaltungenData.find(v => v.id === parseInt(id || '0')) + : null; + + const handleMenuOpen = (event: React.MouseEvent, turnierId: string) => { + setAnchorEl(event.currentTarget); + setSelectedTurnierId(turnierId); + }; + + const handleMenuClose = () => { + setAnchorEl(null); + setSelectedTurnierId(null); + }; + + const handleNeuesTurnier = () => { + console.log('Neues Turnier erstellen für Veranstaltung:', id); + // TODO: Dialog öffnen + }; + + const handleImportZNS = (turnierId: string) => { + console.log('Import ZNS N2-Daten für Turnier:', turnierId); + handleMenuClose(); + }; + + const handleExportZNS = (turnierId: string) => { + console.log('Export ZNS für Turnier:', turnierId); + handleMenuClose(); + }; + + const handleImportUSB = (turnierId: string) => { + console.log('Import von USB für Turnier:', turnierId); + handleMenuClose(); + }; + + const handleExportUSB = (turnierId: string) => { + console.log('Export auf USB für Turnier:', turnierId); + handleMenuClose(); + }; + + const handleImportLokal = (turnierId: string) => { + console.log('Import von lokaler Datei für Turnier:', turnierId); + handleMenuClose(); + }; + + const handleExportLokal = (turnierId: string) => { + console.log('Export als lokale Datei für Turnier:', turnierId); + handleMenuClose(); + }; + + if (!veranstaltung) { + return ( + + + Veranstaltung nicht gefunden + + + ); + } + + return ( + + + {/* Veranstaltungs-Info oben */} + + + + + {veranstaltung.name} + + + + 📍 {veranstaltung.ort} + + + 📅 {veranstaltung.datum} + + + 🏆 {veranstaltung.turniere.length} Turniere + + + + + + + + {/* Button: Neues Turnier */} + + + + + {/* Turniere dieser Veranstaltung */} + + Turniere dieser Veranstaltung + + + + {veranstaltung.turniere.map((turnier) => ( + + + + + + + + {turnier.name} + + + + + + + {turnier.datum} + + + + {turnier.disziplin} + + + {turnier.bewerbeAnzahl} Bewerbe + + + {(turnier.kategorie === 'B' || turnier.kategorie === 'A') && ( + turnier.znsStatus === 'geladen' ? ( + <> + + + ZNS N2-Daten geladen + + + ) : ( + <> + + + ZNS N2-Daten ausstehend + + + ) + )} + + + + + handleMenuOpen(e, turnier.nr)} + > + + + + + {/* Actions für dieses Turnier */} + + + + {(turnier.kategorie === 'B' || turnier.kategorie === 'A') && ( + <> + + + + )} + + + + + + + + ))} + + + {veranstaltung.turniere.length === 0 && ( + + + Noch keine Turniere für diese Veranstaltung angelegt + + + + )} + + {/* Context Menu */} + + selectedTurnierId && handleImportLokal(selectedTurnierId)} sx={{fontSize: '10px'}}> + + Import von lokaler Datei + + selectedTurnierId && handleExportLokal(selectedTurnierId)} sx={{fontSize: '10px'}}> + + Export als lokale Datei + + + selectedTurnierId && handleImportUSB(selectedTurnierId)} sx={{fontSize: '10px'}}> + + Import von USB-Stick + + selectedTurnierId && handleExportUSB(selectedTurnierId)} sx={{fontSize: '10px'}}> + + Export auf USB-Stick + + {selectedTurnierId && veranstaltung.turniere.find(t => t.nr === selectedTurnierId)?.kategorie !== 'C' && ( + <> + + selectedTurnierId && handleImportZNS(selectedTurnierId)} sx={{fontSize: '10px'}}> + + ZNS N2-Daten importieren + + selectedTurnierId && handleExportZNS(selectedTurnierId)} sx={{fontSize: '10px'}}> + + ZNS Ergebnisse exportieren + + + )} + + + + ); +} diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/VeranstaltungUebersicht.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/VeranstaltungUebersicht.tsx new file mode 100644 index 00000000..5530439d --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/turnier/VeranstaltungUebersicht.tsx @@ -0,0 +1,347 @@ +import {useState} from 'react'; +import {useParams, useNavigate} from 'react-router'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import Paper from '@mui/material/Paper'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import Chip from '@mui/material/Chip'; +import IconButton from '@mui/material/IconButton'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import Divider from '@mui/material/Divider'; +import SaveIcon from '@mui/icons-material/Save'; +import FolderOpenIcon from '@mui/icons-material/FolderOpen'; +import AddIcon from '@mui/icons-material/Add'; +import UploadIcon from '@mui/icons-material/Upload'; +import DownloadIcon from '@mui/icons-material/Download'; +import UsbIcon from '@mui/icons-material/Usb'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import CloudUploadIcon from '@mui/icons-material/CloudUpload'; +import CloudDownloadIcon from '@mui/icons-material/CloudDownload'; +import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; +import CalendarTodayIcon from '@mui/icons-material/CalendarToday'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import WarningIcon from '@mui/icons-material/Warning'; +import {veranstaltungenData} from '../Dashboard'; + +export function VeranstaltungUebersicht() { + const params = useParams(); + const id = params.id; + const [anchorEl, setAnchorEl] = useState(null); + const [selectedTurnierId, setSelectedTurnierId] = useState(null); + const navigate = useNavigate(); + + // Veranstaltung laden + const veranstaltung = id !== 'neu' + ? veranstaltungenData.find(v => v.id === parseInt(id || '0')) + : null; + + // Wenn neu, zeige eine leere Ansicht für neue Veranstaltung + if (id === 'neu') { + return ( + + + + + 🆕 Neue Veranstaltung erstellen + + + Bitte wechseln Sie zu den Tabs "Stammdaten", "Organisation", "Bewerbe" oder "Preisliste", um die + Veranstaltung zu konfigurieren. + + + + + ); + } + + if (!veranstaltung) { + return ( + + + Veranstaltung nicht gefunden + + + ); + } + + const handleMenuOpen = (event: React.MouseEvent, turnierId: string) => { + setAnchorEl(event.currentTarget); + setSelectedTurnierId(turnierId); + }; + + const handleMenuClose = () => { + setAnchorEl(null); + setSelectedTurnierId(null); + }; + + const handleNeuesTurnier = () => { + console.log('Neues Turnier erstellen für Veranstaltung:', id); + navigate(`/veranstaltung/${id}/turnier/neu`); + }; + + const handleImportZNS = (turnierId: string) => { + console.log('Import ZNS N2-Daten für Turnier:', turnierId); + handleMenuClose(); + }; + + const handleExportZNS = (turnierId: string) => { + console.log('Export ZNS für Turnier:', turnierId); + handleMenuClose(); + }; + + const handleImportUSB = (turnierId: string) => { + console.log('Import von USB für Turnier:', turnierId); + handleMenuClose(); + }; + + const handleExportUSB = (turnierId: string) => { + console.log('Export auf USB für Turnier:', turnierId); + handleMenuClose(); + }; + + const handleImportLokal = (turnierId: string) => { + console.log('Import von lokaler Datei für Turnier:', turnierId); + handleMenuClose(); + }; + + const handleExportLokal = (turnierId: string) => { + console.log('Export als lokale Datei für Turnier:', turnierId); + handleMenuClose(); + }; + + return ( + + + {/* Veranstaltungs-Info oben */} + + + + + {veranstaltung.name} + + + + 📍 {veranstaltung.ort} + + + 📅 {veranstaltung.datum} + + + 🏆 {veranstaltung.turniere.length} Turniere + + + + + + + + {/* Button: Neues Turnier */} + + + + + {/* Turniere dieser Veranstaltung */} + + Turniere dieser Veranstaltung + + + + {veranstaltung.turniere.map((turnier) => ( + + + + + + + + {turnier.name} + + + + + + + {turnier.datum} + + + + {turnier.disziplin} + + + {turnier.bewerbeAnzahl} Bewerbe + + + {(turnier.kategorie === 'B' || turnier.kategorie === 'A') && ( + turnier.znsStatus === 'geladen' ? ( + <> + + + ZNS N2-Daten geladen + + + ) : ( + <> + + + ZNS N2-Daten ausstehend + + + ) + )} + + + + + handleMenuOpen(e, turnier.nr)} + > + + + + + {/* Actions für dieses Turnier */} + + + + {(turnier.kategorie === 'B' || turnier.kategorie === 'A') && ( + <> + + + + )} + + + + + + + + ))} + + + {veranstaltung.turniere.length === 0 && ( + + + Noch keine Turniere für diese Veranstaltung angelegt + + + + )} + + {/* Context Menu */} + + selectedTurnierId && handleImportLokal(selectedTurnierId)} sx={{fontSize: '10px'}}> + + Import von lokaler Datei + + selectedTurnierId && handleExportLokal(selectedTurnierId)} sx={{fontSize: '10px'}}> + + Export als lokale Datei + + + selectedTurnierId && handleImportUSB(selectedTurnierId)} sx={{fontSize: '10px'}}> + + Import von USB-Stick + + selectedTurnierId && handleExportUSB(selectedTurnierId)} sx={{fontSize: '10px'}}> + + Export auf USB-Stick + + {selectedTurnierId && veranstaltung.turniere.find(t => t.nr === selectedTurnierId)?.kategorie !== 'C' && ( + <> + + selectedTurnierId && handleImportZNS(selectedTurnierId)} sx={{fontSize: '10px'}}> + + ZNS N2-Daten importieren + + selectedTurnierId && handleExportZNS(selectedTurnierId)} sx={{fontSize: '10px'}}> + + ZNS Ergebnisse exportieren + + + )} + + + + ); +} diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/accordion.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/accordion.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/accordion.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/accordion.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/alert-dialog.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/alert-dialog.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/alert-dialog.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/alert-dialog.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/alert.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/alert.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/alert.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/alert.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/aspect-ratio.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/aspect-ratio.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/aspect-ratio.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/aspect-ratio.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/avatar.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/avatar.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/avatar.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/avatar.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/badge.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/badge.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/badge.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/badge.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/breadcrumb.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/breadcrumb.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/breadcrumb.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/breadcrumb.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/button.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/button.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/button.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/button.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/calendar.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/calendar.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/calendar.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/calendar.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/card.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/card.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/card.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/card.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/carousel.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/carousel.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/carousel.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/carousel.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/chart.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/chart.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/chart.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/chart.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/checkbox.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/checkbox.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/checkbox.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/checkbox.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/collapsible.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/collapsible.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/collapsible.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/collapsible.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/command.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/command.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/command.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/command.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/context-menu.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/context-menu.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/context-menu.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/context-menu.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/dialog.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/dialog.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/dialog.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/dialog.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/drawer.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/drawer.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/drawer.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/drawer.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/dropdown-menu.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/dropdown-menu.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/dropdown-menu.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/dropdown-menu.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/form.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/form.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/form.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/form.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/hover-card.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/hover-card.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/hover-card.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/hover-card.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/input-otp.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/input-otp.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/input-otp.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/input-otp.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/input.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/input.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/input.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/input.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/label.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/label.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/label.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/label.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/menubar.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/menubar.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/menubar.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/menubar.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/navigation-menu.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/navigation-menu.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/navigation-menu.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/navigation-menu.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/pagination.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/pagination.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/pagination.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/pagination.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/popover.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/popover.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/popover.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/popover.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/progress.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/progress.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/progress.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/progress.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/radio-group.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/radio-group.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/radio-group.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/radio-group.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/resizable.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/resizable.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/resizable.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/resizable.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/scroll-area.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/scroll-area.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/scroll-area.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/scroll-area.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/select.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/select.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/select.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/select.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/separator.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/separator.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/separator.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/separator.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/sheet.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/sheet.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/sheet.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/sheet.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/sidebar.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/sidebar.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/sidebar.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/sidebar.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/skeleton.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/skeleton.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/skeleton.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/skeleton.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/slider.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/slider.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/slider.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/slider.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/sonner.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/sonner.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/sonner.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/sonner.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/switch.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/switch.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/switch.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/switch.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/table.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/table.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/table.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/table.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/tabs.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/tabs.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/tabs.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/tabs.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/textarea.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/textarea.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/textarea.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/textarea.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/toggle-group.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/toggle-group.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/toggle-group.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/toggle-group.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/toggle.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/toggle.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/toggle.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/toggle.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/tooltip.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/tooltip.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/tooltip.tsx rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/tooltip.tsx diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/use-mobile.ts b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/use-mobile.ts similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/use-mobile.ts rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/use-mobile.ts diff --git a/docs/06_Frontend/FIGMA/src/app/components/ui/utils.ts b/docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/utils.ts similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/ui/utils.ts rename to docs/06_Frontend/FIGMA/Vision_02/src/app/components/ui/utils.ts diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/app/routes.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/routes.tsx new file mode 100644 index 00000000..73573a35 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/app/routes.tsx @@ -0,0 +1,28 @@ +import {createBrowserRouter} from 'react-router'; +import {Login} from './components/Login'; +import {AdminVerwaltung} from './components/Dashboard'; +import {TurnierErstellen} from './components/TurnierErstellen'; +import {TurnierAnsicht} from './components/TurnierAnsicht'; + +export const router = createBrowserRouter([ + { + path: '/', + Component: Login, + }, + { + path: '/admin', + Component: AdminVerwaltung, + }, + { + path: '/veranstaltung/:id', + Component: TurnierErstellen, + }, + { + path: '/veranstaltung/:veranstaltungId/turnier/neu', + Component: TurnierAnsicht, + }, + { + path: '/veranstaltung/:veranstaltungId/turnier/:nr', + Component: TurnierAnsicht, + }, +]); diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/app/theme.tsx b/docs/06_Frontend/FIGMA/Vision_02/src/app/theme.tsx new file mode 100644 index 00000000..32b46185 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/app/theme.tsx @@ -0,0 +1,56 @@ +import {createTheme} from '@mui/material/styles'; + +export const theme = createTheme({ + palette: { + primary: { + main: '#3F51B5', // Indigo + }, + secondary: { + main: '#FF4081', + }, + background: { + default: '#fafafa', + paper: '#ffffff', + }, + }, + typography: { + fontSize: 11, + body1: { + fontSize: '11px', + }, + body2: { + fontSize: '11px', + }, + }, + components: { + MuiButton: { + styleOverrides: { + root: { + textTransform: 'none', + fontSize: '11px', + }, + sizeSmall: { + padding: '4px 12px', + fontSize: '10px', + }, + }, + }, + MuiTextField: { + styleOverrides: { + root: { + '& .MuiInputBase-input': { + fontSize: '11px', + }, + }, + }, + }, + MuiTableCell: { + styleOverrides: { + root: { + fontSize: '11px', + padding: '6px 8px', + }, + }, + }, + }, +}); diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/imports/26128.md b/docs/06_Frontend/FIGMA/Vision_02/src/imports/26128.md new file mode 100644 index 00000000..f46f0e94 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/imports/26128.md @@ -0,0 +1,71 @@ +# CSN-C NEU / CSNP-C NEU NEUMARKT/M. + +**Turnier-Nr.: 26128** | [cite_start]**Datum: 25. April 2026** [cite: 1, 2] + +## Allgemeine Informationen + +* **Veranstalter:** Union Reit- u. Fahrverein Neumarkt/M. (6-009) [cite_start][cite: 3] +* [cite_start]**Ort:** Reitanlage Stroblmair, 4212 Neumarkt [cite: 3] +* [cite_start]**Kontakt:** Ursula Stroblmair, Brandstetterweg 2, 4212 Neumarkt [cite: 4] + * **Tel.:** 0664 1832381 + * [cite_start]**E-Mail:** reit-stall@gmx.at [cite: 4] +* [cite_start]**Nennungsschluss:** 24.04.2026, 19:00 Uhr [cite: 4] +* [cite_start]**Online-Nennung:** Ab Mittwoch, 22.04. + auf [www.ihremeldestelle.at](http://www.ihremeldestelle.at) [cite: 5] +* [cite_start]**Meldestelle:** Geöffnet ab 24.04., 17:00 Uhr (Tel: +43 681 10769120) [cite: 8] + +## Technische Details + +* [cite_start]**Austragungsplatz:** 45 x 65 m (Sand/Vlies) [cite: 6] +* [cite_start]**Vorbereitungsplatz:** 20 x 40 m Halle (Sand/Vlies) [cite: 6] +* [cite_start]**Warmreiten:** Draußen (20 x 60 m Sand/Vlies) möglich [cite: 16] +* [cite_start]**Boxen:** Keine Einstallung möglich [cite: 9] + +## Funktionäre + +* [cite_start]**Turnierleiter:** Ursula Stroblmair [cite: 6] +* [cite_start]**Turnierbeauftragter:** Rudi Kreupl [cite: 7] +* [cite_start]**Richter:** Rudi Kreupl, Helmut Riedler [cite: 7] +* [cite_start]**Parcoursbauchef:** Kurt Reitetschlägerr [cite: 8] +* [cite_start]**Tierarzt:** Dr. Sabine Ötschmaier [cite: 8] + +--- + +## Besondere Bestimmungen + +* **Kosten:** Startgeld € 15,- pro Bewerb. [cite_start]Kein Nenngeld, kein Sporteuro. [cite: 11] +* **Teilnahmebedingungen:** + * [cite_start]Für Springprüfungen bis 95 cm: Mitgliedschaft OEPS-Verein und Reiterpass erforderlich. [cite: 12] + * [cite_start]Pferde bis 90 cm müssen **nicht** beim OEPS registriert sein. [cite: 14] + * [cite_start]Pferdepass mit gültigem Impfschutz (§ 11 OTO) ist vorzulegen. [cite: 15] + * [cite_start]Haftpflichtversicherung für jedes Pferd ist Pflicht. [cite: 21] +* **Startregelung:** + * [cite_start]Ein Pferd darf maximal 3x pro Tag starten. [cite: 14] + * [cite_start]In Bewerben bis 95 cm darf ein Pferd mit zwei verschiedenen Reitern starten. [cite: 13] +* [cite_start]**Hunde:** Am gesamten Gelände herrscht Leinenpflicht. [cite: 18] + +--- + +## Bewerbe (Samstag, 25. April 2026 - Beginn 08:00 Uhr) + +| Nr. | Bewerb | Höhe | Richtverfahren / Abteilungen | +|:-------|:--------------------------------|:-------|:------------------------------------------------------------------------------------| +| **1** | Pony Stilspringprüfung | 60 cm | [cite_start]RV: § 204/4 (CSNP-C) [cite: 27] | +| **2** | Einlaufspringprüfung | 60 cm | [cite_start]RV: § 204/4 (1. Abt: lizenzfrei / 2. Abt: mit Lizenz) [cite: 27] | +| **3** | Pony Stilspringprüfung | 70 cm | [cite_start]RV: § 204/4 (CSNP-C) [cite: 27] | +| **4** | Einlaufspringprüfung | 70 cm | [cite_start]RV: § 218 (1. Abt: lizenzfrei / 2. Abt: mit Lizenz) [cite: 27] | +| **5** | Pony Stilspringprüfung | 80 cm | [cite_start]RV: § 204/4 (CSNP-C) [cite: 27] | +| **6** | Stilspringprüfung | 80 cm | [cite_start]RV: § 204/4 (1. Abt: lizenzfrei / 2. Abt: R1 & 5-6j. Pferde) [cite: 27] | +| **7** | Pony Stilspringprüfung | 95 cm | [cite_start]RV: § 204/4 (CSNP-C) [cite: 27] | +| **8** | Springreiterbewerb (lizenzfrei) | 95 cm | [cite_start]RV: § 204/4 (CSNP-C) [cite: 27] | +| **9** | Standardspringprüfung | 95 cm | [cite_start]RV: A2 (1. Abt: R1 / 2. Abt: R2 und höher) [cite: 27] | +| **10** | Springpferdeprüfung | 105 cm | [cite_start]RV: § 203/3 (1. Abt: 4-jährig / 2. Abt: 5-6-jährig) [cite: 27] | +| **11** | Stilspringprüfung | 105 cm | [cite_start]RV: § 204/4 (1. Abt: R1) [cite: 27] | +| **12** | Standardspringprüfung | 105 cm | [cite_start]RV: A2 (1. Abt: R1 / 2. Abt: R2/RS2 und höher) [cite: 27] | +| **13** | Stilspringprüfung | 115 cm | [cite_start]RV: § 204/4 (1. Abt: R1) [cite: 28, 30, 31] | +| **14** | Standardspringprüfung | 115 cm | [cite_start]RV: A2 (1. Abt: R1 / 2. Abt: R2/RS2 und höher) [cite: 32, 34, 36] | + +--- +**Haftung:** Der Veranstalter übernimmt keine Haftung. [cite_start]Teilnehmer haften persönlich für Schäden gegenüber +Dritten. [cite: 19, 20] + diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/imports/26129.md b/docs/06_Frontend/FIGMA/Vision_02/src/imports/26129.md new file mode 100644 index 00000000..7f9b58d6 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/imports/26129.md @@ -0,0 +1,70 @@ +# CDN-C NEU / CDNP-C NEU NEUMARKT/M., OÖ + +**Turnier-Nr.: 26129** | [cite_start]**Datum: 26. April 2026** [cite: 37] + +## Allgemeine Informationen + +* **Veranstalter**: Union Reit- u. Fahrverein Neumarkt/M. (6-009) [cite_start][cite: 38]. +* [cite_start]**Ort**: Reitanlage Stroblmair, 4212 Neumarkt[cite: 38]. +* [cite_start]**Kontaktadresse**: Ursula Stroblmair, Brandstetterweg 2, 4212 Neumarkt[cite: 39]. + * [cite_start]**Telefon**: 0664 1832381[cite: 39]. + * [cite_start]**E-Mail**: reit-stall@gmx.at[cite: 39]. +* [cite_start]**Nennungsschluss**: 25.04.2026, 19:00 Uhr[cite: 39, 53]. +* [cite_start]**Online-Nennung**: Ab Mittwoch, 22.04. auf www.ihremeldestelle.at möglich[cite: 40]. +* [cite_start]**Meldestelle**: Geöffnet ab 25.04., 17:00 Uhr (Tel: +43 681 10769120)[cite: 43]. +* [cite_start]**Start- und Ergebnislisten**: Ab 20:30 Uhr auf www.ihremeldestelle.at verfügbar[cite: 44]. + +## Technische Details und Gebühren + +* [cite_start]**Austragungsplatz**: 20 x 60 m Sand/Vlies[cite: 41]. +* [cite_start]**Vorbereitungsplatz**: 20 x 40 m Halle (Sand/Vlies) und 20 x 60 m (Sand/Vlies)[cite: 41]. +* [cite_start]**Boxen**: Keine Einstallung möglich[cite: 44]. +* [cite_start]**Kosten**: Startgeld € 15,- pro Bewerb; kein Nenngeld und kein Sporteuro[cite: 40, 47]. + +## Funktionäre + +* [cite_start]**Turnierleiter**: Ursula Stroblmair[cite: 41]. +* [cite_start]**Turnierbeauftragte**: Alexandra Schuster[cite: 42]. +* [cite_start]**Richter**: Alexandra Schuster, Ulrike Knasmüller-Prinz, Karin Wallner[cite: 42]. +* [cite_start]**Steward**: Barbara Hruschka[cite: 42]. +* [cite_start]**Tierarzt**: Dr. Sabine Ötschmaier[cite: 42]. + +--- + +## Besondere Bestimmungen + +* **Teilnahmevoraussetzungen**: + * [cite_start]Für Reiterpass-/Reiternadel-Aufgaben ist die Mitgliedschaft bei einem OEPS-Verein und der Besitz des + Reiterpasses erforderlich[cite: 48]. + * [cite_start]Pferde für Reiterpass-/Reiternadel-Aufgaben müssen nicht beim OEPS registriert sein[cite: 50]. +* **Pferde**: + * [cite_start]Ein Pferd darf pro Tag maximal 3x starten[cite: 49]. + * [cite_start]Ein Pferd darf mit zwei verschiedenen Reitern an den Start gehen[cite: 49]. + * [cite_start]Vorlage des Pferdepasses mit gültigem Impfschutz gemäß § 11 OTO ist Pflicht[cite: 51]. + * [cite_start]Jedes teilnehmende Pferd muss haftpflichtversichert sein[cite: 57]. +* [cite_start]**Haftung**: Der Veranstalter übernimmt keine Haftung jeder Art und Ursache[cite: 55]. [cite_start] + Teilnehmer und Besitzer haften persönlich für Schäden gegenüber Dritten[cite: 56]. +* [cite_start]**Sonstiges**: Es gilt Leinenpflicht für Hunde auf dem gesamten Gelände[cite: 54]. [cite_start] + Ausländische Equiden unterliegen der TRACES-Pflicht[cite: 58]. + +--- + +## Bewerbe (Sonntag, 26. April 2026 - Beginn 08:00 Uhr) + +| Nr. | Bewerb | Aufg. | Details / Abteilungen | +|:-------|:---------------------------------|:---------------|:------------------------------------------------------------------------| +| **1** | Dressurreiterprüfung Reiterpass | R1 | [cite_start]RV: A § 103/5 [cite: 63] | +| **2** | Dressurreiterprüfung Reiternadel | R4 | [cite_start]RV: A § 103/5 [cite: 64] | +| **3** | Dressurreiterprüfung lizenzfrei | LF1 | [cite_start]RV: A § 103/5 [cite: 68] | +| **4** | Dressurreiterprüfung lizenzfrei | LF3 | [cite_start]RV: A § 103/5 [cite: 69] | +| **5** | First Ridden | - [cite_start] | [cite: 71] | +| **6** | Führzügelklasse | - [cite_start] | [cite: 73] | +| **7** | Pony Dressurprüfung Kl. A | P1 | [cite_start]RV: A, § 901 [cite: 75, 76] | +| **8** | Dressurreiterprüfung Kl. A | DRA1 | 1. Abt: R1/RD1; 2. [cite_start]Abt: R2/RD2 u. höher [cite: 78, 79, 81] | +| **9** | Dressurprüfung Kl. A | A5 | 1. Abt: R1/RD1; 2. [cite_start]Abt: R2/RD2 u. höher [cite: 82, 83, 98] | +| **13** | Dressurpferdeprüfung Kl. A | DPA1 | 1. Abt: 4-jähr. Pferde; 2. Abt: 5-6-jähr. [cite_start]Pferde [cite: 85] | +| **14** | Dressurpferdprüfung Kl. L | DPL1 | Für 5-6-jähr. [cite_start]Pferde [cite: 87] | +| **10** | Pony Dressurprüfung Kl. L | P6 | [cite_start]RV: A, § 901 [cite: 89, 90] | +| **11** | Dressurreiterprüfung Kl. L | DRL1 | 1. Abt: R1/RD1; 2. [cite_start]Abt: R2/RD2 u. höher [cite: 89, 92, 97] | +| **12** | Dressurprüfung Kl. L | L3 | 1. Abt: R1/RD1; 2. [cite_start]Abt: R2/RD2 u. höher [cite: 94, 96] | + diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/imports/Detail-Bewerbe-Springen-Dressur.md b/docs/06_Frontend/FIGMA/Vision_02/src/imports/Detail-Bewerbe-Springen-Dressur.md new file mode 100644 index 00000000..74cb51ec --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/imports/Detail-Bewerbe-Springen-Dressur.md @@ -0,0 +1,128 @@ +# Detaillierte Bewerbs-Parameter: Springen und Dressur + +Dieses Dokument beschreibt die genauen Parameter, die in der Turnier-Ausschreibung für die einzelnen Bewerbe der Sparten +Springen (CSN) und Dressur (CDN) definiert werden müssen, basierend auf der aktuellen ÖTO. + +--- + +## 1. Verständnis eines Ausschreibungs-Beispiels + +Ein typischer Bewerb im Ausschreibungs-Text sieht oft so aus: +> **6 Stilspringprüfung 80 cm J RV: § 204/4 CSN-C Neu** +> **1. Abt. lizenzfrei 2.Abt. R1 und Reiter mit 5 & 6 jährigen Pferden** + +### Aufschlüsselung der Parameter: + +* **`6`**: **Bewerbsnummer**. Eine fortlaufende Nummer zur eindeutigen Identifikation des Bewerbs (Prüfung Nr. 6 des + Turniers). +* **`Stilspringprüfung`**: **Art der Prüfung**. Es geht hier nicht rein nach Fehlern und Zeit, sondern der Reiter wird + von Richtern mit einer Wertnote für seinen Sitz, seine Einwirkung und den Rhythmus beurteilt. +* **`80 cm`**: **Klasse / Maximale Hindernishöhe**. In diesem Fall entspricht dies der Einsteigerklasse (E bzw. E0). +* **`J`**: **Startbuchstabe**. Nach diesem Buchstaben wird die Startreihenfolge alphabetisch gelost (oft nach dem Namen + des Pferdes). Ist der Buchstabe "J", startet das Pferd "Jolly Jumper" als erstes, danach "Karino" usw., bis am Ende + das Pferd "Ikarus" startet. +* **`RV: § 204/4`**: **Richtverfahren**. Der direkte Verweis auf den entsprechenden Paragraphen der ÖTO. Hier bedeutet + dies: Stilspringprüfung mit Wertnoten von 0,0 bis 10,0. +* **`CSN-C Neu`**: **Turnierkategorie**. Nationales Springturnier, Kategorie C-Neu. Das ist eine Einsteiger-Kategorie, + bei der auch Reiter ohne Lizenz (nur mit Reiterpass) antreten dürfen. Es gibt hier kein Preisgeld, nur Sachpreise und + Schleifen. +* **`1. Abt. lizenzfrei`**: **Abteilung 1 (Unterteilung)**. Diese Abteilung (erste Siegerehrung/Platzierung) ist + exklusiv für Reiter, die noch keine Turnierlizenz haben. +* **`2. Abt. R1 und Reiter mit 5 & 6 jährigen Pferden`**: **Abteilung 2**. Die zweite Abteilung wertet alle Reiter mit + der Einstiegslizenz (R1) sowie erfahrene Reiter, wenn sie junge Nachwuchspferde reiten. So wird Fairness garantiert. + +--- + +## 2. Sparte Springen (CSN) im Detail + +### 2.1 Die Klassen (Höhen für Großpferde) + +Die Klassen geben die maximale Höhe der Hindernisse an: + +* **Klasse E0 (Einsteiger):** 60 bis 90 cm +* **Klasse A (Leicht):** 105 bis 110 cm +* **Klasse L (Mittelleicht):** 115 bis 120 cm +* **Klasse LM (Leicht-Mittelschwer):** 125 bis 130 cm +* **Klasse M (Mittelschwer):** 135 cm +* **Klasse S (Schwer):** 140 bis 160 cm + *(Hinweis: Für Ponys sind die Höhen reduziert und die Abstände in Kombinationen verkürzt)* + +### 2.2 Die Richtverfahren (RV) gemäß § 204 ÖTO + +Das Richtverfahren definiert, wie Fehler und Zeiten gewertet werden. + +#### Richtverfahren A (Standardspringprüfung) + +Es gibt Strafpunkte (Fehler) für Abwürfe (4 Fehlerpunkte) und Verweigerungen/Ungehorsam (4 Fehlerpunkte beim ersten Mal, +beim zweiten Mal ab 115 cm Ausschluss). Zeitüberschreitungen über die "Erlaubte Zeit" geben Zeitfehler (0,25 Punkte pro +Sekunde). + +* **A1:** Es gibt keine Zeitwertung. Alle fehlerfreien Ritte (gleiche Punktezahl) sind ex aequo auf Platz 1. +* **A2:** Fehler und Zeit. Bei Punktegleichheit gewinnt die schnellere Umlaufzeit. +* **A3 (Idealzeit):** Eine Idealzeit (meist Erlaubte Zeit minus 10%) wird definiert. Es gewinnt der Reiter, der am + nächsten an der Idealzeit ist (darunter oder darüber). Schützt vor "Bolzen" in Anfängerprüfungen. +* **AM3, AM4, AM5, AM6:** Standardspringen **mit Stechen**. Wer im Grundparcours fehlerfrei bleibt, reitet danach einen + verkürzten Stechparcours auf Zeit. + +#### Richtverfahren C (Zeitspringen) + +Fehlerpunkte gibt es nicht. Für jeden Abwurf werden stattdessen **Strafsekunden** (meist 4 Sekunden) zur gerittenen Zeit +addiert. Die schnellste Endzeit gewinnt. + +### 2.3 Spezial-Richtverfahren / Prüfungsarten + +* **Einlaufspringprüfung (§ 218):** Ein Trainingsbewerb (RV A1). Es gibt keine Platzierung. Jeder fehlerfreie Reiter + bekommt eine braune "Clear-Round"-Schleife. +* **Punktespringprüfung (§ 219):** Man sammelt Pluspunkte für fehlerfreie Sprünge. Am Ende steht oft ein schwieriger " + Joker-Sprung", der doppelte Punkte oder bei einem Fehler doppelten Abzug bringt. +* **2-Phasenspringprüfung (§ 220):** Der Parcours ist in zwei Teile geteilt. Wer Phase 1 fehlerfrei schafft, reitet ohne + anzuhalten sofort in Phase 2 (die meist auf Zeit geht). Wer in Phase 1 patzt, wird abgeglockt. +* **Stilspringprüfung:** Bewertung mit Wertnoten von 0 bis 10. Abzüge erfolgen für Ungehorsam (-0,5 oder -1,0) und + Hindernisfehler (-0,5). + +--- + +## 3. Sparte Dressur (CDN) im Detail + +### 3.1 Die Klassen und Aufgaben + +Die Dressur wird nach vorgegebenen "Aufgaben" geritten (z.B. "Aufgabe A2", "FEI Grand Prix"), die die zu reitenden +Hufschlagfiguren exakt vorgeben. + +* **Klasse A:** Grundlagen, Hufschlagfiguren, einfache Galoppwechsel über den Trab. +* **Klasse L:** Beginnende Versammlung, Kurzkehrt, Außengalopp. +* **Klasse LM:** Schulterherein, fliegende Wechsel können vorkommen. (Ab hier wahlweise Trense oder Kandare). +* **Klasse M:** Traversalen, fliegende Galoppwechsel. (Kandarenpflicht). +* **Klasse S:** Pirouetten, Serienwechsel (z.B. Wechsel alle 2 Sprünge), Piaffe, Passage. + +### 3.2 Die Richtverfahren (RV) gemäß § 104 ÖTO + +Das Richtverfahren definiert die Sitzverteilung und Art der Notengebung der Richter. + +#### Richtverfahren A (Gemeinsames Richten) + +Typisch für untere bis mittlere Klassen. Die Richtergruppe (die zusammen bei "C" sitzt) einigt sich auf **eine +gemeinsame Wertnote** zwischen 0,0 und 10,0 (z.B. 7,4). + +* *Verreiten:* Wird mit einem Abzug von der Gesamtnote bestraft (1. Mal: -0,2 / 2. Mal: -0,4). + +#### Richtverfahren B (Getrenntes Richten) + +Typisch ab Klasse M und bei Meisterschaften. Mindestens drei Richter sitzen an verschiedenen Stellen (C, H, M) und +werten völlig unabhängig voneinander. Jede einzelne Lektion wird mit einer Note (0 bis 10) bewertet. Am Ende werden die +Punkte addiert und in einen Prozentwert umgerechnet (z.B. 68,542 %). + +* *Verreiten:* Führt zu Abzügen bei der Gesamtpunktezahl bei jedem Richter (1. Mal: -2 Pkt. pro Richter / 2. Mal: -4 + Pkt. pro Richter). + +*Wichtig für alle Dressurprüfungen:* Ein **drittes Verreiten** führt unausweichlich zum Ausschluss (Abglocken) des +Teilnehmers. + +### 3.3 Sonderformen + +* **Musikkür:** Wird immer nach RV B gerichtet. Hierbei werden zwei getrennte Notensets vergeben: Eine Note für die * + *Technik** (Wurden alle geforderten Lektionen korrekt gezeigt?) und eine Note für die **Künstlerische Ausführung** ( + Choreographie, Musikinterpretation, Schwierigkeitsgrad). +* **Dressurreiterprüfung / Dressurpferdeprüfung:** Ähnlich wie im Stilspringen zählt hier primär der Sitz und die + Einwirkung des Reiters (Reiterprüfung) bzw. das Potenzial und die Grundgangarten des jungen Pferdes (Pferdeprüfung). + Wird in der Regel nach RV A (Wertnoten) gerichtet. diff --git a/docs/06_Frontend/FIGMA/Vision_02/src/imports/Struktur_Turnier-Ausschreibung-gemäß-OETO.md b/docs/06_Frontend/FIGMA/Vision_02/src/imports/Struktur_Turnier-Ausschreibung-gemäß-OETO.md new file mode 100644 index 00000000..ddf63f71 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vision_02/src/imports/Struktur_Turnier-Ausschreibung-gemäß-OETO.md @@ -0,0 +1,77 @@ +# Struktur einer Turnier-Ausschreibung gemäß ÖTO + +Diese Dokumentation beschreibt die notwendigen Felder und Sektionen einer offiziellen Ausschreibung für den +österreichischen Pferdesport (OEPS). + +## 1. Allgemeine Turnierdaten (Header) + +Dies entspricht im Wesentlichen dem **A-Satz** der ZNS-Schnittstelle. + +* **Turniernummer:** Eindeutige vom OEPS vergebene Kennung (z.B. 25123). +* **Veranstalter:** Name des durchführenden Vereins und dessen Vereinsnummer. +* **Austragungsort:** Genaue Adresse der Reitanlage. +* **Datum:** Von-bis Datum des Turniers. +* **Turnierkategorie:** Einstufung (z.B. CSN-B, CDN-A, CCN-C). +* **Nennschluss:** Datum, bis zu dem reguläre Nennungen möglich sind. + +## 2. Besondere Bestimmungen (Rechtlicher Rahmen) + +* **Regelwerk:** Hinweis auf die gültige ÖTO (Österreichische Turnierordnung) und ggf. FEI-Regeln. +* **Haftungsausschluss:** Verweis auf die Haftungsbestimmungen der ÖTO. +* **Teilnahmeberechtigung:** Allgemeine Einschränkungen (z.B. nur für Mitglieder bestimmter Landesverbände oder geladene + Gäste). + +## 3. Funktionäre (Offizielle) + +Wichtig für die Zuweisung im **C-Satz**. + +* **Turnierleiter:** Verantwortliche Person des Veranstalters. +* **Richterkollegium:** Liste der Richter inkl. deren Qualifikationen (z.B. "D-GP", "S"). +* **Technischer Delegierter (TD):** (Vor allem bei Vielseitigkeit). +* **Parcourschef:** Verantwortlich für das Design der Hindernisse. +* **Turniertierarzt & Schmied:** Notwendige medizinische Versorgung. + +## 4. Beschaffenheit der Anlage + +Informationen, die Einfluss auf das **DressurPruefungSpezifika** oder **SpringenPruefungSpezifika** haben. + +* **Austragungsplatz:** Maße (z.B. 20x60m), Bodenbelag (Sand, Gras). +* **Vorbereitungsplatz:** (Abreiteplatz) Maße und Bodenbelag. + +## 5. Nennungen & Gebühren + +Grundlage für den **Nennungs_Context**. + +* **Nennweg:** Hinweis auf das ZNS (Zentrales Nennsystem). +* **Nenngebühr:** Grundgebühr pro Pferd/Reiter-Paar. +* **Startgebühr:** Gebühr pro einzelner Prüfung. +* **Boxen/Einstreu:** Kosten für fixe oder mobile Boxen, inkl. Erst-Einstreu. +* **Zusatzgebühren:** Stromanschluss, Camping, Nachnenngebühren. + +## 6. Prüfungs-Programm (Bewerbe) + +Dies ist das Herzstück und bildet den **B-Satz** ab. Jede Prüfung muss folgende Details aufweisen: + +### Pflichtfelder pro Prüfung: + +| Feld | Beschreibung | Beispiel | +|:-------------------|:--------------------------------------|:---------------------------------| +| **Bewerbsnummer** | Fortlaufende Nummer | 01, 02, ... | +| **Bezeichnung** | Name der Prüfung | Standardspringprüfung | +| **Klasse** | Schwierigkeitsgrad | E, A, L, LM, M, S | +| **Abteilungen** | Unterteilung nach Lizenzen | Abt. 1: R1 / Abt. 2: R2 u. höher | +| **Aufgabe** | (Nur Dressur) Spezifische ÖTO-Aufgabe | A2, L1, FEI Grand Prix | +| **Anforderungen** | Erforderliche Lizenzen/Alter | R1 oder höher | +| **Richtverfahren** | Verweis auf ÖTO-Paragraphen | § 218, § 204 | +| **Dotierung** | Preisgeld-Aufstellung | EUR 500,- (150/100/80/...) | + +## 7. Stallungen & Unterbringung + +* Anreise- und Abreisezeiten. +* Verfügbarkeit von Futter und Einstreu. +* Veterinäramtliche Bestimmungen (Impfschutz-Kontrolle gemäß ÖTO). + +## 8. Vorläufige Zeiteinteilung + +* Grober Ablaufplan (welcher Tag, welche Prüfungen). +* Hinweis auf die endgültige Zeiteinteilung (meist am Vorabend im Nennungs-System). diff --git a/docs/06_Frontend/FIGMA/src/imports/pasted_text/meldestelle-desktop-screens.md b/docs/06_Frontend/FIGMA/Vision_02/src/imports/pasted_text/meldestelle-desktop-screens.md similarity index 100% rename from docs/06_Frontend/FIGMA/src/imports/pasted_text/meldestelle-desktop-screens.md rename to docs/06_Frontend/FIGMA/Vision_02/src/imports/pasted_text/meldestelle-desktop-screens.md diff --git a/docs/06_Frontend/FIGMA/src/imports/pasted_text/nennungs-maske-design.md b/docs/06_Frontend/FIGMA/Vision_02/src/imports/pasted_text/nennungs-maske-design.md similarity index 100% rename from docs/06_Frontend/FIGMA/src/imports/pasted_text/nennungs-maske-design.md rename to docs/06_Frontend/FIGMA/Vision_02/src/imports/pasted_text/nennungs-maske-design.md diff --git a/docs/06_Frontend/FIGMA/src/styles/fonts.css b/docs/06_Frontend/FIGMA/Vision_02/src/styles/fonts.css similarity index 100% rename from docs/06_Frontend/FIGMA/src/styles/fonts.css rename to docs/06_Frontend/FIGMA/Vision_02/src/styles/fonts.css diff --git a/docs/06_Frontend/FIGMA/src/styles/index.css b/docs/06_Frontend/FIGMA/Vision_02/src/styles/index.css similarity index 100% rename from docs/06_Frontend/FIGMA/src/styles/index.css rename to docs/06_Frontend/FIGMA/Vision_02/src/styles/index.css diff --git a/docs/06_Frontend/FIGMA/src/styles/tailwind.css b/docs/06_Frontend/FIGMA/Vision_02/src/styles/tailwind.css similarity index 100% rename from docs/06_Frontend/FIGMA/src/styles/tailwind.css rename to docs/06_Frontend/FIGMA/Vision_02/src/styles/tailwind.css diff --git a/docs/06_Frontend/FIGMA/src/styles/theme.css b/docs/06_Frontend/FIGMA/Vision_02/src/styles/theme.css similarity index 100% rename from docs/06_Frontend/FIGMA/src/styles/theme.css rename to docs/06_Frontend/FIGMA/Vision_02/src/styles/theme.css diff --git a/docs/06_Frontend/FIGMA/vite.config.ts b/docs/06_Frontend/FIGMA/Vision_02/vite.config.ts similarity index 100% rename from docs/06_Frontend/FIGMA/vite.config.ts rename to docs/06_Frontend/FIGMA/Vision_02/vite.config.ts diff --git a/docs/06_Frontend/FIGMA/Vison_01/ATTRIBUTIONS.md b/docs/06_Frontend/FIGMA/Vison_01/ATTRIBUTIONS.md new file mode 100644 index 00000000..ce6bb5a6 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/ATTRIBUTIONS.md @@ -0,0 +1,5 @@ +This Figma Make file includes components from [shadcn/ui](https://ui.shadcn.com/) used +under [MIT license](https://github.com/shadcn-ui/ui/blob/main/LICENSE.md). + +This Figma Make file includes photos from [Unsplash](https://unsplash.com) used +under [license](https://unsplash.com/license). diff --git a/docs/06_Frontend/FIGMA/README.md b/docs/06_Frontend/FIGMA/Vison_01/README.md similarity index 100% rename from docs/06_Frontend/FIGMA/README.md rename to docs/06_Frontend/FIGMA/Vison_01/README.md diff --git a/docs/06_Frontend/FIGMA/guidelines.zip b/docs/06_Frontend/FIGMA/Vison_01/guidelines.zip similarity index 100% rename from docs/06_Frontend/FIGMA/guidelines.zip rename to docs/06_Frontend/FIGMA/Vison_01/guidelines.zip diff --git a/docs/06_Frontend/FIGMA/Vison_01/guidelines/Guidelines.md b/docs/06_Frontend/FIGMA/Vison_01/guidelines/Guidelines.md new file mode 100644 index 00000000..110f1178 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/guidelines/Guidelines.md @@ -0,0 +1,61 @@ +**Add your own guidelines here** + diff --git a/docs/06_Frontend/FIGMA/package.json b/docs/06_Frontend/FIGMA/Vison_01/package.json similarity index 100% rename from docs/06_Frontend/FIGMA/package.json rename to docs/06_Frontend/FIGMA/Vison_01/package.json diff --git a/docs/06_Frontend/FIGMA/Vison_01/postcss.config.mjs b/docs/06_Frontend/FIGMA/Vison_01/postcss.config.mjs new file mode 100644 index 00000000..531dbecd --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/postcss.config.mjs @@ -0,0 +1,15 @@ +/** + * PostCSS Configuration + * + * Tailwind CSS v4 (via @tailwindcss/vite) automatically sets up all required + * PostCSS plugins — you do NOT need to include `tailwindcss` or `autoprefixer` here. + * + * This file only exists for adding additional PostCSS plugins, if needed. + * For example: + * + * import postcssNested from 'postcss-nested' + * export default { plugins: [postcssNested()] } + * + * Otherwise, you can leave this file empty. + */ +export default {} diff --git a/docs/06_Frontend/FIGMA/src.zip b/docs/06_Frontend/FIGMA/Vison_01/src.zip similarity index 100% rename from docs/06_Frontend/FIGMA/src.zip rename to docs/06_Frontend/FIGMA/Vison_01/src.zip diff --git a/docs/06_Frontend/FIGMA/src/app/App.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/App.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/App.tsx rename to docs/06_Frontend/FIGMA/Vison_01/src/app/App.tsx diff --git a/docs/06_Frontend/FIGMA/Vison_01/src/app/components/Bewerbsliste.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/Bewerbsliste.tsx new file mode 100644 index 00000000..d0ce73b2 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/Bewerbsliste.tsx @@ -0,0 +1,139 @@ +import {useState} from 'react'; +import Box from '@mui/material/Box'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; +import IconButton from '@mui/material/IconButton'; +import Button from '@mui/material/Button'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import FilterListIcon from '@mui/icons-material/FilterList'; + +// Mock-Daten für Bewerbe +const mockBewerbe = [ + {tag: 'So', platz: 1, nr: '1', beginn: '08:00', nenn: 0, name: 'Dressurreiterprüfung Ratepass', klasse: 'A'}, + {tag: 'So', platz: 1, nr: '2', beginn: '08:20', nenn: 0, name: 'Dressurreiterprüfung Katecnadel', klasse: 'L'}, + {tag: 'So', platz: 1, nr: '3', beginn: '08:40', nenn: 0, name: 'Dressurreiterprüfung Idf. (Idf.)', klasse: 'M'}, + {tag: 'So', platz: 1, nr: '4', beginn: '09:00', nenn: 0, name: 'Dressurprüfung Idf. (Idf.)', klasse: 'L'}, + {tag: 'So', platz: 1, nr: '5', beginn: '09:20', nenn: 0, name: 'Führzügelklasse', klasse: 'E'}, + {tag: 'So', platz: 1, nr: '6', beginn: '09:40', nenn: 0, name: 'First Ridden', klasse: 'E'}, + {tag: 'So', platz: 1, nr: '7', beginn: '10:00', nenn: 0, name: 'Pony Dressurprüfung Kl. A', klasse: 'A'}, + {tag: 'So', platz: 1, nr: '8', beginn: '10:20', nenn: 0, name: 'Dressurreiterprüfung Kl. A', klasse: 'A'}, + {tag: 'So', platz: 1, nr: '9', beginn: '10:40', nenn: 0, name: 'Dressurprüfung Kl. A', klasse: 'A'}, + {tag: 'So', platz: 1, nr: '10', beginn: '11:00', nenn: 0, name: 'Pony Dressurprüfung Kl. A', klasse: 'A'}, + {tag: 'So', platz: 1, nr: '11', beginn: '11:20', nenn: 0, name: 'Dressurreiterprüfung Kl. L', klasse: 'L'}, + {tag: 'So', platz: 1, nr: '12', beginn: '11:40', nenn: 0, name: 'Dressurprüfung Kl. L', klasse: 'L'}, +]; + +interface Props { + selectedPferd: any; + selectedReiter: any; + onNennung: (bewerb: any) => void; +} + +export function Bewerbsliste({selectedPferd, selectedReiter, onNennung}: Props) { + const [selectedBewerb, setSelectedBewerb] = useState(null); + + const handleBewerbDoppelklick = (bewerb: any) => { + if (selectedPferd && selectedReiter) { + onNennung(bewerb); + setSelectedBewerb(bewerb.nr); + } + }; + + const canNennen = selectedPferd && selectedReiter; + + return ( + + + Bewerbsübersicht + + + + + + + + Aktualisieren + + + {mockBewerbe.length} Bewerbe + + + + 0 gefiltert + + + + + + + + Tag + Pl. + Bewerb + Beginn + Nenn. + Bewerbsname + + + + {mockBewerbe.map((bewerb, idx) => { + const isSelected = selectedBewerb === bewerb.nr; + const isClickable = canNennen; + + return ( + handleBewerbDoppelklick(bewerb)} + sx={{ + cursor: isClickable ? 'pointer' : 'default', + '&:nth-of-type(odd)': {bgcolor: isSelected ? 'primary.100' : 'action.hover'}, + '&.Mui-selected': { + bgcolor: 'primary.100', + '&:hover': { + bgcolor: 'primary.200', + }, + }, + opacity: isClickable ? 1 : 0.5, + }} + > + {bewerb.tag} + {bewerb.platz} + {bewerb.nr} + {bewerb.beginn} + {bewerb.nenn} + {bewerb.name} + + ); + })} + +
+
+ + {!canNennen && ( + + Bitte wählen Sie zuerst ein Pferd und einen Reiter aus + + )} +
+ ); +} diff --git a/docs/06_Frontend/FIGMA/Vison_01/src/app/components/NennungenTabelle.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/NennungenTabelle.tsx new file mode 100644 index 00000000..1af2c8c8 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/NennungenTabelle.tsx @@ -0,0 +1,129 @@ +import {useState} from 'react'; +import Box from '@mui/material/Box'; +import Tabs from '@mui/material/Tabs'; +import Tab from '@mui/material/Tab'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; +import Button from '@mui/material/Button'; +import IconButton from '@mui/material/IconButton'; +import RefreshIcon from '@mui/icons-material/Refresh'; + +interface Props { + nennungen: any[]; + selectedPferd: any; + selectedReiter: any; +} + +export function NennungenTabelle({nennungen, selectedPferd, selectedReiter}: Props) { + const [tabValue, setTabValue] = useState(0); + + // Filter basierend auf Tab + const getFilteredNennungen = () => { + if (!selectedPferd && !selectedReiter) return []; + + switch (tabValue) { + case 0: // Reiter + return selectedReiter + ? nennungen.filter(n => n.reiter === selectedReiter.vorname + ' ' + selectedReiter.nachname) + : []; + case 1: // Pferd + return selectedPferd + ? nennungen.filter(n => n.pferd === selectedPferd.name) + : []; + case 2: // Bewerbe + return (selectedPferd && selectedReiter) + ? nennungen.filter(n => + n.pferd === selectedPferd.name && + n.reiter === selectedReiter.vorname + ' ' + selectedReiter.nachname + ) + : []; + default: + return []; + } + }; + + const filteredNennungen = getFilteredNennungen(); + + return ( + + setTabValue(v)} + sx={{borderBottom: 1, borderColor: 'divider', minHeight: 32}}> + + + + + + + + + + + Aktualisieren + + + {filteredNennungen.length} Nennungen + + + + + + + + + + Tag + Pl. + Bewerb + Bewerbsname + Bemerkung + Pferd + + + + {filteredNennungen.length === 0 ? ( + + + Keine Nennungen vorhanden + + + ) : ( + filteredNennungen.map((nennung, idx) => ( + + {nennung.tag} + {nennung.platz} + {nennung.bewerbNr} + {nennung.bewerbName} + {nennung.startwunsch || '-'} + {nennung.pferd} + + )) + )} + +
+
+
+ ); +} diff --git a/docs/06_Frontend/FIGMA/Vison_01/src/app/components/NennungsMaske.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/NennungsMaske.tsx new file mode 100644 index 00000000..4399ec0f --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/NennungsMaske.tsx @@ -0,0 +1,115 @@ +import {useState} from 'react'; +import Box from '@mui/material/Box'; +import Button from '@mui/material/Button'; +import {PferdReiterEingabe} from './PferdReiterEingabe'; +import {NennungenTabelle} from './NennungenTabelle'; +import {VerkaufBuchungen} from './VerkaufBuchungen'; +import {Bewerbsliste} from './Bewerbsliste'; +import ListIcon from '@mui/icons-material/List'; +import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; +import ReceiptIcon from '@mui/icons-material/Receipt'; + +export function NennungsMaske() { + const [selectedPferd, setSelectedPferd] = useState(null); + const [selectedReiter, setSelectedReiter] = useState(null); + const [nennungen, setNennungen] = useState([]); + + const handleNennung = (bewerb: any) => { + if (selectedPferd && selectedReiter) { + const neueNennung = { + tag: bewerb.tag, + platz: bewerb.platz, + bewerbNr: bewerb.nr, + bewerbName: bewerb.name, + beginn: bewerb.beginn, + pferd: selectedPferd.name, + reiter: `${selectedReiter.vorname} ${selectedReiter.nachname}`, + startwunsch: null, + }; + setNennungen([...nennungen, neueNennung]); + } + }; + + return ( + + {/* Zeile 1 (50% Höhe): Pferd/Reiter Suche + Verkauf/Buchungen */} + + {/* Links: Pferd & Reiter Eingabe (60%) */} + + + + + {/* Rechts: Verkauf/Buchungen (40%) */} + + + + + + {/* Zeile 2 (5% Höhe): Navigation Buttons */} + + + + + + + {/* Zeile 3 (45% Höhe): Nennungsübersicht + Bewerbsübersicht */} + + {/* Links: Nennungsübersicht (60%) */} + + + + + {/* Rechts: Bewerbsübersicht (40%) */} + + + + + + ); +} diff --git a/docs/06_Frontend/FIGMA/src/app/components/PferdReiterEingabe.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/PferdReiterEingabe.tsx similarity index 100% rename from docs/06_Frontend/FIGMA/src/app/components/PferdReiterEingabe.tsx rename to docs/06_Frontend/FIGMA/Vison_01/src/app/components/PferdReiterEingabe.tsx diff --git a/docs/06_Frontend/FIGMA/Vison_01/src/app/components/VerkaufBuchungen.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/VerkaufBuchungen.tsx new file mode 100644 index 00000000..8fad1549 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/VerkaufBuchungen.tsx @@ -0,0 +1,213 @@ +import {useState} from 'react'; +import Box from '@mui/material/Box'; +import Tabs from '@mui/material/Tabs'; +import Tab from '@mui/material/Tab'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableContainer from '@mui/material/TableContainer'; +import TableHead from '@mui/material/TableHead'; +import TableRow from '@mui/material/TableRow'; +import Toolbar from '@mui/material/Toolbar'; +import Typography from '@mui/material/Typography'; +import IconButton from '@mui/material/IconButton'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import AddIcon from '@mui/icons-material/Add'; +import RemoveIcon from '@mui/icons-material/Remove'; + +// Mock-Daten für Verkauf +const mockVerkaufArtikel = [ + {knr: '', text: 'Belastung', einzelpreis: 0, menge: 0, gebucht: '0.00'}, + {knr: '', text: 'Gutschrift', einzelpreis: 0, menge: 0, gebucht: '0.00'}, + {knr: '', text: 'Boxenpauschale', einzelpreis: 115.00, menge: 0, gebucht: '0.00'}, + {knr: '', text: 'Ansage', einzelpreis: 2.00, menge: 0, gebucht: '0.00'}, + {knr: '', text: 'Füttern', einzelpreis: 3.00, menge: 0, gebucht: '0.00'}, + {knr: '', text: 'Heu', einzelpreis: 13.00, menge: 0, gebucht: '0.00'}, + {knr: '', text: 'Späne', einzelpreis: 15.00, menge: 0, gebucht: '0.00'}, + {knr: '', text: 'Stroh', einzelpreis: 5.00, menge: 0, gebucht: '0.00'}, + {knr: '', text: 'Strom', einzelpreis: 50.00, menge: 0, gebucht: '0.00'}, + {knr: '', text: 'Y-Nummer', einzelpreis: 35.00, menge: 0, gebucht: '0.00'}, + {knr: '', text: 'Z-Nummer', einzelpreis: 10.00, menge: 0, gebucht: '0.00'}, +]; + +interface Props { + selectedReiter: any; +} + +export function VerkaufBuchungen({selectedReiter}: Props) { + const [tabValue, setTabValue] = useState(0); + const [verkaufMengen, setVerkaufMengen] = useState<{ [key: string]: number }>({}); + + const handleMengeChange = (text: string, delta: number) => { + setVerkaufMengen(prev => ({ + ...prev, + [text]: Math.max(0, (prev[text] || 0) + delta), + })); + }; + + return ( + + setTabValue(v)} + sx={{borderBottom: 1, borderColor: 'divider', minHeight: 32}}> + + + + + {tabValue === 0 && ( + <> + + + + + + Aktualisieren + + + {mockVerkaufArtikel.length} Artikel + + + + + + + + + + KNr + + + Menge + - + Buchungstext + Betrag + Gebucht + + + + {mockVerkaufArtikel.map((artikel, idx) => { + const menge = verkaufMengen[artikel.text] || 0; + const betrag = menge * artikel.einzelpreis; + return ( + + {artikel.knr} + + handleMengeChange(artikel.text, 1)} + sx={{width: 20, height: 20}} + > + + + + + setVerkaufMengen(prev => ({ + ...prev, + [artikel.text]: Math.max(0, parseInt(e.target.value) || 0), + }))} + sx={{ + width: 50, + '& .MuiInputBase-input': { + textAlign: 'center', + fontSize: '10px', + py: 0.25, + px: 0.5, + }, + }} + /> + + + handleMengeChange(artikel.text, -1)} + sx={{width: 20, height: 20}} + > + + + + {artikel.text} + 0 ? 600 : 400, py: 0.5}} align="right"> + {betrag.toFixed(2)} + + {artikel.gebucht} + + ); + })} + +
+
+ + )} + + {tabValue === 1 && ( + <> + + + + + + Aktualisieren + + + 0 Buchungen + + + + + + + + + Kopfnr + Menge + Buchungstext + Soll + Haben + + + + + + Keine Buchungen vorhanden + + + +
+
+ + )} +
+ ); +} diff --git a/docs/06_Frontend/FIGMA/Vison_01/src/app/components/figma/ImageWithFallback.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/figma/ImageWithFallback.tsx new file mode 100644 index 00000000..ff6e48f5 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/figma/ImageWithFallback.tsx @@ -0,0 +1,27 @@ +import React, {useState} from 'react' + +const ERROR_IMG_SRC = + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODgiIGhlaWdodD0iODgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBvcGFjaXR5PSIuMyIgZmlsbD0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIzLjciPjxyZWN0IHg9IjE2IiB5PSIxNiIgd2lkdGg9IjU2IiBoZWlnaHQ9IjU2IiByeD0iNiIvPjxwYXRoIGQ9Im0xNiA1OCAxNi0xOCAzMiAzMiIvPjxjaXJjbGUgY3g9IjUzIiBjeT0iMzUiIHI9IjciLz48L3N2Zz4KCg==' + +export function ImageWithFallback(props: React.ImgHTMLAttributes) { + const [didError, setDidError] = useState(false) + + const handleError = () => { + setDidError(true) + } + + const {src, alt, style, className, ...rest} = props + + return didError ? ( +
+
+ Error loading image +
+
+ ) : ( + {alt} + ) +} diff --git a/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/accordion.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/accordion.tsx new file mode 100644 index 00000000..19e8905a --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/accordion.tsx @@ -0,0 +1,67 @@ +"use client"; + +import * as React from "react"; +import * as AccordionPrimitive from "@radix-ui/react-accordion"; +import {ChevronDownIcon} from "lucide-react"; + +import {cn} from "./utils"; + +function Accordion({ + ...props + }: React.ComponentProps) { + return ; +} + +function AccordionItem({ + className, + ...props + }: React.ComponentProps) { + return ( + + ); +} + +function AccordionTrigger({ + className, + children, + ...props + }: React.ComponentProps) { + return ( + + svg]:rotate-180", + className, + )} + {...props} + > + {children} + + + + ); +} + +function AccordionContent({ + className, + children, + ...props + }: React.ComponentProps) { + return ( + +
{children}
+
+ ); +} + +export {Accordion, AccordionItem, AccordionTrigger, AccordionContent}; diff --git a/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/alert-dialog.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..d49018d1 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +"use client"; + +import * as React from "react"; +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"; + +import {cn} from "./utils"; +import {buttonVariants} from "./button"; + +function AlertDialog({ + ...props + }: React.ComponentProps) { + return ; +} + +function AlertDialogTrigger({ + ...props + }: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogPortal({ + ...props + }: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogOverlay({ + className, + ...props + }: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogContent({ + className, + ...props + }: React.ComponentProps) { + return ( + + + + + ); +} + +function AlertDialogHeader({ + className, + ...props + }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDialogFooter({ + className, + ...props + }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDialogTitle({ + className, + ...props + }: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogDescription({ + className, + ...props + }: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogAction({ + className, + ...props + }: React.ComponentProps) { + return ( + + ); +} + +function AlertDialogCancel({ + className, + ...props + }: React.ComponentProps) { + return ( + + ); +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; diff --git a/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/alert.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/alert.tsx new file mode 100644 index 00000000..6424cc40 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/alert.tsx @@ -0,0 +1,66 @@ +import * as React from "react"; +import {cva, type VariantProps} from "class-variance-authority"; + +import {cn} from "./utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +function Alert({ + className, + variant, + ...props + }: React.ComponentProps<"div"> & VariantProps) { + return ( +
+ ); +} + +function AlertTitle({className, ...props}: React.ComponentProps<"div">) { + return ( +
+ ); +} + +function AlertDescription({ + className, + ...props + }: React.ComponentProps<"div">) { + return ( +
+ ); +} + +export {Alert, AlertTitle, AlertDescription}; diff --git a/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/aspect-ratio.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/aspect-ratio.tsx new file mode 100644 index 00000000..cd697698 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/aspect-ratio.tsx @@ -0,0 +1,11 @@ +"use client"; + +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"; + +function AspectRatio({ + ...props + }: React.ComponentProps) { + return ; +} + +export {AspectRatio}; diff --git a/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/avatar.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/avatar.tsx new file mode 100644 index 00000000..cac4642f --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +"use client"; + +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +import {cn} from "./utils"; + +function Avatar({ + className, + ...props + }: React.ComponentProps) { + return ( + + ); +} + +function AvatarImage({ + className, + ...props + }: React.ComponentProps) { + return ( + + ); +} + +function AvatarFallback({ + className, + ...props + }: React.ComponentProps) { + return ( + + ); +} + +export {Avatar, AvatarImage, AvatarFallback}; diff --git a/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/badge.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/badge.tsx new file mode 100644 index 00000000..07ffa941 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/badge.tsx @@ -0,0 +1,46 @@ +import * as React from "react"; +import {Slot} from "@radix-ui/react-slot"; +import {cva, type VariantProps} from "class-variance-authority"; + +import {cn} from "./utils"; + +const badgeVariants = cva( + "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", + outline: + "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +function Badge({ + className, + variant, + asChild = false, + ...props + }: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot : "span"; + + return ( + + ); +} + +export {Badge, badgeVariants}; diff --git a/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/breadcrumb.tsx b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/breadcrumb.tsx new file mode 100644 index 00000000..6916fcd1 --- /dev/null +++ b/docs/06_Frontend/FIGMA/Vison_01/src/app/components/ui/breadcrumb.tsx @@ -0,0 +1,109 @@ +import * as React from "react"; +import {Slot} from "@radix-ui/react-slot"; +import {ChevronRight, MoreHorizontal} from "lucide-react"; + +import {cn} from "./utils"; + +function Breadcrumb({...props}: React.ComponentProps<"nav">) { + return