@nebulit/embuilder 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +254 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +138 -0
- package/package.json +49 -0
- package/templates/.claude/hooks/QUICKSTART.md +256 -0
- package/templates/.claude/hooks/README.md +533 -0
- package/templates/.claude/hooks/analyze-commit.sh +22 -0
- package/templates/.claude/hooks/analyze-commit.ts +518 -0
- package/templates/.claude/hooks/analyzers/README.md +198 -0
- package/templates/.claude/hooks/analyzers/code-quality-checker.ts +154 -0
- package/templates/.claude/hooks/analyzers/code-quality.md +54 -0
- package/templates/.claude/hooks/analyzers/commit-blocker-example.ts.disabled +110 -0
- package/templates/.claude/hooks/analyzers/commit-policy.md +49 -0
- package/templates/.claude/hooks/analyzers/event-model-validator.md +49 -0
- package/templates/.claude/hooks/analyzers/event-model-validator.ts +169 -0
- package/templates/.claude/hooks/analyzers/example-logger.ts +70 -0
- package/templates/.claude/hooks/analyzers/slice-scope-validator.md +81 -0
- package/templates/.claude/hooks/check-review-result.sh +47 -0
- package/templates/.claude/hooks/prepare-review.sh +34 -0
- package/templates/.claude/hooks/review-agent-prompt.md +42 -0
- package/templates/.claude/hooks/run-review-agent.sh +124 -0
- package/templates/.claude/settings.local.json +37 -0
- package/templates/.claude/skills/help/README.md +84 -0
- package/templates/.claude/skills/help/SKILL.md +393 -0
- package/templates/.claude/skills/help/templates/demo-config.json +6753 -0
- package/templates/.claude/skills/sample-slices/SKILL.md +8 -0
- package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/code-slice.json +124 -0
- package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/slice.json +255 -0
- package/templates/.claude/skills/sample-slices/templates/.slices/Library/availablebooks/slice.json +107 -0
- package/templates/.claude/skills/sample-slices/templates/.slices/index.json +20 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/additem/slice.json +979 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/archiveitem/slice.json +529 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/cartitems/slice.json +1072 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/cartwithproducts/slice.json +394 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/changedprices/slice.json +88 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/changeinventory/slice.json +264 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/changeprice/slice.json +308 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/clearcart/slice.json +358 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/inventories/slice.json +203 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/publishcart/slice.json +876 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/removeitem/slice.json +560 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/submitcart/slice.json +708 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/submittedcartdata/slice.json +399 -0
- package/templates/.claude/skills/sample-slices/templates/index.json +108 -0
- package/templates/.claude/skills/slice-automation/SKILL.md +49 -0
- package/templates/.claude/skills/slice-state-change/SKILL.md +369 -0
- package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocation.test.ts.sample +76 -0
- package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocationCommand.ts.sample +84 -0
- package/templates/.claude/skills/slice-state-change/templates/AddLocation/routes.ts.sample +73 -0
- package/templates/.claude/skills/slice-state-change/templates/README.md +46 -0
- package/templates/.claude/skills/slice-state-view/SKILL.md +336 -0
- package/templates/.claude/skills/slice-state-view/templates/Locations/Locations.test.ts.sample +84 -0
- package/templates/.claude/skills/slice-state-view/templates/Locations/LocationsProjection.ts.sample +50 -0
- package/templates/.claude/skills/slice-state-view/templates/Locations/routes.ts.sample +46 -0
- package/templates/.claude/skills/slice-state-view/templates/README.md +109 -0
- package/templates/.claude/skills/slice-state-view/templates/Tables/Tables.test.ts.sample +104 -0
- package/templates/.claude/skills/slice-state-view/templates/Tables/TablesProjection.ts.sample +59 -0
- package/templates/.claude/skills/slice-state-view/templates/Tables/routes.ts.sample +46 -0
- package/templates/.claude/skills/slice-state-view/templates/V2__tables.sql +7 -0
- package/templates/.claude/skills/slice-state-view/templates/V8__locations.sql +7 -0
- package/templates/.claude/skills/test-analyzer/SKILL.md +373 -0
- package/templates/.claude/skills/test-analyzer/examples/specification-format.md +143 -0
- package/templates/.claude/skills/test-analyzer/examples/state-change-example.md +111 -0
- package/templates/.claude/skills/test-analyzer/examples/state-view-example.md +122 -0
- package/templates/AGENTS.md +110 -0
- package/templates/Claude.md +58 -0
- package/templates/README.md +178 -0
- package/templates/backend/.env +9 -0
- package/templates/backend/BACKEND_AUTH_SETUP.md +183 -0
- package/templates/backend/SWAGGER.md +213 -0
- package/templates/backend/eslint.config.mjs +31 -0
- package/templates/backend/flyway.conf +17 -0
- package/templates/backend/package.json +44 -0
- package/templates/backend/prd.json.example +64 -0
- package/templates/backend/public/assets/images/banner.png +0 -0
- package/templates/backend/public/assets/logo.png +0 -0
- package/templates/backend/public/file.svg +4 -0
- package/templates/backend/public/globe.svg +12 -0
- package/templates/backend/public/next.svg +6 -0
- package/templates/backend/public/vercel.svg +3 -0
- package/templates/backend/public/window.svg +5 -0
- package/templates/backend/server.ts +129 -0
- package/templates/backend/setup-env.sh +50 -0
- package/templates/backend/src/common/assertions.ts +6 -0
- package/templates/backend/src/common/db.ts +1 -0
- package/templates/backend/src/common/loadPostgresEventstore.ts +16 -0
- package/templates/backend/src/common/parseEndpoint.ts +51 -0
- package/templates/backend/src/common/replay.ts +9 -0
- package/templates/backend/src/common/routes.ts +19 -0
- package/templates/backend/src/common/testHelpers.ts +53 -0
- package/templates/backend/src/core/readmodel.ts +28 -0
- package/templates/backend/src/core/types.ts +26 -0
- package/templates/backend/src/process/process.ts +53 -0
- package/templates/backend/src/supabase/LoginHandler.ts +36 -0
- package/templates/backend/src/supabase/ProtectedPageProps.ts +21 -0
- package/templates/backend/src/supabase/README.md +171 -0
- package/templates/backend/src/supabase/api.ts +63 -0
- package/templates/backend/src/supabase/authMiddleware.ts +53 -0
- package/templates/backend/src/supabase/component.ts +12 -0
- package/templates/backend/src/supabase/requireUser.ts +72 -0
- package/templates/backend/src/supabase/serverProps.ts +25 -0
- package/templates/backend/src/supabase/staticProps.ts +10 -0
- package/templates/backend/src/swagger.ts +34 -0
- package/templates/backend/src/util/assertions.ts +6 -0
- package/templates/backend/supabase/config.toml +295 -0
- package/templates/backend/supabase/migrations/20260121155918593_catalogentries.sql.sample +23 -0
- package/templates/backend/supabase/seed.sql +1 -0
- package/templates/backend/tsconfig.json +31 -0
- package/templates/frontend/.env.development +3 -0
- package/templates/frontend/AGENTS.md +7 -0
- package/templates/frontend/README.md +73 -0
- package/templates/frontend/components.json +20 -0
- package/templates/frontend/eslint.config.js +26 -0
- package/templates/frontend/index.html +18 -0
- package/templates/frontend/package-lock.json +8347 -0
- package/templates/frontend/package.json +94 -0
- package/templates/frontend/postcss.config.js +6 -0
- package/templates/frontend/public/favicon.ico +0 -0
- package/templates/frontend/public/logo.png +0 -0
- package/templates/frontend/public/placeholder.svg +1 -0
- package/templates/frontend/public/robots.txt +14 -0
- package/templates/frontend/src/App.css +42 -0
- package/templates/frontend/src/App.tsx +47 -0
- package/templates/frontend/src/components/NavLink.tsx +28 -0
- package/templates/frontend/src/components/ProtectedRoute.tsx +24 -0
- package/templates/frontend/src/components/calendar/Calendar.tsx +302 -0
- package/templates/frontend/src/components/layout/DashboardLayout.tsx +21 -0
- package/templates/frontend/src/components/layout/Header.tsx +45 -0
- package/templates/frontend/src/components/layout/Sidebar.tsx +82 -0
- package/templates/frontend/src/components/tables/ReservationTemplates.tsx +189 -0
- package/templates/frontend/src/components/ui/accordion.tsx +52 -0
- package/templates/frontend/src/components/ui/alert-dialog.tsx +104 -0
- package/templates/frontend/src/components/ui/alert.tsx +43 -0
- package/templates/frontend/src/components/ui/aspect-ratio.tsx +5 -0
- package/templates/frontend/src/components/ui/avatar.tsx +38 -0
- package/templates/frontend/src/components/ui/badge.tsx +29 -0
- package/templates/frontend/src/components/ui/breadcrumb.tsx +90 -0
- package/templates/frontend/src/components/ui/button.tsx +47 -0
- package/templates/frontend/src/components/ui/calendar.tsx +54 -0
- package/templates/frontend/src/components/ui/card.tsx +43 -0
- package/templates/frontend/src/components/ui/carousel.tsx +224 -0
- package/templates/frontend/src/components/ui/chart.tsx +303 -0
- package/templates/frontend/src/components/ui/checkbox.tsx +26 -0
- package/templates/frontend/src/components/ui/collapsible.tsx +9 -0
- package/templates/frontend/src/components/ui/command.tsx +132 -0
- package/templates/frontend/src/components/ui/context-menu.tsx +178 -0
- package/templates/frontend/src/components/ui/dialog.tsx +95 -0
- package/templates/frontend/src/components/ui/drawer.tsx +87 -0
- package/templates/frontend/src/components/ui/dropdown-menu.tsx +179 -0
- package/templates/frontend/src/components/ui/form.tsx +129 -0
- package/templates/frontend/src/components/ui/hover-card.tsx +27 -0
- package/templates/frontend/src/components/ui/input-otp.tsx +61 -0
- package/templates/frontend/src/components/ui/input.tsx +22 -0
- package/templates/frontend/src/components/ui/label.tsx +17 -0
- package/templates/frontend/src/components/ui/menubar.tsx +207 -0
- package/templates/frontend/src/components/ui/navigation-menu.tsx +120 -0
- package/templates/frontend/src/components/ui/pagination.tsx +81 -0
- package/templates/frontend/src/components/ui/popover.tsx +29 -0
- package/templates/frontend/src/components/ui/progress.tsx +23 -0
- package/templates/frontend/src/components/ui/radio-group.tsx +36 -0
- package/templates/frontend/src/components/ui/resizable.tsx +37 -0
- package/templates/frontend/src/components/ui/scroll-area.tsx +38 -0
- package/templates/frontend/src/components/ui/select.tsx +143 -0
- package/templates/frontend/src/components/ui/separator.tsx +20 -0
- package/templates/frontend/src/components/ui/sheet.tsx +107 -0
- package/templates/frontend/src/components/ui/sidebar.tsx +637 -0
- package/templates/frontend/src/components/ui/skeleton.tsx +7 -0
- package/templates/frontend/src/components/ui/slider.tsx +23 -0
- package/templates/frontend/src/components/ui/sonner.tsx +27 -0
- package/templates/frontend/src/components/ui/stat-card.tsx +44 -0
- package/templates/frontend/src/components/ui/switch.tsx +27 -0
- package/templates/frontend/src/components/ui/table.tsx +72 -0
- package/templates/frontend/src/components/ui/tabs.tsx +53 -0
- package/templates/frontend/src/components/ui/textarea.tsx +21 -0
- package/templates/frontend/src/components/ui/toast.tsx +111 -0
- package/templates/frontend/src/components/ui/toaster.tsx +24 -0
- package/templates/frontend/src/components/ui/toggle-group.tsx +49 -0
- package/templates/frontend/src/components/ui/toggle.tsx +37 -0
- package/templates/frontend/src/components/ui/tooltip.tsx +28 -0
- package/templates/frontend/src/components/ui/use-toast.ts +3 -0
- package/templates/frontend/src/contexts/AuthContext.tsx +94 -0
- package/templates/frontend/src/contexts/RefreshContext.tsx +236 -0
- package/templates/frontend/src/hooks/api/index.ts +2 -0
- package/templates/frontend/src/hooks/api/useLocations.ts +15 -0
- package/templates/frontend/src/hooks/use-mobile.tsx +19 -0
- package/templates/frontend/src/hooks/use-toast.ts +186 -0
- package/templates/frontend/src/hooks/useApiContext.ts +11 -0
- package/templates/frontend/src/index.css +118 -0
- package/templates/frontend/src/integrations/supabase/client.ts +9 -0
- package/templates/frontend/src/lib/api-client.ts +136 -0
- package/templates/frontend/src/lib/api.ts +1028 -0
- package/templates/frontend/src/lib/utils.ts +6 -0
- package/templates/frontend/src/main.tsx +5 -0
- package/templates/frontend/src/pages/Auth.tsx +408 -0
- package/templates/frontend/src/pages/Dashboard.tsx +168 -0
- package/templates/frontend/src/pages/Menus.tsx +224 -0
- package/templates/frontend/src/pages/NotFound.tsx +24 -0
- package/templates/frontend/src/pages/Register.tsx +285 -0
- package/templates/frontend/src/test/example.test.ts +0 -0
- package/templates/frontend/src/test/setup.ts +15 -0
- package/templates/frontend/src/types/index.ts +8 -0
- package/templates/frontend/src/vite-env.d.ts +1 -0
- package/templates/frontend/tailwind.config.ts +101 -0
- package/templates/frontend/tsconfig.app.json +31 -0
- package/templates/frontend/tsconfig.json +16 -0
- package/templates/frontend/tsconfig.node.json +22 -0
- package/templates/frontend/vite.config.ts +21 -0
- package/templates/frontend/vitest.config.ts +16 -0
- package/templates/init.sh +1 -0
- package/templates/prompt.md +139 -0
- package/templates/ralph.sh +120 -0
- package/templates/server.mjs +505 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vite_react_shadcn_ts",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"build:dev": "vite build --mode development",
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"preview": "vite preview",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@toast-ui/react-calendar": "^2.1.3",
|
|
17
|
+
"@hookform/resolvers": "^3.10.0",
|
|
18
|
+
"@radix-ui/react-accordion": "^1.2.11",
|
|
19
|
+
"@radix-ui/react-alert-dialog": "^1.1.14",
|
|
20
|
+
"@radix-ui/react-aspect-ratio": "^1.1.7",
|
|
21
|
+
"@radix-ui/react-avatar": "^1.1.10",
|
|
22
|
+
"@radix-ui/react-checkbox": "^1.3.2",
|
|
23
|
+
"@radix-ui/react-collapsible": "^1.1.11",
|
|
24
|
+
"@radix-ui/react-context-menu": "^2.2.15",
|
|
25
|
+
"@radix-ui/react-dialog": "^1.1.14",
|
|
26
|
+
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
|
27
|
+
"@radix-ui/react-hover-card": "^1.1.14",
|
|
28
|
+
"@radix-ui/react-label": "^2.1.7",
|
|
29
|
+
"@radix-ui/react-menubar": "^1.1.15",
|
|
30
|
+
"@radix-ui/react-navigation-menu": "^1.2.13",
|
|
31
|
+
"@radix-ui/react-popover": "^1.1.14",
|
|
32
|
+
"@radix-ui/react-progress": "^1.1.7",
|
|
33
|
+
"@radix-ui/react-radio-group": "^1.3.7",
|
|
34
|
+
"@radix-ui/react-scroll-area": "^1.2.9",
|
|
35
|
+
"@radix-ui/react-select": "^2.2.5",
|
|
36
|
+
"@radix-ui/react-separator": "^1.1.7",
|
|
37
|
+
"@radix-ui/react-slider": "^1.3.5",
|
|
38
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
39
|
+
"@radix-ui/react-switch": "^1.2.5",
|
|
40
|
+
"@radix-ui/react-tabs": "^1.1.12",
|
|
41
|
+
"@radix-ui/react-toast": "^1.2.14",
|
|
42
|
+
"@radix-ui/react-toggle": "^1.1.9",
|
|
43
|
+
"@radix-ui/react-toggle-group": "^1.1.10",
|
|
44
|
+
"@radix-ui/react-tooltip": "^1.2.7",
|
|
45
|
+
"@supabase/supabase-js": "^2.75.0",
|
|
46
|
+
"@tanstack/react-query": "^5.83.0",
|
|
47
|
+
"class-variance-authority": "^0.7.1",
|
|
48
|
+
"clsx": "^2.1.1",
|
|
49
|
+
"cmdk": "^1.1.1",
|
|
50
|
+
"date-fns": "^3.6.0",
|
|
51
|
+
"embla-carousel-react": "^8.6.0",
|
|
52
|
+
"input-otp": "^1.4.2",
|
|
53
|
+
"lucide-react": "^0.462.0",
|
|
54
|
+
"next-themes": "^0.3.0",
|
|
55
|
+
"react": "^18.3.1",
|
|
56
|
+
"react-calendar": "^6.0.0",
|
|
57
|
+
"react-day-picker": "^8.10.1",
|
|
58
|
+
"react-dom": "^18.3.1",
|
|
59
|
+
"react-hook-form": "^7.61.1",
|
|
60
|
+
"react-resizable-panels": "^2.1.9",
|
|
61
|
+
"react-router-dom": "^6.30.1",
|
|
62
|
+
"recharts": "^2.15.4",
|
|
63
|
+
"sonner": "^1.7.4",
|
|
64
|
+
"tailwind-merge": "^2.6.0",
|
|
65
|
+
"tailwindcss-animate": "^1.0.7",
|
|
66
|
+
"toast-ui": "^1.0.0",
|
|
67
|
+
"uuid": "^13.0.0",
|
|
68
|
+
"vaul": "^0.9.9",
|
|
69
|
+
"zod": "^3.25.76"
|
|
70
|
+
},
|
|
71
|
+
"devDependencies": {
|
|
72
|
+
"@eslint/js": "^9.32.0",
|
|
73
|
+
"@tailwindcss/typography": "^0.5.16",
|
|
74
|
+
"@testing-library/jest-dom": "^6.6.0",
|
|
75
|
+
"@testing-library/react": "^16.0.0",
|
|
76
|
+
"@types/node": "^22.16.5",
|
|
77
|
+
"@types/react": "^18.3.23",
|
|
78
|
+
"@types/react-dom": "^18.3.7",
|
|
79
|
+
"@vitejs/plugin-react-swc": "^3.11.0",
|
|
80
|
+
"autoprefixer": "^10.4.21",
|
|
81
|
+
"eslint": "^9.32.0",
|
|
82
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
83
|
+
"eslint-plugin-react-refresh": "^0.4.20",
|
|
84
|
+
"globals": "^15.15.0",
|
|
85
|
+
"jsdom": "^20.0.3",
|
|
86
|
+
"lovable-tagger": "^1.1.13",
|
|
87
|
+
"postcss": "^8.5.6",
|
|
88
|
+
"tailwindcss": "^3.4.17",
|
|
89
|
+
"typescript": "^5.8.3",
|
|
90
|
+
"typescript-eslint": "^8.38.0",
|
|
91
|
+
"vite": "^5.4.19",
|
|
92
|
+
"vitest": "^3.2.4"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#root {
|
|
2
|
+
max-width: 1280px;
|
|
3
|
+
margin: 0 auto;
|
|
4
|
+
padding: 2rem;
|
|
5
|
+
text-align: center;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.logo {
|
|
9
|
+
height: 6em;
|
|
10
|
+
padding: 1.5em;
|
|
11
|
+
will-change: filter;
|
|
12
|
+
transition: filter 300ms;
|
|
13
|
+
}
|
|
14
|
+
.logo:hover {
|
|
15
|
+
filter: drop-shadow(0 0 2em #646cffaa);
|
|
16
|
+
}
|
|
17
|
+
.logo.react:hover {
|
|
18
|
+
filter: drop-shadow(0 0 2em #61dafbaa);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@keyframes logo-spin {
|
|
22
|
+
from {
|
|
23
|
+
transform: rotate(0deg);
|
|
24
|
+
}
|
|
25
|
+
to {
|
|
26
|
+
transform: rotate(360deg);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
31
|
+
a:nth-of-type(2) .logo {
|
|
32
|
+
animation: logo-spin infinite 20s linear;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.card {
|
|
37
|
+
padding: 2em;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.read-the-docs {
|
|
41
|
+
color: #888;
|
|
42
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {Toaster} from "@/components/ui/toaster";
|
|
2
|
+
import {Toaster as Sonner} from "@/components/ui/sonner";
|
|
3
|
+
import {TooltipProvider} from "@/components/ui/tooltip";
|
|
4
|
+
import {AuthProvider} from "@/contexts/AuthContext";
|
|
5
|
+
|
|
6
|
+
import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
|
|
7
|
+
import {BrowserRouter, Routes, Route} from "react-router-dom";
|
|
8
|
+
import Dashboard from "./pages/Dashboard";
|
|
9
|
+
import Tables from "./pages/Tables";
|
|
10
|
+
import Staff from "./pages/Staff";
|
|
11
|
+
import Shifts from "./pages/Shifts";
|
|
12
|
+
import Tasks from "./pages/Tasks";
|
|
13
|
+
import Menus from "./pages/Menus";
|
|
14
|
+
import Vacations from "./pages/Vacations";
|
|
15
|
+
import NotFound from "./pages/NotFound";
|
|
16
|
+
import Auth from "@/pages/Auth.tsx";
|
|
17
|
+
import Register from "@/pages/Register.tsx";
|
|
18
|
+
import { ProtectedRoute } from "./components/ProtectedRoute";
|
|
19
|
+
|
|
20
|
+
const queryClient = new QueryClient();
|
|
21
|
+
|
|
22
|
+
const App = () => (
|
|
23
|
+
<QueryClientProvider client={queryClient}>
|
|
24
|
+
<AuthProvider>
|
|
25
|
+
<TooltipProvider>
|
|
26
|
+
<Toaster/>
|
|
27
|
+
<Sonner/>
|
|
28
|
+
<BrowserRouter>
|
|
29
|
+
<Routes>
|
|
30
|
+
<Route path="/register" element={<Register/>}/>
|
|
31
|
+
<Route path="/auth" element={<Auth/>}/>
|
|
32
|
+
<Route path="/" element={<ProtectedRoute><Dashboard/></ProtectedRoute>}/>
|
|
33
|
+
<Route path="/tables" element={<ProtectedRoute><Tables/></ProtectedRoute>}/>
|
|
34
|
+
<Route path="/staff" element={<ProtectedRoute><Staff/></ProtectedRoute>}/>
|
|
35
|
+
<Route path="/shifts" element={<ProtectedRoute><Shifts/></ProtectedRoute>}/>
|
|
36
|
+
<Route path="/tasks" element={<ProtectedRoute><Tasks/></ProtectedRoute>}/>
|
|
37
|
+
<Route path="/menus" element={<ProtectedRoute><Menus/></ProtectedRoute>}/>
|
|
38
|
+
<Route path="/vacations" element={<ProtectedRoute><Vacations/></ProtectedRoute>}/>
|
|
39
|
+
<Route path="*" element={<NotFound/>}/>
|
|
40
|
+
</Routes>
|
|
41
|
+
</BrowserRouter>
|
|
42
|
+
</TooltipProvider>
|
|
43
|
+
</AuthProvider>
|
|
44
|
+
</QueryClientProvider>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
export default App;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NavLink as RouterNavLink, NavLinkProps } from "react-router-dom";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
|
|
5
|
+
interface NavLinkCompatProps extends Omit<NavLinkProps, "className"> {
|
|
6
|
+
className?: string;
|
|
7
|
+
activeClassName?: string;
|
|
8
|
+
pendingClassName?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const NavLink = forwardRef<HTMLAnchorElement, NavLinkCompatProps>(
|
|
12
|
+
({ className, activeClassName, pendingClassName, to, ...props }, ref) => {
|
|
13
|
+
return (
|
|
14
|
+
<RouterNavLink
|
|
15
|
+
ref={ref}
|
|
16
|
+
to={to}
|
|
17
|
+
className={({ isActive, isPending }) =>
|
|
18
|
+
cn(className, isActive && activeClassName, isPending && pendingClassName)
|
|
19
|
+
}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
NavLink.displayName = "NavLink";
|
|
27
|
+
|
|
28
|
+
export { NavLink };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Navigate } from 'react-router-dom';
|
|
2
|
+
import { useAuth } from '@/contexts/AuthContext';
|
|
3
|
+
|
|
4
|
+
interface ProtectedRouteProps {
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function ProtectedRoute({ children }: ProtectedRouteProps) {
|
|
9
|
+
const { user, loading, tenantId } = useAuth();
|
|
10
|
+
|
|
11
|
+
if (loading) {
|
|
12
|
+
return (
|
|
13
|
+
<div className="min-h-screen flex items-center justify-center bg-background">
|
|
14
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!user || !tenantId) {
|
|
20
|
+
return <Navigate to="/auth" replace />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return <>{children}</>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import {useMemo, useRef, useCallback} from "react";
|
|
2
|
+
import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
|
|
3
|
+
import {Button} from "@/components/ui/button";
|
|
4
|
+
import {Calendar as CalendarIcon, ChevronLeft, ChevronRight, Phone, Mail, Clock, AlignLeft} from "lucide-react";
|
|
5
|
+
import Calendar from "@toast-ui/react-calendar";
|
|
6
|
+
import "@toast-ui/calendar/dist/toastui-calendar.min.css";
|
|
7
|
+
import {useState} from "react";
|
|
8
|
+
import {
|
|
9
|
+
Dialog,
|
|
10
|
+
DialogContent,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
} from "@/components/ui/dialog";
|
|
14
|
+
import {format} from "date-fns";
|
|
15
|
+
import {de} from "date-fns/locale";
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export type CalendarEntry = {
|
|
19
|
+
type: string,
|
|
20
|
+
id: string,
|
|
21
|
+
title: string,
|
|
22
|
+
description: string,
|
|
23
|
+
phone: string,
|
|
24
|
+
email: string,
|
|
25
|
+
start: string,
|
|
26
|
+
end: string,
|
|
27
|
+
showupRegistered?: boolean,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface DashboardCalendarProps {
|
|
31
|
+
entries: CalendarEntry[];
|
|
32
|
+
onAppointmentClick: (appointment: CalendarEntry) => void;
|
|
33
|
+
onNoShow?: (reservationId: string) => void;
|
|
34
|
+
onShowUp?: (reservationId: string) => void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function DashboardCalendar({
|
|
38
|
+
entries,
|
|
39
|
+
onAppointmentClick,
|
|
40
|
+
onNoShow,
|
|
41
|
+
onShowUp,
|
|
42
|
+
}: DashboardCalendarProps) {
|
|
43
|
+
const calendarRef = useRef<typeof Calendar>(null);
|
|
44
|
+
const [currentDate, setCurrentDate] = useState(new Date());
|
|
45
|
+
const [view, setView] = useState<'month' | 'week' | 'day'>('month');
|
|
46
|
+
const [selectedEntry, setSelectedEntry] = useState<CalendarEntry | null>(null);
|
|
47
|
+
|
|
48
|
+
// Convert appointments to Toast UI Calendar events
|
|
49
|
+
const events = useMemo(() => {
|
|
50
|
+
return entries.map(entry => {
|
|
51
|
+
const showup = entry.showupRegistered === true;
|
|
52
|
+
return {
|
|
53
|
+
id: entry.id,
|
|
54
|
+
calendarId: 'reservations',
|
|
55
|
+
title: entry.title,
|
|
56
|
+
body: entry.description || '',
|
|
57
|
+
email: entry.email,
|
|
58
|
+
phone: entry.phone,
|
|
59
|
+
start: new Date(entry.start),
|
|
60
|
+
end: new Date(entry.end),
|
|
61
|
+
category: 'time' as const,
|
|
62
|
+
backgroundColor: showup ? '#16a34a' : 'hsl(var(--primary))',
|
|
63
|
+
color: '#ffffff',
|
|
64
|
+
borderColor: showup ? '#15803d' : 'hsl(var(--primary))',
|
|
65
|
+
raw: entry,
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
}, [entries]);
|
|
69
|
+
|
|
70
|
+
const calendars = useMemo(() => [
|
|
71
|
+
{
|
|
72
|
+
id: 'reservations',
|
|
73
|
+
name: 'Appointments',
|
|
74
|
+
backgroundColor: 'hsl(var(--primary))',
|
|
75
|
+
borderColor: 'hsl(var(--primary))',
|
|
76
|
+
color: 'hsl(var(--primary-foreground))',
|
|
77
|
+
},
|
|
78
|
+
], []);
|
|
79
|
+
|
|
80
|
+
const handleClickEvent = useCallback((eventInfo: { event: { raw: CalendarEntry } }) => {
|
|
81
|
+
if (eventInfo.event.raw) {
|
|
82
|
+
setSelectedEntry(eventInfo.event.raw);
|
|
83
|
+
onAppointmentClick(eventInfo.event.raw);
|
|
84
|
+
}
|
|
85
|
+
}, [onAppointmentClick]);
|
|
86
|
+
|
|
87
|
+
const handlePrev = () => {
|
|
88
|
+
const calendarInstance = (calendarRef.current as any)?.getInstance?.();
|
|
89
|
+
if (calendarInstance) {
|
|
90
|
+
calendarInstance.prev();
|
|
91
|
+
setCurrentDate(calendarInstance.getDate().toDate());
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const handleNext = () => {
|
|
96
|
+
const calendarInstance = (calendarRef.current as any)?.getInstance?.();
|
|
97
|
+
if (calendarInstance) {
|
|
98
|
+
calendarInstance.next();
|
|
99
|
+
setCurrentDate(calendarInstance.getDate().toDate());
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const handleToday = () => {
|
|
104
|
+
const calendarInstance = (calendarRef.current as any)?.getInstance?.();
|
|
105
|
+
if (calendarInstance) {
|
|
106
|
+
calendarInstance.today();
|
|
107
|
+
setCurrentDate(calendarInstance.getDate().toDate());
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const handleViewChange = (newView: 'month' | 'week' | 'day') => {
|
|
112
|
+
setView(newView);
|
|
113
|
+
const calendarInstance = (calendarRef.current as any)?.getInstance?.();
|
|
114
|
+
if (calendarInstance) {
|
|
115
|
+
calendarInstance.changeView(newView);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const formatCurrentDate = () => {
|
|
120
|
+
return currentDate.toLocaleDateString('de-DE', {
|
|
121
|
+
month: 'long',
|
|
122
|
+
year: 'numeric'
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const formatDateTime = (dateStr: string) => {
|
|
127
|
+
try {
|
|
128
|
+
return format(new Date(dateStr), "dd. MMMM yyyy, HH:mm 'Uhr'", {locale: de});
|
|
129
|
+
} catch {
|
|
130
|
+
return dateStr;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<>
|
|
136
|
+
<Card className="mt-6">
|
|
137
|
+
<CardHeader>
|
|
138
|
+
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
|
139
|
+
<CardTitle className="flex items-center gap-2">
|
|
140
|
+
<CalendarIcon className="h-5 w-5"/>
|
|
141
|
+
Kalender
|
|
142
|
+
</CardTitle>
|
|
143
|
+
|
|
144
|
+
<div className="flex items-center gap-2">
|
|
145
|
+
{/* View Toggle */}
|
|
146
|
+
<div className="flex rounded-md border overflow-hidden">
|
|
147
|
+
<Button
|
|
148
|
+
variant={view === 'month' ? 'default' : 'ghost'}
|
|
149
|
+
size="sm"
|
|
150
|
+
onClick={() => handleViewChange('month')}
|
|
151
|
+
className="rounded-none"
|
|
152
|
+
>
|
|
153
|
+
Monat
|
|
154
|
+
</Button>
|
|
155
|
+
<Button
|
|
156
|
+
variant={view === 'week' ? 'default' : 'ghost'}
|
|
157
|
+
size="sm"
|
|
158
|
+
onClick={() => handleViewChange('week')}
|
|
159
|
+
className="rounded-none border-x"
|
|
160
|
+
>
|
|
161
|
+
Woche
|
|
162
|
+
</Button>
|
|
163
|
+
<Button
|
|
164
|
+
variant={view === 'day' ? 'default' : 'ghost'}
|
|
165
|
+
size="sm"
|
|
166
|
+
onClick={() => handleViewChange('day')}
|
|
167
|
+
className="rounded-none"
|
|
168
|
+
>
|
|
169
|
+
Tag
|
|
170
|
+
</Button>
|
|
171
|
+
</div>
|
|
172
|
+
|
|
173
|
+
{/* Navigation */}
|
|
174
|
+
<div className="flex items-center gap-1">
|
|
175
|
+
<Button variant="outline" size="icon" onClick={handlePrev}>
|
|
176
|
+
<ChevronLeft className="h-4 w-4"/>
|
|
177
|
+
</Button>
|
|
178
|
+
<Button variant="outline" size="sm" onClick={handleToday}>
|
|
179
|
+
Heute
|
|
180
|
+
</Button>
|
|
181
|
+
<Button variant="outline" size="icon" onClick={handleNext}>
|
|
182
|
+
<ChevronRight className="h-4 w-4"/>
|
|
183
|
+
</Button>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
<p className="text-lg font-semibold mt-2">{formatCurrentDate()}</p>
|
|
188
|
+
</CardHeader>
|
|
189
|
+
<CardContent>
|
|
190
|
+
<div className="calendar-wrapper" style={{height: '600px'}}>
|
|
191
|
+
<Calendar
|
|
192
|
+
ref={calendarRef}
|
|
193
|
+
height="100%"
|
|
194
|
+
view={view}
|
|
195
|
+
calendars={calendars}
|
|
196
|
+
events={events}
|
|
197
|
+
month={{
|
|
198
|
+
dayNames: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
|
|
199
|
+
startDayOfWeek: 1,
|
|
200
|
+
}}
|
|
201
|
+
week={{
|
|
202
|
+
dayNames: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
|
|
203
|
+
startDayOfWeek: 1,
|
|
204
|
+
hourStart: 6,
|
|
205
|
+
hourEnd: 22,
|
|
206
|
+
taskView: false,
|
|
207
|
+
eventView: ['time'],
|
|
208
|
+
}}
|
|
209
|
+
usageStatistics={false}
|
|
210
|
+
isReadOnly={true}
|
|
211
|
+
onClickEvent={handleClickEvent}
|
|
212
|
+
template={{
|
|
213
|
+
time(event: { title: string, body: string, raw: { email: string, phone: string } }) {
|
|
214
|
+
const emailIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:inline;vertical-align:middle;margin-right:3px"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>`;
|
|
215
|
+
const phoneIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:inline;vertical-align:middle;margin-right:3px"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12 19.79 19.79 0 0 1 1.61 3.4 2 2 0 0 1 3.6 1.22h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 8.78a16 16 0 0 0 6.29 6.29l.96-.96a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>`;
|
|
216
|
+
return `<span style="font-size: 12px;">
|
|
217
|
+
<div><b>${event.title}</b></div><br/>
|
|
218
|
+
<div>Anlass / Beschreibung: ${event.body}</div>
|
|
219
|
+
${event.raw?.email ? `<div>${emailIcon}${event.raw.email}</div>` : ''}
|
|
220
|
+
${event.raw?.phone ? `<div>${phoneIcon}${event.raw.phone}</div>` : ''}
|
|
221
|
+
</span>`;
|
|
222
|
+
},
|
|
223
|
+
allday(event: { title: string }) {
|
|
224
|
+
return `<span style="font-size: 12px;">${event.title}</span>`;
|
|
225
|
+
},
|
|
226
|
+
timegridDisplayPrimaryTime({time}: { time: Date }) {
|
|
227
|
+
return `${time.getHours().toString().padStart(2, '0')}:${time.getMinutes().toString().padStart(2, '0')}`;
|
|
228
|
+
},
|
|
229
|
+
timegridDisplayTime({time}: { time: Date }) {
|
|
230
|
+
return `${time.getHours().toString().padStart(2, '0')}:${time.getMinutes().toString().padStart(2, '0')}`;
|
|
231
|
+
},
|
|
232
|
+
}}
|
|
233
|
+
/>
|
|
234
|
+
</div>
|
|
235
|
+
</CardContent>
|
|
236
|
+
</Card>
|
|
237
|
+
|
|
238
|
+
<Dialog open={!!selectedEntry} onOpenChange={(open) => !open && setSelectedEntry(null)}>
|
|
239
|
+
<DialogContent>
|
|
240
|
+
<DialogHeader>
|
|
241
|
+
<DialogTitle>{selectedEntry?.title}</DialogTitle>
|
|
242
|
+
</DialogHeader>
|
|
243
|
+
{selectedEntry && (
|
|
244
|
+
<div className="space-y-4 pt-2">
|
|
245
|
+
<div className="flex items-start gap-3">
|
|
246
|
+
<Clock className="h-4 w-4 mt-0.5 text-muted-foreground shrink-0"/>
|
|
247
|
+
<div className="text-sm">
|
|
248
|
+
<p>{formatDateTime(selectedEntry.start)}</p>
|
|
249
|
+
<p className="text-muted-foreground">bis {formatDateTime(selectedEntry.end)}</p>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
{selectedEntry.description && (
|
|
253
|
+
<div className="flex items-start gap-3">
|
|
254
|
+
<AlignLeft className="h-4 w-4 mt-0.5 text-muted-foreground shrink-0"/>
|
|
255
|
+
<p className="text-sm">{selectedEntry.description}</p>
|
|
256
|
+
</div>
|
|
257
|
+
)}
|
|
258
|
+
{selectedEntry.email && (
|
|
259
|
+
<div className="flex items-center gap-3">
|
|
260
|
+
<Mail className="h-4 w-4 text-muted-foreground shrink-0"/>
|
|
261
|
+
<a href={`mailto:${selectedEntry.email}`}
|
|
262
|
+
className="text-sm text-primary hover:underline">
|
|
263
|
+
{selectedEntry.email}
|
|
264
|
+
</a>
|
|
265
|
+
</div>
|
|
266
|
+
)}
|
|
267
|
+
{selectedEntry.phone && (
|
|
268
|
+
<div className="flex items-center gap-3">
|
|
269
|
+
<Phone className="h-4 w-4 text-muted-foreground shrink-0"/>
|
|
270
|
+
<a href={`tel:${selectedEntry.phone}`}
|
|
271
|
+
className="text-sm text-primary hover:underline">
|
|
272
|
+
{selectedEntry.phone}
|
|
273
|
+
</a>
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
276
|
+
<div className="flex gap-3 pt-2">
|
|
277
|
+
<Button
|
|
278
|
+
className="flex-1 bg-green-600 hover:bg-green-700 text-white"
|
|
279
|
+
onClick={() => {
|
|
280
|
+
onShowUp?.(selectedEntry.id);
|
|
281
|
+
setSelectedEntry(null);
|
|
282
|
+
}}
|
|
283
|
+
>
|
|
284
|
+
Sind gekommen
|
|
285
|
+
</Button>
|
|
286
|
+
<Button
|
|
287
|
+
className="flex-1 bg-red-600 hover:bg-red-700 text-white"
|
|
288
|
+
onClick={() => {
|
|
289
|
+
onNoShow?.(selectedEntry.id);
|
|
290
|
+
setSelectedEntry(null);
|
|
291
|
+
}}
|
|
292
|
+
>
|
|
293
|
+
Sind nicht gekommen
|
|
294
|
+
</Button>
|
|
295
|
+
</div>
|
|
296
|
+
</div>
|
|
297
|
+
)}
|
|
298
|
+
</DialogContent>
|
|
299
|
+
</Dialog>
|
|
300
|
+
</>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
import { Sidebar } from "./Sidebar";
|
|
3
|
+
import { Header } from "./Header";
|
|
4
|
+
|
|
5
|
+
interface DashboardLayoutProps {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
title: string;
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function DashboardLayout({ children, title, subtitle }: DashboardLayoutProps) {
|
|
12
|
+
return (
|
|
13
|
+
<div className="min-h-screen bg-background">
|
|
14
|
+
<Sidebar />
|
|
15
|
+
<div className="pl-64">
|
|
16
|
+
<Header title={title} subtitle={subtitle} />
|
|
17
|
+
<main className="p-6">{children}</main>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Bell, Search } from "lucide-react";
|
|
2
|
+
import { Button } from "@/components/ui/button";
|
|
3
|
+
import { Input } from "@/components/ui/input";
|
|
4
|
+
|
|
5
|
+
interface HeaderProps {
|
|
6
|
+
title: string;
|
|
7
|
+
subtitle?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Header({ title, subtitle }: HeaderProps) {
|
|
11
|
+
return (
|
|
12
|
+
<header className="sticky top-0 z-40 flex h-16 items-center justify-between border-b border-border bg-background/95 px-6 backdrop-blur supports-[backdrop-filter]:bg-background/60">
|
|
13
|
+
<div>
|
|
14
|
+
<h1 className="text-xl font-semibold text-foreground">{title}</h1>
|
|
15
|
+
{subtitle && (
|
|
16
|
+
<p className="text-sm text-muted-foreground">{subtitle}</p>
|
|
17
|
+
)}
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div className="flex items-center gap-4">
|
|
21
|
+
<div className="relative hidden md:block">
|
|
22
|
+
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
|
|
23
|
+
<Input
|
|
24
|
+
type="search"
|
|
25
|
+
placeholder="Search..."
|
|
26
|
+
className="w-64 pl-9"
|
|
27
|
+
/>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<Button variant="ghost" size="icon" className="relative">
|
|
31
|
+
<Bell className="h-5 w-5" />
|
|
32
|
+
<span className="absolute -right-0.5 -top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-primary text-[10px] font-medium text-primary-foreground">
|
|
33
|
+
3
|
|
34
|
+
</span>
|
|
35
|
+
</Button>
|
|
36
|
+
|
|
37
|
+
<div className="flex items-center gap-3">
|
|
38
|
+
<div className="h-9 w-9 rounded-full bg-primary/10 flex items-center justify-center">
|
|
39
|
+
<span className="text-sm font-medium text-primary">JD</span>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</header>
|
|
44
|
+
);
|
|
45
|
+
}
|