@@ -165,23 +159,39 @@ export default function DataPreview({ data, loading, formData }: DataPreviewProp
{formData && formData.startDate === '2025-01-01' && (
-
+ {filling ? (
+ <>
+
+ Vyplňování...
+ >
+ ) : (
+ <>
+
+ Vyplnit na web
+ >
+ )}
+
{fillResult && (
-
- {fillResult.dry_run ? 'Výsledek DRY RUN:' : 'Výsledek vyplňování:'}
+
+ {fillResult.dry_run ? (
+ <>
+
+ Výsledek DRY RUN:
+ >
+ ) : (
+ <>
+
+ Výsledek vyplňování:
+ >
+ )}
Měsíc: {fillResult.month}
@@ -205,18 +215,23 @@ export default function DataPreview({ data, loading, formData }: DataPreviewProp
>
)}
{fillResult.dry_run && (
-
- ⚠️ DRY RUN MODE - Data nebyla odeslána na web
+
+
+ DRY RUN MODE - Data nebyla odeslána na web
)}
{!fillResult.dry_run && fillResult.updates_successful > 0 && (
-
- ✅ Data byla úspěšně vyplněna. Nyní zkontrolujte na webu a klikněte "Uzavřít měsíc" ručně.
+
+
+ Data byla úspěšně vyplněna. Nyní zkontrolujte na webu a klikněte "Uzavřít měsíc" ručně.
)}
{!fillResult.dry_run && fillResult.errors && fillResult.errors.length > 0 && (
-
Chyby:
+
+
+ Chyby:
+
{fillResult.errors.map((err: any, idx: number) => (
• {err.type} řádek {err.row}: {err.error}
))}
@@ -227,7 +242,7 @@ export default function DataPreview({ data, loading, formData }: DataPreviewProp
)}
)}
-
-
+
+
)
}
diff --git a/frontend/app/components/JourneyForm.tsx b/frontend/app/components/JourneyForm.tsx
index 325e3e5..83c3b5a 100644
--- a/frontend/app/components/JourneyForm.tsx
+++ b/frontend/app/components/JourneyForm.tsx
@@ -2,6 +2,11 @@
import { useState } from 'react'
import API_URL from '@/lib/api'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
+import { Calculator, Download } from 'lucide-react'
interface JourneyFormProps {
onDataCalculated: (data: any) => void
@@ -99,130 +104,114 @@ export default function JourneyForm({ onDataCalculated, setLoading, onFormDataCh
}
return (
-
+
+
)
}
diff --git a/frontend/app/globals.css b/frontend/app/globals.css
index 71b4bac..0187eaa 100644
--- a/frontend/app/globals.css
+++ b/frontend/app/globals.css
@@ -1,9 +1,26 @@
@import "tailwindcss";
@theme {
- --color-primary: #2563eb;
- --color-primary-dark: #1d4ed8;
- --radius-card: 12px;
+ --color-background: 0 0% 100%;
+ --color-foreground: 222.2 84% 4.9%;
+ --color-card: 0 0% 100%;
+ --color-card-foreground: 222.2 84% 4.9%;
+ --color-popover: 0 0% 100%;
+ --color-popover-foreground: 222.2 84% 4.9%;
+ --color-primary: 221.2 83.2% 53.3%;
+ --color-primary-foreground: 210 40% 98%;
+ --color-secondary: 210 40% 96.1%;
+ --color-secondary-foreground: 222.2 47.4% 11.2%;
+ --color-muted: 210 40% 96.1%;
+ --color-muted-foreground: 215.4 16.3% 46.9%;
+ --color-accent: 210 40% 96.1%;
+ --color-accent-foreground: 222.2 47.4% 11.2%;
+ --color-destructive: 0 84.2% 60.2%;
+ --color-destructive-foreground: 210 40% 98%;
+ --color-border: 214.3 31.8% 91.4%;
+ --color-input: 214.3 31.8% 91.4%;
+ --color-ring: 221.2 83.2% 53.3%;
+ --radius: 0.5rem;
}
* {
@@ -16,5 +33,5 @@ body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
- color: #1f2937;
+ color: hsl(var(--color-foreground));
}
diff --git a/frontend/components.json b/frontend/components.json
new file mode 100644
index 0000000..31772b3
--- /dev/null
+++ b/frontend/components.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "default",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.js",
+ "css": "app/globals.css",
+ "baseColor": "slate",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils"
+ }
+}
diff --git a/frontend/components/ui/button.tsx b/frontend/components/ui/button.tsx
new file mode 100644
index 0000000..451c492
--- /dev/null
+++ b/frontend/components/ui/button.tsx
@@ -0,0 +1,57 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2",
+ sm: "h-8 rounded-md px-3 text-xs",
+ lg: "h-10 rounded-md px-8",
+ icon: "h-9 w-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes
,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/frontend/components/ui/card.tsx b/frontend/components/ui/card.tsx
new file mode 100644
index 0000000..4ddac08
--- /dev/null
+++ b/frontend/components/ui/card.tsx
@@ -0,0 +1,76 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/frontend/components/ui/input.tsx b/frontend/components/ui/input.tsx
new file mode 100644
index 0000000..8b4ed30
--- /dev/null
+++ b/frontend/components/ui/input.tsx
@@ -0,0 +1,22 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Input = React.forwardRef>(
+ ({ className, type, ...props }, ref) => {
+ return (
+
+ )
+ }
+)
+Input.displayName = "Input"
+
+export { Input }
diff --git a/frontend/components/ui/label.tsx b/frontend/components/ui/label.tsx
new file mode 100644
index 0000000..594b7cc
--- /dev/null
+++ b/frontend/components/ui/label.tsx
@@ -0,0 +1,24 @@
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const labelVariants = cva(
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
+)
+
+const Label = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, ...props }, ref) => (
+
+))
+Label.displayName = LabelPrimitive.Root.displayName
+
+export { Label }
diff --git a/frontend/lib/utils.ts b/frontend/lib/utils.ts
new file mode 100644
index 0000000..2cb92be
--- /dev/null
+++ b/frontend/lib/utils.ts
@@ -0,0 +1,6 @@
+import { type ClassValue, clsx } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 3c3e592..f8806a8 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9,14 +9,20 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
+ "@radix-ui/react-label": "^2.1.7",
+ "@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/postcss": "^4.1.14",
"@types/node": "^24.7.1",
"@types/react": "^19.2.2",
"autoprefixer": "^10.4.21",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.545.0",
"next": "^15.5.4",
"postcss": "^8.5.6",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.3"
}
@@ -662,6 +668,85 @@
"node": ">= 10"
}
},
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.7.tgz",
+ "integrity": "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@swc/helpers": {
"version": "0.5.15",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
@@ -1058,12 +1143,33 @@
"node": ">=18"
}
},
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
@@ -1363,6 +1469,15 @@
"url": "https://opencollective.com/parcel"
}
},
+ "node_modules/lucide-react": {
+ "version": "0.545.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.545.0.tgz",
+ "integrity": "sha512-7r1/yUuflQDSt4f1bpn5ZAocyIxcTyVyBBChSVtBKn5M+392cPmI5YJMWOJKk/HUWGm5wg83chlAZtCcGbEZtw==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.19",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz",
@@ -1661,6 +1776,16 @@
}
}
},
+ "node_modules/tailwind-merge": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz",
+ "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
"node_modules/tailwindcss": {
"version": "4.1.14",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.14.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 0cdd0b8..e0dbbe5 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -13,14 +13,20 @@
"license": "ISC",
"description": "",
"dependencies": {
+ "@radix-ui/react-label": "^2.1.7",
+ "@radix-ui/react-slot": "^1.2.3",
"@tailwindcss/postcss": "^4.1.14",
"@types/node": "^24.7.1",
"@types/react": "^19.2.2",
"autoprefixer": "^10.4.21",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.545.0",
"next": "^15.5.4",
"postcss": "^8.5.6",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "tailwind-merge": "^3.3.1",
"tailwindcss": "^4.1.14",
"typescript": "^5.9.3"
}
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
index 46b2cf4..0a308d9 100644
--- a/frontend/tailwind.config.js
+++ b/frontend/tailwind.config.js
@@ -6,7 +6,48 @@ module.exports = {
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
- extend: {},
+ extend: {
+ colors: {
+ border: "hsl(var(--color-border))",
+ input: "hsl(var(--color-input))",
+ ring: "hsl(var(--color-ring))",
+ background: "hsl(var(--color-background))",
+ foreground: "hsl(var(--color-foreground))",
+ primary: {
+ DEFAULT: "hsl(var(--color-primary))",
+ foreground: "hsl(var(--color-primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--color-secondary))",
+ foreground: "hsl(var(--color-secondary-foreground))",
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--color-destructive))",
+ foreground: "hsl(var(--color-destructive-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--color-muted))",
+ foreground: "hsl(var(--color-muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--color-accent))",
+ foreground: "hsl(var(--color-accent-foreground))",
+ },
+ popover: {
+ DEFAULT: "hsl(var(--color-popover))",
+ foreground: "hsl(var(--color-popover-foreground))",
+ },
+ card: {
+ DEFAULT: "hsl(var(--color-card))",
+ foreground: "hsl(var(--color-card-foreground))",
+ },
+ },
+ borderRadius: {
+ lg: "var(--radius)",
+ md: "calc(var(--radius) - 2px)",
+ sm: "calc(var(--radius) - 4px)",
+ },
+ },
},
plugins: [],
}