diff --git a/src/admin/pages/Attendance.tsx b/src/admin/pages/Attendance.tsx index 93c0102..6497629 100644 --- a/src/admin/pages/Attendance.tsx +++ b/src/admin/pages/Attendance.tsx @@ -1,7 +1,6 @@ import { useState, useEffect, useRef } from "react"; import { useQuery } from "@tanstack/react-query"; import { Link as RouterLink } from "react-router-dom"; -import { motion } from "framer-motion"; import { parseISO, isValid } from "date-fns"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; @@ -30,6 +29,7 @@ import { StatusChip, TextField, ProgressBar, + PageEnter, EmptyState, LoadingState, type DataColumn, @@ -614,24 +614,18 @@ export default function Attendance() { ]; return ( - - - - Docházka - - {new Date().toLocaleDateString("cs-CZ", { - weekday: "long", - day: "numeric", - month: "long", - year: "numeric", - })} - - - + + + Docházka + + {new Date().toLocaleDateString("cs-CZ", { + weekday: "long", + day: "numeric", + month: "long", + year: "numeric", + })} + + {/* Left Column - Clock In/Out */} - + <> )} - + {/* Right Column - Stats & Quick Links */} - - - {/* Leave Balance Card */} + + {/* Leave Balance Card */} + + {vacationDaysRemaining}{" "} + + {czechPlural(vacationDaysRemaining, "den", "dny", "dnů")} + {vacationHoursRemaining > 0 && ` ${vacationHoursRemaining}h`} + + + } + footer={ + + + Celkem: {leaveBalance.vacation_total}h + Čerpáno: {leaveBalance.vacation_used}h + + 0 + ? (leaveBalance.vacation_remaining / + leaveBalance.vacation_total) * + 100 + : 0 + } + color="success" + height={6} + /> + + } + /> + + {/* Monthly Fund Card */} + {data.monthly_fund && ( - {vacationDaysRemaining}{" "} - - {czechPlural(vacationDaysRemaining, "den", "dny", "dnů")} - {vacationHoursRemaining > 0 && - ` ${vacationHoursRemaining}h`} - - - } + icon={CalendarIcon} + color="info" + label={data.monthly_fund.month_name} + value={`${data.monthly_fund.worked}h / ${data.monthly_fund.fund}h`} footer={ - Celkem: {leaveBalance.vacation_total}h - Čerpáno: {leaveBalance.vacation_used}h + Odpracováno: {data.monthly_fund.worked}h + {data.monthly_fund.overtime > 0 ? ( + + Přesčas: +{data.monthly_fund.overtime}h + + ) : ( + Zbývá: {data.monthly_fund.remaining}h + )} 0 - ? (leaveBalance.vacation_remaining / - leaveBalance.vacation_total) * - 100 + data.monthly_fund.fund > 0 + ? Math.min( + 100, + (data.monthly_fund.covered / + data.monthly_fund.fund) * + 100, + ) : 0 } - color="success" + color={getFundBarColor(data.monthly_fund)} height={6} /> + {data.monthly_fund.leave_hours > 0 && ( + + {"Pokryto: "} + {data.monthly_fund.covered}h (práce{" "} + {data.monthly_fund.worked}h + {data.monthly_fund.vacation_hours > 0 && + ` + dovolená ${data.monthly_fund.vacation_hours}h`} + {data.monthly_fund.sick_hours > 0 && + ` + nemoc ${data.monthly_fund.sick_hours}h`} + {data.monthly_fund.unpaid_hours > 0 && + ` + neplacené ${data.monthly_fund.unpaid_hours}h`} + ) + + )} } /> + )} - {/* Monthly Fund Card */} - {data.monthly_fund && ( - - - Odpracováno: {data.monthly_fund.worked}h - {data.monthly_fund.overtime > 0 ? ( - - Přesčas: +{data.monthly_fund.overtime}h - - ) : ( - Zbývá: {data.monthly_fund.remaining}h - )} - - 0 - ? Math.min( - 100, - (data.monthly_fund.covered / - data.monthly_fund.fund) * - 100, - ) - : 0 - } - color={getFundBarColor(data.monthly_fund)} - height={6} - /> - {data.monthly_fund.leave_hours > 0 && ( - - {"Pokryto: "} - {data.monthly_fund.covered}h (práce{" "} - {data.monthly_fund.worked}h - {data.monthly_fund.vacation_hours > 0 && - ` + dovolená ${data.monthly_fund.vacation_hours}h`} - {data.monthly_fund.sick_hours > 0 && - ` + nemoc ${data.monthly_fund.sick_hours}h`} - {data.monthly_fund.unpaid_hours > 0 && - ` + neplacené ${data.monthly_fund.unpaid_hours}h`} - ) - - )} - - } + {/* Sick Leave Card */} + + + {/* Quick Links */} + + + Rychlé odkazy + + + - )} - - {/* Sick Leave Card */} - - - {/* Quick Links */} - - - Rychlé odkazy - - + + {hasPermission("attendance.manage") && ( + )} + {hasPermission("attendance.balances") && ( - {hasPermission("attendance.manage") && ( - - )} - {hasPermission("attendance.balances") && ( - - )} - - - - + )} + + + {/* Leave Modal */} @@ -1273,6 +1256,6 @@ export default function Attendance() { cancelText="Zrušit" confirmVariant="primary" /> - + ); } diff --git a/src/admin/pages/AttendanceAdmin.tsx b/src/admin/pages/AttendanceAdmin.tsx index a14ba23..dc99121 100644 --- a/src/admin/pages/AttendanceAdmin.tsx +++ b/src/admin/pages/AttendanceAdmin.tsx @@ -1,4 +1,3 @@ -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import { useAlert } from "../context/AlertContext"; @@ -18,6 +17,7 @@ import { FilterBar, LoadingState, MonthField, + PageEnter, Select, StatCard, StatusChip, @@ -165,262 +165,236 @@ export default function AttendanceAdmin() { const hasTotals = Object.keys(data.user_totals).length > 0; return ( - - + + Správa docházky + + {hasData && ( + + )} + + + + + + {/* Filters */} + + + + setMonth(val)} /> + + + + + ({ - value: String(user.id), - label: user.name, - })), - ]} - /> - - - - - - {/* User Totals (KPI cards) */} - {hasTotals && ( - - - {Object.entries(data.user_totals).map(([uid, userData]) => { - const ut = userData as UserTotalData; - const balance = data.leave_balances[uid]; - const statColor: StatCardColor = ut.working - ? "success" - : "default"; - return ( - { + const ut = userData as UserTotalData; + const balance = data.leave_balances[uid]; + const statColor: StatCardColor = ut.working ? "success" : "default"; + return ( + + {formatMinutes(ut.minutes)} + + + } + color={statColor} + footer={ + + {/* Leave-type badges */} - {formatMinutes(ut.minutes)} - + {ut.vacation_hours > 0 && ( + + )} + {ut.sick_hours > 0 && ( + + )} + {ut.svatek !== undefined && ut.svatek > 0 && ( + + )} + {ut.unpaid_hours > 0 && ( + + )} - } - color={statColor} - footer={ - - {/* Leave-type badges */} - - {ut.vacation_hours > 0 && ( - - )} - {ut.sick_hours > 0 && ( - - )} - {ut.svatek !== undefined && ut.svatek > 0 && ( - - )} - {ut.unpaid_hours > 0 && ( - - )} - - {/* Fond usage */} - {ut.fund !== null && - (() => { - // Fond "used" = real_worked + vacation + - // worked_holiday + free_holiday (everything the user - // actually got paid for this month). - const fondUsed = getFondUsed(ut); - const fundVal = ut.fund ?? 0; - const delta = fondUsed - fundVal; - const pct = Math.min( - 100, - (fondUsed / (ut.fund || 1)) * 100, - ); - return ( - - { + // Fond "used" = real_worked + vacation + + // worked_holiday + free_holiday (everything the user + // actually got paid for this month). + const fondUsed = getFondUsed(ut); + const fundVal = ut.fund ?? 0; + const delta = fondUsed - fundVal; + const pct = Math.min( + 100, + (fondUsed / (ut.fund || 1)) * 100, + ); + return ( + + + + Fond: {formatHoursDecimal(fondUsed * 60)}h /{" "} + {formatHoursDecimal(fundVal * 60)}h + + {delta > 0 && ( - Fond: {formatHoursDecimal(fondUsed * 60)}h /{" "} - {formatHoursDecimal(fundVal * 60)}h + +{formatHoursDecimal(delta * 60)}h - {delta > 0 && ( - - +{formatHoursDecimal(delta * 60)}h - - )} - {delta < 0 && ( - - -{formatHoursDecimal(Math.abs(delta) * 60)}h - - )} - - + )} + {delta < 0 && ( + + -{formatHoursDecimal(Math.abs(delta) * 60)}h + + )} - ); - })()} + + + ); + })()} - {/* Remaining vacation */} - - {balance ? ( - - Zbývá dovolené:{" "} - {balance.vacation_remaining.toFixed(1)}h /{" "} - {balance.vacation_total}h - - ) : ( - - — - - )} - + {/* Remaining vacation */} + + {balance ? ( + + Zbývá dovolené:{" "} + {balance.vacation_remaining.toFixed(1)}h /{" "} + {balance.vacation_total}h + + ) : ( + + — + + )} - } - /> - ); - })} - - + + } + /> + ); + })} + )} {/* Records Table */} - - - setDeleteConfirm({ show: true, record })} - /> - - + + setDeleteConfirm({ show: true, record })} + /> + {/* Modals */} - + ); } diff --git a/src/admin/pages/AttendanceBalances.tsx b/src/admin/pages/AttendanceBalances.tsx index 9555f2e..08017eb 100644 --- a/src/admin/pages/AttendanceBalances.tsx +++ b/src/admin/pages/AttendanceBalances.tsx @@ -25,6 +25,7 @@ import { Select, StatusChip, PageHeader, + PageEnter, EmptyState, LoadingState, ProgressBar, @@ -392,7 +393,7 @@ export default function AttendanceBalances() { ]; return ( - + - + ); } diff --git a/src/admin/pages/AttendanceCreate.tsx b/src/admin/pages/AttendanceCreate.tsx index 9de2318..e2a095c 100644 --- a/src/admin/pages/AttendanceCreate.tsx +++ b/src/admin/pages/AttendanceCreate.tsx @@ -3,7 +3,6 @@ import { useQuery } from "@tanstack/react-query"; import { Link as RouterLink } from "react-router-dom"; import { useNavigate } from "react-router-dom"; import Box from "@mui/material/Box"; -import { motion } from "framer-motion"; import { userListOptions } from "../lib/queries/users"; import { useAlert } from "../context/AlertContext"; @@ -17,6 +16,7 @@ import { DateField, Field, LoadingState, + PageEnter, PageHeader, Select, TextField, @@ -118,230 +118,214 @@ export default function AttendanceCreate() { } return ( - - - + + ← Zpět na správu + + } + /> + + + + {/* Employee + Shift date */} + + + setForm({ ...form, leave_type: val })} + options={[ + { value: "work", label: "Práce" }, + { value: "vacation", label: "Dovolená" }, + { value: "sick", label: "Nemoc" }, + { value: "unpaid", label: "Neplacené volno" }, + ]} + /> + + + {/* Leave hours — shown only for non-work types */} + {!isWorkType && ( + + + setForm({ + ...form, + leave_hours: parseFloat(e.target.value), + }) + } + slotProps={{ htmlInput: { min: 0.5, max: 24, step: 0.5 } }} + /> + + )} + + {/* Work-type time fields */} + {isWorkType && ( + <> + {/* Arrival */} + + + setForm({ ...form, arrival_date: val })} + /> + + + setForm({ ...form, arrival_time: val })} + /> + + + + {/* Break start */} + + + + setForm({ ...form, break_start_date: val }) + } + /> + + + + setForm({ ...form, break_start_time: val }) + } + /> + + + + {/* Break end */} + + + + setForm({ ...form, break_end_date: val }) + } + /> + + + + setForm({ ...form, break_end_time: val }) + } + /> + + + + {/* Departure */} + + + + setForm({ ...form, departure_date: val }) + } + /> + + + + setForm({ ...form, departure_time: val }) + } + /> + + + + )} + + {/* Notes */} + + setForm({ ...form, notes: e.target.value })} + /> + + + {/* Actions */} + + - } - /> - - - - - - {/* Employee + Shift date */} - - - setForm({ ...form, leave_type: val })} - options={[ - { value: "work", label: "Práce" }, - { value: "vacation", label: "Dovolená" }, - { value: "sick", label: "Nemoc" }, - { value: "unpaid", label: "Neplacené volno" }, - ]} - /> - - - {/* Leave hours — shown only for non-work types */} - {!isWorkType && ( - - - setForm({ - ...form, - leave_hours: parseFloat(e.target.value), - }) - } - slotProps={{ htmlInput: { min: 0.5, max: 24, step: 0.5 } }} - /> - - )} - - {/* Work-type time fields */} - {isWorkType && ( - <> - {/* Arrival */} - - - - setForm({ ...form, arrival_date: val }) - } - /> - - - - setForm({ ...form, arrival_time: val }) - } - /> - - - - {/* Break start */} - - - - setForm({ ...form, break_start_date: val }) - } - /> - - - - setForm({ ...form, break_start_time: val }) - } - /> - - - - {/* Break end */} - - - - setForm({ ...form, break_end_date: val }) - } - /> - - - - setForm({ ...form, break_end_time: val }) - } - /> - - - - {/* Departure */} - - - - setForm({ ...form, departure_date: val }) - } - /> - - - - setForm({ ...form, departure_time: val }) - } - /> - - - - )} - - {/* Notes */} - - setForm({ ...form, notes: e.target.value })} - /> - - - {/* Actions */} - - - - - - - + + + ); } diff --git a/src/admin/pages/AttendanceHistory.tsx b/src/admin/pages/AttendanceHistory.tsx index 0d1d9dc..9f9b6d5 100644 --- a/src/admin/pages/AttendanceHistory.tsx +++ b/src/admin/pages/AttendanceHistory.tsx @@ -1,6 +1,5 @@ import { useState, useMemo, useRef } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import { useAuth } from "../context/AuthContext"; @@ -36,6 +35,7 @@ import { ProgressBar, StatusChip, PageHeader, + PageEnter, EmptyState, LoadingState, type DataColumn, @@ -494,196 +494,169 @@ export default function AttendanceHistory() { ]; return ( - - - 0 ? ( - - ) : undefined - } - /> - + + 0 ? ( + + ) : undefined + } + /> {/* Filters */} - - - - - setMonth(val)} /> - - - - + + + + setMonth(val)} /> + + + {/* Monthly Fund Card */} - - - {isPending ? ( - - ) : ( + + {isPending ? ( + + ) : ( + + + + + + + + + - + Fond: {fund.worked}h / {fund.fund}h + + - - - - - + {fund.business_days} prac. dnů + {fund.holiday_count > 0 && ` + ${fund.holiday_count} svátky`} + - - - - Fond: {fund.worked}h / {fund.fund}h - - - {fund.business_days} prac. dnů - {fund.holiday_count > 0 && - ` + ${fund.holiday_count} svátky`} - + + + + {computed.totalMinutes > 0 && ( + + )} + {computed.vacationHours > 0 && ( + + )} + {computed.sickHours > 0 && ( + + )} + {computed.freeHolidayHours > 0 && ( + + )} + {computed.unpaidHours > 0 && ( + + )} - - - - {computed.totalMinutes > 0 && ( - - )} - {computed.vacationHours > 0 && ( - - )} - {computed.sickHours > 0 && ( - - )} - {computed.freeHolidayHours > 0 && ( - - )} - {computed.unpaidHours > 0 && ( - - )} - - - {computed.delta > 0 - ? `Přesčas: +${formatHoursDecimal(computed.delta * 60)}h` - : computed.delta < 0 - ? `Zbývá: ${formatHoursDecimal(-computed.delta * 60)}h` - : "Fond splněn"} - - + {computed.delta > 0 + ? `Přesčas: +${formatHoursDecimal(computed.delta * 60)}h` + : computed.delta < 0 + ? `Zbývá: ${formatHoursDecimal(-computed.delta * 60)}h` + : "Fond splněn"} + - )} - - + + )} + {/* Records Table */} - - - {isPending ? ( - - ) : ( - - columns={columns} - rows={records} - rowKey={(record) => record.id} - empty={ - - } - /> - )} - - + + {isPending ? ( + + ) : ( + + columns={columns} + rows={records} + rowKey={(record) => record.id} + empty={} + /> + )} + {/* Hidden Print Content */} {records.length > 0 && ( @@ -888,7 +861,7 @@ export default function AttendanceHistory() { )} - + ); } diff --git a/src/admin/pages/AttendanceLocation.tsx b/src/admin/pages/AttendanceLocation.tsx index 8ece091..b8ed5e5 100644 --- a/src/admin/pages/AttendanceLocation.tsx +++ b/src/admin/pages/AttendanceLocation.tsx @@ -4,7 +4,6 @@ import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; import { useNavigate, useParams, Link as RouterLink } from "react-router-dom"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; @@ -16,7 +15,7 @@ import { attendanceLocationOptions, type LocationRecord, } from "../lib/queries/attendance"; -import { Button, Card, LoadingState, PageHeader } from "../ui"; +import { Button, Card, LoadingState, PageEnter, PageHeader } from "../ui"; const BackIcon = ( + {/* Header */} - - - Zpět na správu - - } - /> - + + Zpět na správu + + } + /> {/* Info + map card */} - - - - {record.user_name} — {formatDate(record.shift_date)} - + + + {record.user_name} — {formatDate(record.shift_date)} + - {/* Leaflet map — kept entirely as-is */} - {hasAnyLocation && ( -
- )} + {/* Leaflet map — kept entirely as-is */} + {hasAnyLocation && ( +
+ )} - {/* Location detail grid */} + {/* Location detail grid */} + + {/* Arrival */} - {/* Arrival */} + + Příchod + + + {record.arrival_time + ? formatDatetimeLocal(record.arrival_time) + : "—"} + + {hasArrivalLocation ? ( + <> + + {record.arrival_address || Adresa nezjištěna} + + + GPS: {record.arrival_lat}, {record.arrival_lng} + {record.arrival_accuracy && + ` (přesnost: ${Math.round(Number(record.arrival_accuracy))}m)`} + + + + ) : ( + + Poloha nebyla zaznamenána + + )} + + + {/* Departure */} + {(hasDepartureLocation || record.departure_time) && ( - Příchod + Odchod - {record.arrival_time - ? formatDatetimeLocal(record.arrival_time) + {record.departure_time + ? formatDatetimeLocal(record.departure_time) : "—"} - {hasArrivalLocation ? ( + {hasDepartureLocation ? ( <> - {record.arrival_address || Adresa nezjištěna} + {record.departure_address || Adresa nezjištěna} - GPS: {record.arrival_lat}, {record.arrival_lng} - {record.arrival_accuracy && - ` (přesnost: ${Math.round(Number(record.arrival_accuracy))}m)`} + GPS: {record.departure_lat}, {record.departure_lng} + {record.departure_accuracy && + ` (přesnost: ${Math.round(Number(record.departure_accuracy))}m)`} - - ) : ( - - Poloha nebyla zaznamenána - - )} - - )} - - - - + )} + + + ); } diff --git a/src/admin/pages/AuditLog.tsx b/src/admin/pages/AuditLog.tsx index 678a83d..267c0b4 100644 --- a/src/admin/pages/AuditLog.tsx +++ b/src/admin/pages/AuditLog.tsx @@ -1,6 +1,5 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import { useAuth } from "../context/AuthContext"; @@ -21,6 +20,7 @@ import { TextField, StatusChip, PageHeader, + PageEnter, FilterBar, EmptyState, LoadingState, @@ -267,30 +267,24 @@ export default function AuditLog() { ]; return ( - - - setShowCleanup(true)} - > - Vyčistit - - } - /> - + + setShowCleanup(true)} + > + Vyčistit + + } + /> {/* Cleanup Modal */} - + ); } diff --git a/src/admin/pages/Dashboard.tsx b/src/admin/pages/Dashboard.tsx index c9e9794..eed3c92 100644 --- a/src/admin/pages/Dashboard.tsx +++ b/src/admin/pages/Dashboard.tsx @@ -1,6 +1,5 @@ import { useState, useCallback } from "react"; import { Link as RouterLink } from "react-router-dom"; -import { motion } from "framer-motion"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; @@ -12,7 +11,7 @@ import { dashboardOptions } from "../lib/queries/dashboard"; import { require2FAOptions } from "../lib/queries/settings"; import { getCzechDate } from "../utils/dashboardHelpers"; import { useApiMutation } from "../lib/queries/mutations"; -import { Card, Button, StatusChip } from "../ui"; +import { Card, Button, StatusChip, PageEnter } from "../ui"; import DashKpiCards from "../components/dashboard/DashKpiCards"; import DashQuickActions from "../components/dashboard/DashQuickActions"; import DashActivityFeed from "../components/dashboard/DashActivityFeed"; @@ -221,15 +220,9 @@ export default function Dashboard() { }; return ( - + {/* Header */} - + Vítejte zpět, {user?.fullName || user?.username} @@ -240,75 +233,69 @@ export default function Dashboard() { {/* 2FA Required Banner */} {user?.require2FA && !user?.totpEnabled && ( - - - - - - - - - - - - - - Dvoufaktorové ověření je povinné - - - Administrátor vyžaduje aktivaci 2FA. Dokud ji neaktivujete, - nemáte přístup k ostatním sekcím systému. - - - - + + + + + + + + + Dvoufaktorové ověření je povinné + + + Administrátor vyžaduje aktivaci 2FA. Dokud ji neaktivujete, + nemáte přístup k ostatním sekcím systému. + + - - + + + )} {/* Loading spinner */} @@ -339,10 +326,6 @@ export default function Dashboard() { {/* Main content grid */} {!dashLoading && ( )} - + ); } diff --git a/src/admin/pages/InvoiceDetail.tsx b/src/admin/pages/InvoiceDetail.tsx index 6139784..3be9bb6 100644 --- a/src/admin/pages/InvoiceDetail.tsx +++ b/src/admin/pages/InvoiceDetail.tsx @@ -7,7 +7,6 @@ import { } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import DOMPurify from "dompurify"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; @@ -61,6 +60,7 @@ import { ConfirmDialog, EmptyState, LoadingState, + PageEnter, } from "../ui"; const API_BASE = "/api/admin"; @@ -941,13 +941,9 @@ export default function InvoiceDetail() { return ; } return ( - + {/* Header */} - + - + {/* Info */} - - - - Informace - - - - - {invoice.customer_name || "—"} + + + Informace + + + + + {invoice.customer_name || "—"} + + {invoice.customer && ( + + {invoice.customer.company_id && + `IČ: ${invoice.customer.company_id}`} + {invoice.customer.vat_id && + ` · DIČ: ${invoice.customer.vat_id}`} - {invoice.customer && ( - + + + {invoice.order_id ? ( + - {invoice.customer.company_id && - `IČ: ${invoice.customer.company_id}`} - {invoice.customer.vat_id && - ` · DIČ: ${invoice.customer.vat_id}`} - + {invoice.order_number} + + ) : ( + "—" )} - - - - {invoice.order_id ? ( - - {invoice.order_number} - - ) : ( - "—" - )} - - - - {invoice.currency} - - - - - - {formatDate(invoice.issue_date)} - - - - - {formatDate(invoice.due_date)} - - - - - {formatDate(invoice.tax_date)} + + + + {invoice.currency} + + + + + + {formatDate(invoice.issue_date)} + + + + + {formatDate(invoice.due_date)} + + + + + {formatDate(invoice.tax_date)} + + + + + + {invoice.payment_method} + + + {invoice.invoice_number} + + + + {invoice.issued_by || "—"} + + + + {invoice.paid_date && ( + + + + {formatDate(invoice.paid_date)} - - - - {invoice.payment_method} - - - - - {invoice.invoice_number} - - - - - {invoice.issued_by || "—"} - - - - {invoice.paid_date && ( - - - - {formatDate(invoice.paid_date)} - - - - )} - - + )} + {/* Items (read-only) */} - - - - Položky - - {invoice.items && invoice.items.length > 0 ? ( - - - - - - # - - Popis - - Množství - - - Jednotka - - - Jedn. cena - - - %DPH - - - Celkem - - - - - {invoice.items.map((item, index) => { - const lineSubtotal = - (Number(item.quantity) || 0) * - (Number(item.unit_price) || 0); - const lineVat = Number(invoice.apply_vat) - ? (lineSubtotal * (Number(item.vat_rate) || 0)) / 100 - : 0; - return ( - - - {index + 1} - - - {item.description || "—"} - {item.item_description && ( - - {item.item_description} - - )} - - - {item.quantity}{" "} - {item.unit && ( - - {item.unit} - - )} - - - {item.unit || "—"} - - - {formatCurrency(item.unit_price, invoice.currency)} - - - {Number(invoice.apply_vat) - ? Number(item.vat_rate) - : 0} - % - - - {formatCurrency( - lineSubtotal + lineVat, - invoice.currency, - )} - - - ); - })} - -
-
- ) : ( - - )} + + + Položky + + {invoice.items && invoice.items.length > 0 ? ( + + + + + + # + + Popis + + Množství + + + Jednotka + + + Jedn. cena + + + %DPH + + + Celkem + + + + + {invoice.items.map((item, index) => { + const lineSubtotal = + (Number(item.quantity) || 0) * + (Number(item.unit_price) || 0); + const lineVat = Number(invoice.apply_vat) + ? (lineSubtotal * (Number(item.vat_rate) || 0)) / 100 + : 0; + return ( + + + {index + 1} + + + {item.description || "—"} + {item.item_description && ( + + {item.item_description} + + )} + + + {item.quantity}{" "} + {item.unit && ( + + {item.unit} + + )} + + {item.unit || "—"} + + {formatCurrency(item.unit_price, invoice.currency)} + + + {Number(invoice.apply_vat) + ? Number(item.vat_rate) + : 0} + % + + + {formatCurrency( + lineSubtotal + lineVat, + invoice.currency, + )} + + + ); + })} + +
+
+ ) : ( + + )} + + + + Mezisoučet: + + + {formatCurrency(createTotals.subtotal, invoice.currency)} + + + {Number(invoice.apply_vat) > 0 && + Object.entries(createTotals.vatByRate).map(([rate, amount]) => ( + + + DPH {rate}%: + + + {formatCurrency(amount, invoice.currency)} + + + ))} - - - Mezisoučet: - - - {formatCurrency(createTotals.subtotal, invoice.currency)} - - - {Number(invoice.apply_vat) > 0 && - Object.entries(createTotals.vatByRate).map(([rate, amount]) => ( - - - DPH {rate}%: - - - {formatCurrency(amount, invoice.currency)} - - - ))} - + Celkem k úhradě: + + - - Celkem k úhradě: - - - {formatCurrency(createTotals.total, invoice.currency)} - - + {formatCurrency(createTotals.total, invoice.currency)} + -
-
+ + {/* Notes (read-only) */} - - - - Veřejné poznámky na faktuře + + + Veřejné poznámky na faktuře + + {notes && notes.trim() && notes !== "


" ? ( + + ) : ( + + Žádné poznámky. - {notes && notes.trim() && notes !== "


" ? ( - - ) : ( - - Žádné poznámky. - - )} -
-
+ )} + {/* Delete confirm */} - + ); } @@ -1371,12 +1343,8 @@ export default function InvoiceDetail() { return ; } return ( - - + + - +
{/* Basic info */} - - - - Základní údaje - - - - - - - {form.customer_id ? ( - - - {form.customer_name} - - - setForm((prev) => ({ - ...prev, - customer_id: null, - customer_name: "", - })) - } - title="Odebrat zákazníka" - aria-label="Odebrat zákazníka" - > - - - - - - - ) : ( - e.stopPropagation()} - > - { - setCustomerSearch(e.target.value); - setShowCustomerDropdown(true); - }} - onFocus={() => setShowCustomerDropdown(true)} - placeholder="Hledat zákazníka (název, IČ, město)..." - autoComplete="off" - /> - {showCustomerDropdown && ( - - {filteredCustomers.length === 0 ? ( - - Žádní zákazníci - - ) : ( - filteredCustomers.slice(0, 10).map((c) => ( - selectCustomer(c)} - sx={{ - px: 1.5, - py: 1, - cursor: "pointer", - "&:hover": { bgcolor: "action.hover" }, - }} - > - {c.name} - {(c.company_id || c.city) && ( - - {c.company_id && `IČ: ${c.company_id}`} - {c.city && ` · ${c.city}`} - - )} - - )) - )} - - )} - - )} - - - - - - - + + + Základní údaje + + + - setForm((prev) => ({ - ...prev, - billing_text: e.target.value, - })) - } - placeholder="Fakturujeme Vám za: (ponechte prázdné pro výchozí)" + value={invoiceNumber} + InputProps={{ readOnly: true }} + sx={{ "& .MuiInputBase-root": { bgcolor: "action.hover" } }} /> - - - - { - setForm((prev) => ({ ...prev, issue_date: val })); - setErrors((prev) => ({ ...prev, issue_date: "" })); - }} - /> - - - - setForm((prev) => ({ ...prev, payment_method: val })) - } - options={[ - { value: "Příkazem", label: "Příkazem" }, - { value: "Hotově", label: "Hotově" }, - { value: "Dobírka", label: "Dobírka" }, - ]} - /> - - - - setForm((prev) => ({ ...prev, language: val })) - } - options={[ - { value: "cs", label: "Čeština" }, - { value: "en", label: "English" }, - ]} - /> - - + + {form.customer_id ? ( - + + {form.customer_name} + + setForm((prev) => ({ ...prev, - apply_vat: e.target.checked ? 1 : 0, + customer_id: null, + customer_name: "", })) } - /> - Uplatnit DPH + title="Odebrat zákazníka" + aria-label="Odebrat zákazníka" + > + + + + + - - + ) : ( + e.stopPropagation()} + > + { + setCustomerSearch(e.target.value); + setShowCustomerDropdown(true); + }} + onFocus={() => setShowCustomerDropdown(true)} + placeholder="Hledat zákazníka (název, IČ, město)..." + autoComplete="off" + /> + {showCustomerDropdown && ( + + {filteredCustomers.length === 0 ? ( + + Žádní zákazníci + + ) : ( + filteredCustomers.slice(0, 10).map((c) => ( + selectCustomer(c)} + sx={{ + px: 1.5, + py: 1, + cursor: "pointer", + "&:hover": { bgcolor: "action.hover" }, + }} + > + {c.name} + {(c.company_id || c.city) && ( + + {c.company_id && `IČ: ${c.company_id}`} + {c.city && ` · ${c.city}`} + + )} + + )) + )} + + )} + + )} + + + + + - - setDueDays(Number(val))} + options={Array.from({ length: 60 }, (_, i) => i + 1).map( + (n) => ({ value: String(n), label: String(n) }), + )} + /> + {computedDueDate && ( + + Splatnost:{" "} + {new Date(computedDueDate).toLocaleDateString("cs-CZ")} + + )} + + + { + setForm((prev) => ({ ...prev, tax_date: val })); + setErrors((prev) => ({ ...prev, tax_date: "" })); + }} + /> + + + + + + + setForm((prev) => ({ ...prev, currency: val })) + } + options={( + companySettings?.available_currencies || [ + "CZK", + "EUR", + "USD", + "GBP", + ] + ).map((c) => ({ value: c, label: c }))} + /> + + + + setForm((prev) => ({ + ...prev, + apply_vat: e.target.checked ? 1 : 0, + })) + } + /> + Uplatnit DPH + + + + + + updateForm("currency", val)} - disabled={readOnly} - options={( - companySettings?.available_currencies || [ - "CZK", - "EUR", - "USD", - "GBP", - ] - ).map((c) => ({ value: c, label: c }))} - /> - - - - updateForm("vat_rate", parseFloat(val) || 0) - } - disabled={readOnly} - sx={{ flex: 1 }} - options={( - companySettings?.available_vat_rates || [0, 10, 12, 15, 21] - ).map((r) => ({ value: String(r), label: `${r}%` }))} - /> - - updateForm("apply_vat", e.target.checked)} - disabled={readOnly} + {!readOnly && ( + - Účtovat DPH - - - - - - - - {/* Items Section with drag-and-drop */} - - - - - Položky - - {!readOnly && ( - - {itemTemplates && itemTemplates.length > 0 && ( - updateForm("currency", val)} + disabled={readOnly} + options={( + companySettings?.available_currencies || [ + "CZK", + "EUR", + "USD", + "GBP", + ] + ).map((c) => ({ value: c, label: c }))} + /> + + + updateForm("vat_rate", parseFloat(val) || 0)} + disabled={readOnly} + sx={{ flex: 1 }} + options={( + companySettings?.available_vat_rates || [0, 10, 12, 15, 21] + ).map((r) => ({ value: String(r), label: `${r}%` }))} + /> + - updateForm("apply_vat", e.target.checked)} + disabled={readOnly} size="small" - sx={{ - minWidth: 720, - "& td, & th": { borderColor: "divider" }, - }} - > - - - {!readOnly && } - - # - - Popis - Množství - Jednotka - Cena/ks - - V ceně - - - Celkem - - {!readOnly && } - - - - {items.map((item, index) => ( - 1} - onUpdate={(field, value) => - updateItem(index, field as keyof OfferItem, value) - } - onRemove={() => removeItem(index)} - /> - ))} - -
- - - + /> + Účtovat DPH +
+
+ +
+
- {/* Totals */} - + + + Položky + + {!readOnly && ( + + {itemTemplates && itemTemplates.length > 0 && ( + { - const templateId = Number(val); - if (!templateId) return; - const template = scopeTemplates.find( - (t) => t.id === templateId, - ); - if (template?.scope_template_sections?.length) { - const newSections = - template.scope_template_sections.map((s) => ({ - title: s.title || "", - title_cz: s.title_cz || "", - content: s.content || "", - })); - setSections((prev) => [...prev, ...newSections]); - alert.success(`Načtena šablona "${template.name}"`); - } - }} - sx={{ minWidth: 160 }} - options={[ - { value: "", label: "Ze šablony..." }, - ...scopeTemplates.map((t) => ({ - value: String(t.id), - label: t.name, - })), - ]} - /> - )} - - - )} - - - {sections.length === 0 ? ( - - ) : ( - - {sections.map((section, idx) => ( + {formatCurrency(total, form.currency)} + + + + + + {/* Scope/Range Section */} + + + + Rozsah projektu + + {!readOnly && ( + + {scopeTemplates.length > 0 && ( + updateForm("responsible_user_id", v)} - disabled={!canEdit} - > - — Nevybráno — - {users.map((u) => ( - - {u.name} - - ))} - - - - - - - - - updateForm("start_date", val)} - disabled={!canEdit} - /> - - - updateForm("end_date", val)} - disabled={!canEdit} - /> - - - -
+ + + Základní údaje + + + + + + + updateForm("name", e.target.value)} + placeholder="Název projektu" + fullWidth + disabled={!canEdit} + /> + + + + + + + + + + + + + + updateForm("start_date", val)} + disabled={!canEdit} + /> + + + updateForm("end_date", val)} + disabled={!canEdit} + /> + + + {/* Notes */} - - - - Poznámky - + + + Poznámky + - {/* Add note */} - - setNewNote(e.target.value)} - multiline - minRows={2} - placeholder="Napište poznámku..." - fullWidth - onKeyDown={(e) => { - if (e.key === "Enter" && e.ctrlKey && newNote.trim()) { - handleAddNote(); - } - }} - /> - - - - - - {/* Legacy notes (read-only) */} - {project.notes && ( - + setNewNote(e.target.value)} + multiline + minRows={2} + placeholder="Napište poznámku..." + fullWidth + onKeyDown={(e) => { + if (e.key === "Enter" && e.ctrlKey && newNote.trim()) { + handleAddNote(); + } + }} + /> + + + + - {/* Notes list */} - {notes.length === 0 && !project.notes && ( + {/* Legacy notes (read-only) */} + {project.notes && ( + + + Starší poznámka (před zavedením systému) + - Zatím žádné poznámky + {project.notes} - )} - {(notes.length > 0 || project.notes) && ( - - {notes.map((note) => ( + + )} + + {/* Notes list */} + {notes.length === 0 && !project.notes && ( + + Zatím žádné poznámky + + )} + {(notes.length > 0 || project.notes) && ( + + {notes.map((note) => ( + - - - - - {note.user_name} - - - {formatNoteDate(note.created_at)} - - - - {note.content} + + + + {note.user_name} + + + {formatNoteDate(note.created_at)} - {isAdmin && ( - handleDeleteNote(note.id)} - title="Smazat poznámku" - aria-label="Smazat poznámku" - disabled={deletingNoteId === note.id} - sx={{ flexShrink: 0 }} - > - {TrashIcon} - - )} + + {note.content} + + {isAdmin && ( + handleDeleteNote(note.id)} + title="Smazat poznámku" + aria-label="Smazat poznámku" + disabled={deletingNoteId === note.id} + sx={{ flexShrink: 0 }} + > + {TrashIcon} + + )} - ))} - - )} - - + + ))} + + )} + {/* Project File Manager */} - + - + {/* Links */} - - - - Propojení - - - - - Objednávka - - - {project.order_id ? ( - - {project.order_number} - {project.order_status && ( - - ( - {STATUS_LABELS[project.order_status] || - project.order_status} - ) - - )} - - ) : ( - "—" - )} - - - - - Nabídka - - - {project.quotation_id ? ( - - {project.quotation_number} - - ) : ( - "—" - )} - - + + + Propojení + + + + + Objednávka + + + {project.order_id ? ( + + {project.order_number} + {project.order_status && ( + + ( + {STATUS_LABELS[project.order_status] || + project.order_status} + ) + + )} + + ) : ( + "—" + )} + - - + + + Nabídka + + + {project.quotation_id ? ( + + {project.quotation_number} + + ) : ( + "—" + )} + + + + )} - + ); } diff --git a/src/admin/pages/Projects.tsx b/src/admin/pages/Projects.tsx index 87f6e57..9b1cbe5 100644 --- a/src/admin/pages/Projects.tsx +++ b/src/admin/pages/Projects.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Link as RouterLink, useNavigate } from "react-router-dom"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; @@ -32,6 +31,7 @@ import { StatusChip, CheckboxField, LoadingState, + PageEnter, type DataColumn, } from "../ui"; @@ -346,30 +346,24 @@ export default function Projects() { ]; return ( - - + - - Projekty - {hasPermission("projects.create") && ( - - )} - - + Projekty + {hasPermission("projects.create") && ( + + )} + - - - - columns={columns} - rows={projects} - rowKey={(p) => p.id} - sortBy={sort} - sortDir={order} - onSort={handleSort} - empty={ - - - Zatím nejsou žádné projekty. - - - Projekty vznikají z objednávek nebo je můžete vytvořit ručně - tlačítkem „Přidat projekt". - - - } - /> - - - + + + columns={columns} + rows={projects} + rowKey={(p) => p.id} + sortBy={sort} + sortDir={order} + onSort={handleSort} + empty={ + + + Zatím nejsou žádné projekty. + + + Projekty vznikají z objednávek nebo je můžete vytvořit ručně + tlačítkem „Přidat projekt". + + + } + /> + + - + ); } diff --git a/src/admin/pages/Settings.tsx b/src/admin/pages/Settings.tsx index 4f43317..d634f0d 100644 --- a/src/admin/pages/Settings.tsx +++ b/src/admin/pages/Settings.tsx @@ -3,7 +3,6 @@ import { useQuery } from "@tanstack/react-query"; import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import { Navigate, useSearchParams } from "react-router-dom"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; @@ -27,6 +26,7 @@ import { Tabs, TabPanel, LoadingState, + PageEnter, type DataColumn, type TabDef, } from "../ui"; @@ -780,23 +780,17 @@ export default function Settings() { }; return ( - - - - Nastavení - - {activeTab === "system" - ? "Systémová nastavení" - : activeTab === "firma" - ? "Informace o firmě" - : "Role"} - - - + + + Nastavení + + {activeTab === "system" + ? "Systémová nastavení" + : activeTab === "firma" + ? "Informace o firmě" + : "Role"} + + {tabDefs.length > 0 && ( - - - Role - - - - - columns={roleColumns} - rows={roles} - rowKey={(role) => role.id} - /> - - + Role + + + + + columns={roleColumns} + rows={roles} + rowKey={(role) => role.id} + /> + {/* System settings tab */} {canManageSystem && ( - + Zabezpečení @@ -908,16 +891,11 @@ export default function Settings() { )} - + )} {canManageSystem && ( - + Přihlašování @@ -968,7 +946,7 @@ export default function Settings() { - + )} {sysSettingsLoading && !sysFormInitialized ? ( @@ -976,12 +954,7 @@ export default function Settings() { ) : ( <> {/* Section 1: Docházka */} - + Docházka @@ -1051,15 +1024,10 @@ export default function Settings() { - + {/* Section 2: Emailové notifikace */} - + Emailové notifikace @@ -1124,15 +1092,10 @@ export default function Settings() { - + {/* Section 4: Omezení požadavků */} - + Omezení požadavků @@ -1154,39 +1117,26 @@ export default function Settings() { /> - + {/* Section 5: Informace o aplikaci */} - + Informace o aplikaci {renderSystemInfo()} - + {/* Save button */} - - - + {saveSystemSettingsMutation.isPending ? "Ukládání..." : "Uložit"} + )} @@ -1399,6 +1349,6 @@ export default function Settings() { confirmVariant="danger" loading={deleteRoleMutation.isPending} /> - + ); } diff --git a/src/admin/pages/Trips.tsx b/src/admin/pages/Trips.tsx index 59a33b0..6729285 100644 --- a/src/admin/pages/Trips.tsx +++ b/src/admin/pages/Trips.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Link as RouterLink } from "react-router-dom"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; @@ -30,6 +29,7 @@ import { StatusChip, LoadingState, StatCard, + PageEnter, type DataColumn, } from "../ui"; @@ -418,124 +418,106 @@ export default function Trips() { ]; return ( - - + + + Kniha jízd + + {new Date().toLocaleDateString("cs-CZ", { + month: "long", + year: "numeric", + })} + + + + + + {/* Stats Cards */} + + + + + + + + {/* Recent Trips */} + - - Kniha jízd - - {new Date().toLocaleDateString("cs-CZ", { - month: "long", - year: "numeric", - })} - - - - - - {/* Stats Cards */} - - - - - - - - - - {/* Recent Trips */} - - - - Poslední jízdy - - - - columns={columns} - rows={recentTrips} - rowKey={(trip) => trip.id} - empty={ - - - Zatím nemáte žádné záznamy jízd. - - - - } - /> - - + + columns={columns} + rows={recentTrips} + rowKey={(trip) => trip.id} + empty={ + + + Zatím nemáte žádné záznamy jízd. + + + + } + /> + {/* Add/Edit Modal */} - + ); } diff --git a/src/admin/pages/TripsAdmin.tsx b/src/admin/pages/TripsAdmin.tsx index 149ceed..459579e 100644 --- a/src/admin/pages/TripsAdmin.tsx +++ b/src/admin/pages/TripsAdmin.tsx @@ -1,7 +1,6 @@ import { useState, useRef } from "react"; import { useQuery } from "@tanstack/react-query"; import { Link as RouterLink } from "react-router-dom"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; @@ -33,6 +32,7 @@ import { LoadingState, EmptyState, StatCard, + PageEnter, type DataColumn, } from "../ui"; @@ -529,171 +529,147 @@ export default function TripsAdmin() { ]; return ( - - + - - Správa knihy jízd - - {trips.length > 0 && ( - - )} + Správa knihy jízd + + {trips.length > 0 && ( - + )} + - + {/* Filters */} - - - - - { - const y = new Date().getFullYear() - 2 + i; - return { value: String(y), label: String(y) }; - })} - /> - - - - - ({ - value: String(u.id), - label: u.name, - })), - ]} - /> - - - - + + + + { + const y = new Date().getFullYear() - 2 + i; + return { value: String(y), label: String(y) }; + })} + /> + + + + + ({ + value: String(u.id), + label: u.name, + })), + ]} + /> + + + {/* Stats Cards */} - - - - - - - + + + + {/* Trips Table */} - - - {isPending ? ( - - ) : ( - - columns={columns} - rows={trips} - rowKey={(trip) => trip.id} - empty={ - - } - /> - )} - - + + {isPending ? ( + + ) : ( + + columns={columns} + rows={trips} + rowKey={(trip) => trip.id} + empty={ + + } + /> + )} + {/* Edit Modal */}
)} - + ); } diff --git a/src/admin/pages/TripsHistory.tsx b/src/admin/pages/TripsHistory.tsx index 62d41f8..5a5c79f 100644 --- a/src/admin/pages/TripsHistory.tsx +++ b/src/admin/pages/TripsHistory.tsx @@ -1,6 +1,5 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; @@ -15,6 +14,7 @@ import { MonthField, StatusChip, PageHeader, + PageEnter, FilterBar, EmptyState, LoadingState, @@ -216,104 +216,80 @@ export default function TripsHistory() { ]; return ( - - - - + + {/* Filters */} - - - - - setMonth(val)} /> - - - - - setVehicleId(value)} + options={[ + { value: "", label: "Všechna vozidla" }, + ...vehicles.map((v) => ({ + value: String(v.id), + label: `${v.spz} - ${v.name}`, + })), + ]} + /> + + + + + + + + + {/* Trips Table */} - - - {isPending ? ( - - ) : ( - - columns={columns} - rows={trips} - rowKey={(trip) => trip.id} - empty={ - - } - /> - )} - - - + + {isPending ? ( + + ) : ( + + columns={columns} + rows={trips} + rowKey={(trip) => trip.id} + empty={ + + } + /> + )} + + ); } diff --git a/src/admin/pages/Users.tsx b/src/admin/pages/Users.tsx index a0b2cf8..942845e 100644 --- a/src/admin/pages/Users.tsx +++ b/src/admin/pages/Users.tsx @@ -1,6 +1,5 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { motion } from "framer-motion"; import Avatar from "@mui/material/Avatar"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; @@ -26,6 +25,7 @@ import { Select, StatusChip, LoadingState, + PageEnter, type DataColumn, } from "../ui"; @@ -324,53 +324,41 @@ export default function Users() { ]; return ( - - + - - Správa uživatelů - - - + Správa uživatelů + + - - - - columns={columns} - rows={users} - rowKey={(u) => u.id} - rowInactive={(u) => !u.is_active} - empty={ - - - Zatím nejsou žádní uživatelé. - - - - } - /> - - + + + columns={columns} + rows={users} + rowKey={(u) => u.id} + rowInactive={(u) => !u.is_active} + empty={ + + + Zatím nejsou žádní uživatelé. + + + + } + /> + - + ); } diff --git a/src/admin/pages/Vehicles.tsx b/src/admin/pages/Vehicles.tsx index 084de38..058ca61 100644 --- a/src/admin/pages/Vehicles.tsx +++ b/src/admin/pages/Vehicles.tsx @@ -1,6 +1,5 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import Chip from "@mui/material/Chip"; @@ -21,6 +20,7 @@ import { TextField, SwitchField, LoadingState, + PageEnter, type DataColumn, } from "../ui"; @@ -299,53 +299,41 @@ export default function Vehicles() { ]; return ( - - + - - Správa vozidel - - - + Správa vozidel + + - - - - columns={columns} - rows={vehicles} - rowKey={(v) => v.id} - rowInactive={(v) => !v.is_active} - empty={ - - - Zatím nejsou žádná vozidla. - - - - } - /> - - + + + columns={columns} + rows={vehicles} + rowKey={(v) => v.id} + rowInactive={(v) => !v.is_active} + empty={ + + + Zatím nejsou žádná vozidla. + + + + } + /> + - + ); } diff --git a/src/admin/pages/Warehouse.tsx b/src/admin/pages/Warehouse.tsx index e7fac32..cd76275 100644 --- a/src/admin/pages/Warehouse.tsx +++ b/src/admin/pages/Warehouse.tsx @@ -1,6 +1,5 @@ import { Link as RouterLink } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import { useAuth } from "../context/AuthContext"; @@ -22,6 +21,7 @@ import { EmptyState, LoadingState, PageHeader, + PageEnter, type DataColumn, type StatCardColor, } from "../ui"; @@ -193,74 +193,64 @@ export default function Warehouse() { ]; return ( - - - - {hasPermission("warehouse.operate") && ( - <> - - - - )} - {hasPermission("warehouse.manage") && ( + + + {hasPermission("warehouse.operate") && ( + <> + - )} + + )} + {hasPermission("warehouse.manage") && ( - - - } - /> - + )} + + + + } + /> {/* KPI cards */} 0 && ( - + + - + ); } diff --git a/src/admin/pages/WarehouseCategories.tsx b/src/admin/pages/WarehouseCategories.tsx index 2006bcc..9c663cf 100644 --- a/src/admin/pages/WarehouseCategories.tsx +++ b/src/admin/pages/WarehouseCategories.tsx @@ -19,6 +19,7 @@ import { Field, TextField, PageHeader, + PageEnter, EmptyState, LoadingState, type DataColumn, @@ -238,7 +239,7 @@ export default function WarehouseCategories() { ]; return ( - + - + ); } diff --git a/src/admin/pages/WarehouseInventory.tsx b/src/admin/pages/WarehouseInventory.tsx index 491c182..e30f977 100644 --- a/src/admin/pages/WarehouseInventory.tsx +++ b/src/admin/pages/WarehouseInventory.tsx @@ -1,6 +1,5 @@ import { useState } from "react"; import { useNavigate } from "react-router-dom"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; @@ -19,6 +18,7 @@ import { Select, StatusChip, PageHeader, + PageEnter, FilterBar, EmptyState, LoadingState, @@ -127,25 +127,19 @@ export default function WarehouseInventory() { ]; return ( - - - navigate("/warehouse/inventory/new")} - > - Nová inventura - - } - /> - + + navigate("/warehouse/inventory/new")} + > + Nová inventura + + } + /> @@ -199,6 +193,6 @@ export default function WarehouseInventory() { onChange={setPage} /> - + ); } diff --git a/src/admin/pages/WarehouseInventoryDetail.tsx b/src/admin/pages/WarehouseInventoryDetail.tsx index 0f6f58a..0dd506b 100644 --- a/src/admin/pages/WarehouseInventoryDetail.tsx +++ b/src/admin/pages/WarehouseInventoryDetail.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import { useParams, Link as RouterLink } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import { useAlert } from "../context/AlertContext"; @@ -23,6 +22,7 @@ import { EmptyState, LoadingState, PageHeader, + PageEnter, ConfirmDialog, type DataColumn, } from "../ui"; @@ -187,117 +187,99 @@ export default function WarehouseInventoryDetail() { ]; return ( - + {/* Header */} - - + + + + {s.status === "DRAFT" && ( - - {s.status === "DRAFT" && ( - - )} - - } - /> - - - {/* Header info */} - - - - Základní údaje - - - - - Číslo inventury - - - {s.session_number || "—"} - - - - - Vytvořeno - - - {formatDate(s.created_at)} - - - {s.notes && ( - - - Poznámky - - - {s.notes} - - )} - - + } + /> + + {/* Header info */} + + + Základní údaje + + + + + Číslo inventury + + + {s.session_number || "—"} + + + + + Vytvořeno + + + {formatDate(s.created_at)} + + + {s.notes && ( + + + Poznámky + + + {s.notes} + + + )} + + {/* Lines table */} - - - - Položky - - - columns={itemColumns} - rows={items} - rowKey={(row) => row.id} - empty={} - /> - - + + + Položky + + + columns={itemColumns} + rows={items} + rowKey={(row) => row.id} + empty={} + /> + {/* Confirm modal */} - + ); } diff --git a/src/admin/pages/WarehouseInventoryForm.tsx b/src/admin/pages/WarehouseInventoryForm.tsx index 37ae70a..6956a91 100644 --- a/src/admin/pages/WarehouseInventoryForm.tsx +++ b/src/admin/pages/WarehouseInventoryForm.tsx @@ -1,7 +1,6 @@ import { useState, useId, useRef } from "react"; import { useNavigate, Link as RouterLink } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; @@ -14,7 +13,7 @@ import { type WarehouseLocation, } from "../lib/queries/warehouse"; import { useApiMutation } from "../lib/queries/mutations"; -import { Button, Card, TextField, Select, Field } from "../ui"; +import { Button, Card, TextField, Select, Field, PageEnter } from "../ui"; interface InventoryItem { key: string; @@ -165,139 +164,121 @@ export default function WarehouseInventoryForm() { ]; return ( - + {/* Header */} - - - - - {BackIcon} - - Nová inventura - - - - + + + {BackIcon} + + Nová inventura - + + + + {/* Notes */} - - - - Základní údaje - - - setNotes(e.target.value)} - placeholder="Volitelné poznámky k inventuře" - /> - - - + + + Základní údaje + + + setNotes(e.target.value)} + placeholder="Volitelné poznámky k inventuře" + /> + + {/* Lines */} - - - - Položky - - - {items.map((item, index) => ( - + + Položky + + + {items.map((item, index) => ( + + updateItem(index, "item_id", id)} + /> + - updateItem(index, "location_id", val ? Number(val) : null) - } - options={locationOptions} - /> - - updateItem(index, "actual_qty", Number(e.target.value)) - } - inputProps={{ - min: 0, - step: 0.001, - inputMode: "decimal", - pattern: "[0-9]+([\\.,][0-9]+)?", - "aria-label": `Skutečné množství, řádek ${index + 1}`, - }} - placeholder="Skutečné množství" - /> - removeItem(index)} - title="Odebrat řádek" - aria-label="Odebrat řádek" - sx={{ justifySelf: { xs: "start", md: "center" } }} - > - {RemoveIcon} - - - ))} - - - - - + {RemoveIcon} + + + ))} + + + + ); } diff --git a/src/admin/pages/WarehouseIssueDetail.tsx b/src/admin/pages/WarehouseIssueDetail.tsx index d08b697..63a14c4 100644 --- a/src/admin/pages/WarehouseIssueDetail.tsx +++ b/src/admin/pages/WarehouseIssueDetail.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import { useParams, useNavigate, Link as RouterLink } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import { useAlert } from "../context/AlertContext"; @@ -24,6 +23,7 @@ import { LoadingState, PageHeader, ConfirmDialog, + PageEnter, type DataColumn, } from "../ui"; @@ -223,57 +223,42 @@ export default function WarehouseIssueDetail() { ]; return ( - + {/* Header */} - - - - - {canOperate && iss.status === "DRAFT" && ( - <> - - - - - )} - {canOperate && iss.status === "CONFIRMED" && ( + + + + {canOperate && iss.status === "DRAFT" && ( + <> + + - )} - - } - /> - - - {/* Basic info */} - - - - Základní údaje - - - - - Číslo dokladu - - + )} + {canOperate && iss.status === "CONFIRMED" && ( + )} - - + } + /> + + {/* Basic info */} + + + Základní údaje + + + + + Číslo dokladu + + + {iss.issue_number || "—"} + + + + + Projekt + + {iss.project ? ( + + {iss.project.project_number} – {iss.project.name} + + ) : ( + + )} + + + + Vydal + + + {iss.issued_by_user + ? `${iss.issued_by_user.first_name} ${iss.issued_by_user.last_name}` + : "—"} + + + + + Vytvořeno + + + {formatDate(iss.created_at)} + + + {iss.notes && ( + + + Poznámky + + + {iss.notes} + + + )} + + {/* Lines table */} - - - - Položky - - - columns={itemColumns} - rows={items} - rowKey={(row) => row.id} - empty={} - /> - - + + + Položky + + + columns={itemColumns} + rows={items} + rowKey={(row) => row.id} + empty={} + /> + {/* Cancel confirmation dialog */} - + ); } diff --git a/src/admin/pages/WarehouseIssueForm.tsx b/src/admin/pages/WarehouseIssueForm.tsx index a4035fb..a628516 100644 --- a/src/admin/pages/WarehouseIssueForm.tsx +++ b/src/admin/pages/WarehouseIssueForm.tsx @@ -6,7 +6,6 @@ import { Link as RouterLink, } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; @@ -23,7 +22,15 @@ import { } from "../lib/queries/warehouse"; import { projectListOptions, type Project } from "../lib/queries/projects"; import { useApiMutation } from "../lib/queries/mutations"; -import { Button, Card, TextField, Select, Field, LoadingState } from "../ui"; +import { + Button, + Card, + TextField, + Select, + Field, + LoadingState, + PageEnter, +} from "../ui"; interface IssueForm { project_id: number | null; @@ -422,199 +429,181 @@ export default function WarehouseIssueForm() { ]; return ( - + {/* Header */} - - - - - {BackIcon} - - - {isEdit ? "Upravit výdejku" : "Nová výdejka"} - - - - - - + + + {BackIcon} + + + {isEdit ? "Upravit výdejku" : "Nová výdejka"} + - - - {/* Header fields */} - - - - Základní údaje - - - - updateItem(index, "location_id", val ? Number(val) : null) - } - options={locationOptions} - /> - updateItem(index, "notes", e.target.value)} - placeholder="Poznámka" - /> - removeItem(index)} - title="Odebrat řádek" - aria-label="Odebrat řádek" - sx={{ justifySelf: { xs: "start", md: "center" } }} - > - {RemoveIcon} - - - ))} - + - - - + + + + + {/* Header fields */} + + + Základní údaje + + + + updateItem(index, "location_id", val ? Number(val) : null) + } + options={locationOptions} + /> + updateItem(index, "notes", e.target.value)} + placeholder="Poznámka" + /> + removeItem(index)} + title="Odebrat řádek" + aria-label="Odebrat řádek" + sx={{ justifySelf: { xs: "start", md: "center" } }} + > + {RemoveIcon} + + + ))} + + + + ); } diff --git a/src/admin/pages/WarehouseIssues.tsx b/src/admin/pages/WarehouseIssues.tsx index b146612..128078e 100644 --- a/src/admin/pages/WarehouseIssues.tsx +++ b/src/admin/pages/WarehouseIssues.tsx @@ -3,7 +3,6 @@ import { useNavigate } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import useDebounce from "../hooks/useDebounce"; import { usePaginatedQuery } from "../hooks/usePaginatedQuery"; @@ -24,6 +23,7 @@ import { DateField, StatusChip, PageHeader, + PageEnter, FilterBar, EmptyState, LoadingState, @@ -166,27 +166,21 @@ export default function WarehouseIssues() { ]; return ( - - - navigate("/warehouse/issues/new")} - > - Nová výdejka - - ) : undefined - } - /> - + + navigate("/warehouse/issues/new")} + > + Nová výdejka + + ) : undefined + } + /> @@ -292,6 +286,6 @@ export default function WarehouseIssues() { onChange={setPage} /> - + ); } diff --git a/src/admin/pages/WarehouseItemDetail.tsx b/src/admin/pages/WarehouseItemDetail.tsx index e75ae97..851d28e 100644 --- a/src/admin/pages/WarehouseItemDetail.tsx +++ b/src/admin/pages/WarehouseItemDetail.tsx @@ -1,7 +1,6 @@ import { useState, useEffect, useRef } from "react"; import { useQuery } from "@tanstack/react-query"; import { useParams, useNavigate, Link as RouterLink } from "react-router-dom"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import MenuItem from "@mui/material/MenuItem"; @@ -29,6 +28,7 @@ import { LoadingState, PageHeader, ConfirmDialog, + PageEnter, type DataColumn, } from "../ui"; @@ -356,145 +356,35 @@ export default function WarehouseItemDetail() { ]; return ( - + {/* Header */} - - - - {headerActions} - - } - /> - + + + {headerActions} + + } + /> {/* Basic info */} - - - - Základní údaje - + + + Základní údaje + - {editing || isNew ? ( - - - - { - updateForm("item_number", e.target.value); - setErrors((prev) => ({ ...prev, item_number: "" })); - }} - placeholder="Např. SKL-001" - fullWidth - /> - - - { - updateForm("name", e.target.value); - setErrors((prev) => ({ ...prev, name: "" })); - }} - placeholder="Název položky" - fullWidth - /> - - - - updateForm("description", e.target.value)} - placeholder="Volitelný popis položky" - fullWidth - /> - - - - - - - { - updateForm("unit", e.target.value); - setErrors((prev) => ({ ...prev, unit: "" })); - }} - placeholder="ks, m, kg..." - fullWidth - /> - - - updateForm("min_quantity", e.target.value)} - placeholder="0" - fullWidth - /> - - - - updateForm("notes", e.target.value)} - placeholder="Volitelné poznámky" - fullWidth - /> - - - ) : ( + {editing || isNew ? ( + - {/* Číslo položky */} - - - Číslo položky - - - {item?.item_number || "—"} - - - {/* Název */} - - - Název - - - {item?.name || "—"} - - - {/* Popis */} - - - Popis - - - {item?.description || "—"} - - - {/* Kategorie */} - - - Kategorie - - - {item?.category?.name || "—"} - - - {/* Jednotka */} - - - Jednotka - - {item?.unit || "—"} - - {/* Minimální množství */} - - - Minimální množství - - - {item?.min_quantity != null ? item.min_quantity : "—"} - - - {/* Poznámky */} - {item?.notes && ( - - - Poznámky - - - {item.notes} - - - )} + + { + updateForm("item_number", e.target.value); + setErrors((prev) => ({ ...prev, item_number: "" })); + }} + placeholder="Např. SKL-001" + fullWidth + /> + + + { + updateForm("name", e.target.value); + setErrors((prev) => ({ ...prev, name: "" })); + }} + placeholder="Název položky" + fullWidth + /> + - )} - - - - {/* Stock info — only in view mode, for existing items */} - {id !== "new" && !editing && item && ( - + + updateForm("description", e.target.value)} + placeholder="Volitelný popis položky" + fullWidth + /> + + + + + + + { + updateForm("unit", e.target.value); + setErrors((prev) => ({ ...prev, unit: "" })); + }} + placeholder="ks, m, kg..." + fullWidth + /> + + + updateForm("min_quantity", e.target.value)} + placeholder="0" + fullWidth + /> + + + + updateForm("notes", e.target.value)} + placeholder="Volitelné poznámky" + fullWidth + /> + + + ) : ( - - - - + {/* Číslo položky */} + + + Číslo položky + + + {item?.item_number || "—"} + + + {/* Název */} + + + Název + + + {item?.name || "—"} + + + {/* Popis */} + + + Popis + + + {item?.description || "—"} + + + {/* Kategorie */} + + + Kategorie + + + {item?.category?.name || "—"} + + + {/* Jednotka */} + + + Jednotka + + {item?.unit || "—"} + + {/* Minimální množství */} + + + Minimální množství + + + {item?.min_quantity != null ? item.min_quantity : "—"} + + + {/* Poznámky */} + {item?.notes && ( + + + Poznámky + + + {item.notes} + + + )} - + )} + + + {/* Stock info — only in view mode, for existing items */} + {id !== "new" && !editing && item && ( + + + + + + )} {/* Batches table — only in view mode, for existing items */} {id !== "new" && !editing && ( - - - - Dávky (FIFO) - - - columns={batchColumns} - rows={batches} - rowKey={(b) => b.id} - empty={} - /> - - + + + Dávky (FIFO) + + + columns={batchColumns} + rows={batches} + rowKey={(b) => b.id} + empty={} + /> + )} {/* Locations table — only in view mode, for existing items */} {id !== "new" && !editing && ( - - - - Umístění - - - columns={locationColumns} - rows={locations} - rowKey={(l) => l.id} - empty={} - /> - - + + + Umístění + + + columns={locationColumns} + rows={locations} + rowKey={(l) => l.id} + empty={} + /> + )} {/* Delete confirmation */} @@ -675,6 +645,6 @@ export default function WarehouseItemDetail() { confirmVariant="danger" loading={deleteMutation.isPending} /> - + ); } diff --git a/src/admin/pages/WarehouseItems.tsx b/src/admin/pages/WarehouseItems.tsx index 136b39d..d1a3842 100644 --- a/src/admin/pages/WarehouseItems.tsx +++ b/src/admin/pages/WarehouseItems.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { useNavigate } from "react-router-dom"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; @@ -24,6 +23,7 @@ import { Select, StatusChip, PageHeader, + PageEnter, FilterBar, EmptyState, LoadingState, @@ -153,27 +153,21 @@ export default function WarehouseItems() { ]; return ( - - - navigate("/warehouse/items/new")} - > - Přidat položku - - ) : undefined - } - /> - + + navigate("/warehouse/items/new")} + > + Přidat položku + + ) : undefined + } + /> @@ -243,6 +237,6 @@ export default function WarehouseItems() { onChange={setPage} /> - + ); } diff --git a/src/admin/pages/WarehouseLocations.tsx b/src/admin/pages/WarehouseLocations.tsx index 863a2a7..fb33e05 100644 --- a/src/admin/pages/WarehouseLocations.tsx +++ b/src/admin/pages/WarehouseLocations.tsx @@ -20,6 +20,7 @@ import { TextField, StatusChip, PageHeader, + PageEnter, EmptyState, LoadingState, type DataColumn, @@ -303,7 +304,7 @@ export default function WarehouseLocations() { ]; return ( - + - + ); } diff --git a/src/admin/pages/WarehouseReceiptDetail.tsx b/src/admin/pages/WarehouseReceiptDetail.tsx index a37bc73..0649544 100644 --- a/src/admin/pages/WarehouseReceiptDetail.tsx +++ b/src/admin/pages/WarehouseReceiptDetail.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import { useParams, useNavigate, Link as RouterLink } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; @@ -26,6 +25,7 @@ import { LoadingState, PageHeader, ConfirmDialog, + PageEnter, type DataColumn, } from "../ui"; @@ -295,53 +295,38 @@ export default function WarehouseReceiptDetail() { ]; return ( - + {/* Header */} - - - - - {canOperate && r.status === "DRAFT" && ( - <> - - - - - )} - {canOperate && r.status === "CONFIRMED" && ( + + + + {canOperate && r.status === "DRAFT" && ( + <> + + - )} - - } - /> - - - {/* Basic info */} - - - - Základní údaje - - - - - Číslo dokladu - - + )} + {canOperate && r.status === "CONFIRMED" && ( + )} - - + } + /> + + {/* Basic info */} + + + Základní údaje + + + + + Číslo dokladu + + + {r.receipt_number || "—"} + + + + + Dodavatel + + + {r.supplier?.name || "—"} + + + + + Číslo dodacího listu + + + {r.delivery_note_number || "—"} + + + + + Datum dodacího listu + + + {formatDate(r.delivery_note_date)} + + + + + Přijal + + + {r.received_by_user + ? `${r.received_by_user.first_name} ${r.received_by_user.last_name}` + : "—"} + + + + + Vytvořeno + + + {formatDate(r.created_at)} + + + {r.notes && ( + + + Poznámky + + + {r.notes} + + + )} + + {/* Lines table */} - - - - Položky - - - columns={itemColumns} - rows={items} - rowKey={(row) => row.id} - empty={} - /> - - + + + Položky + + + columns={itemColumns} + rows={items} + rowKey={(row) => row.id} + empty={} + /> + {/* Attachments */} - - - - Přílohy - - - columns={attachmentColumns} - rows={attachments} - rowKey={(att) => att.id} - empty={} - /> - - + + + Přílohy + + + columns={attachmentColumns} + rows={attachments} + rowKey={(att) => att.id} + empty={} + /> + {/* Cancel confirmation dialog */} - + ); } diff --git a/src/admin/pages/WarehouseReceiptForm.tsx b/src/admin/pages/WarehouseReceiptForm.tsx index 93d7cff..b6f4758 100644 --- a/src/admin/pages/WarehouseReceiptForm.tsx +++ b/src/admin/pages/WarehouseReceiptForm.tsx @@ -1,7 +1,6 @@ import { useState, useEffect, useRef, useCallback, useId } from "react"; import { useParams, useNavigate, Link as RouterLink } from "react-router-dom"; import { useQuery, useQueryClient } from "@tanstack/react-query"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; @@ -28,6 +27,7 @@ import { DateField, Field, LoadingState, + PageEnter, } from "../ui"; interface ReceiptForm { @@ -410,294 +410,266 @@ export default function WarehouseReceiptForm() { ]; return ( - + {/* Header */} - - - - - {BackIcon} - - - {isEdit ? "Upravit příjmový doklad" : "Nový příjmový doklad"} - - - - - - - - - - {/* Header fields */} - - - - Základní údaje - - - - updateItem(index, "location_id", val ? Number(val) : null) - } - options={locationOptions} - /> - updateItem(index, "notes", e.target.value)} - placeholder="Poznámka" - /> - removeItem(index)} - title="Odebrat řádek" - aria-label="Odebrat řádek" - sx={{ justifySelf: { xs: "start", md: "center" } }} - > - {RemoveIcon} - - - ))} - + + - - + + + + + {/* Header fields */} + + + Základní údaje + + + + updateItem(index, "location_id", val ? Number(val) : null) + } + options={locationOptions} + /> + updateItem(index, "notes", e.target.value)} + placeholder="Poznámka" + /> + removeItem(index)} + title="Odebrat řádek" + aria-label="Odebrat řádek" + sx={{ justifySelf: { xs: "start", md: "center" } }} + > + {RemoveIcon} + + + ))} + + + {/* File upload — only in edit mode for existing receipts */} {isEdit && ( - - - - Přílohy - - { - const input = document.createElement("input"); - input.type = "file"; - input.multiple = true; - input.onchange = () => { - if (input.files && input.files.length > 0) { - handleFileUpload(input.files); - } - }; - input.click(); - }} - sx={{ - display: "flex", - flexDirection: "column", - alignItems: "center", - justifyContent: "center", - gap: 1, - p: 4, - border: "2px dashed", - borderColor: "divider", - borderRadius: 1, - color: "text.secondary", - cursor: "pointer", - opacity: uploadingFiles ? 0.6 : 1, - transition: "border-color .2s, background-color .2s", - "&:hover": { - borderColor: "primary.main", - bgcolor: "action.hover", - }, - }} - > - {uploadingFiles ? ( - <> - - Nahrávání... - - ) : ( - <> - {UploadIcon} - - Přetáhněte soubory sem nebo klikněte pro výběr - - - Dodací listy, faktury a další dokumenty - - - )} - - - + + + Přílohy + + { + const input = document.createElement("input"); + input.type = "file"; + input.multiple = true; + input.onchange = () => { + if (input.files && input.files.length > 0) { + handleFileUpload(input.files); + } + }; + input.click(); + }} + sx={{ + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + gap: 1, + p: 4, + border: "2px dashed", + borderColor: "divider", + borderRadius: 1, + color: "text.secondary", + cursor: "pointer", + opacity: uploadingFiles ? 0.6 : 1, + transition: "border-color .2s, background-color .2s", + "&:hover": { + borderColor: "primary.main", + bgcolor: "action.hover", + }, + }} + > + {uploadingFiles ? ( + <> + + Nahrávání... + + ) : ( + <> + {UploadIcon} + + Přetáhněte soubory sem nebo klikněte pro výběr + + + Dodací listy, faktury a další dokumenty + + + )} + + )} - + ); } diff --git a/src/admin/pages/WarehouseReceipts.tsx b/src/admin/pages/WarehouseReceipts.tsx index 21949f1..354728e 100644 --- a/src/admin/pages/WarehouseReceipts.tsx +++ b/src/admin/pages/WarehouseReceipts.tsx @@ -3,7 +3,6 @@ import { useNavigate } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import useDebounce from "../hooks/useDebounce"; import { usePaginatedQuery } from "../hooks/usePaginatedQuery"; @@ -24,6 +23,7 @@ import { DateField, StatusChip, PageHeader, + PageEnter, FilterBar, EmptyState, LoadingState, @@ -165,27 +165,21 @@ export default function WarehouseReceipts() { ]; return ( - - - navigate("/warehouse/receipts/new")} - > - Nový doklad - - ) : undefined - } - /> - + + navigate("/warehouse/receipts/new")} + > + Nový doklad + + ) : undefined + } + /> @@ -291,6 +285,6 @@ export default function WarehouseReceipts() { onChange={setPage} /> - + ); } diff --git a/src/admin/pages/WarehouseReports.tsx b/src/admin/pages/WarehouseReports.tsx index 843c4b5..8f8c90b 100644 --- a/src/admin/pages/WarehouseReports.tsx +++ b/src/admin/pages/WarehouseReports.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import { projectListOptions } from "../lib/queries/projects"; import { @@ -21,6 +20,7 @@ import { DateField, StatusChip, PageHeader, + PageEnter, FilterBar, EmptyState, LoadingState, @@ -89,26 +89,14 @@ export default function WarehouseReports() { if (!hasPermission("warehouse.view")) return ; return ( - - - - + + - - setActiveTab(v as TabId)} - tabs={TABS} - /> - + setActiveTab(v as TabId)} + tabs={TABS} + /> @@ -139,7 +127,7 @@ export default function WarehouseReports() { - + ); } diff --git a/src/admin/pages/WarehouseReservations.tsx b/src/admin/pages/WarehouseReservations.tsx index 2ed8044..5ea3ba6 100644 --- a/src/admin/pages/WarehouseReservations.tsx +++ b/src/admin/pages/WarehouseReservations.tsx @@ -4,7 +4,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; -import { motion } from "framer-motion"; import Box from "@mui/material/Box"; import IconButton from "@mui/material/IconButton"; import { usePaginatedQuery } from "../hooks/usePaginatedQuery"; @@ -29,6 +28,7 @@ import { Select, StatusChip, PageHeader, + PageEnter, FilterBar, EmptyState, LoadingState, @@ -325,27 +325,21 @@ export default function WarehouseReservations() { ]; return ( - - - setShowCreateModal(true)} - > - Nová rezervace - - ) : undefined - } - /> - + + setShowCreateModal(true)} + > + Nová rezervace + + ) : undefined + } + /> @@ -490,6 +484,6 @@ export default function WarehouseReservations() { confirmVariant="danger" loading={cancelling} /> - + ); }