feat(mui): consistent staggered page entrance across all 43 route pages
Adopt the PageEnter wrapper as every page's outermost render element and remove the ad-hoc per-page entrance motion.div wrappers. Every page now enters the same way — all top-level sections rise+fade in, staggered — so nothing appears instantly and the motion is identical app-wide. Presentation-only: no data/logic/hooks/invalidate/permissions touched. Embedded sub-tabs (CompanySettings, ReceivedInvoices), Login (auth shell) and the dev UiKit are intentionally excluded. Gates: tsc -b --noEmit=0, build=0, vitest 152/152. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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,12 +614,7 @@ export default function Attendance() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h4">Docházka</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
|
||||
@@ -631,7 +626,6 @@ export default function Attendance() {
|
||||
})}
|
||||
</Typography>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
@@ -642,11 +636,7 @@ export default function Attendance() {
|
||||
}}
|
||||
>
|
||||
{/* Left Column - Clock In/Out */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<>
|
||||
<Card>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -986,14 +976,9 @@ export default function Attendance() {
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</motion.div>
|
||||
</>
|
||||
|
||||
{/* Right Column - Stats & Quick Links */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", gap: 3 }}>
|
||||
{/* Leave Balance Card */}
|
||||
<StatCard
|
||||
@@ -1010,8 +995,7 @@ export default function Attendance() {
|
||||
sx={{ fontFamily: "inherit" }}
|
||||
>
|
||||
{czechPlural(vacationDaysRemaining, "den", "dny", "dnů")}
|
||||
{vacationHoursRemaining > 0 &&
|
||||
` ${vacationHoursRemaining}h`}
|
||||
{vacationHoursRemaining > 0 && ` ${vacationHoursRemaining}h`}
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
@@ -1154,7 +1138,6 @@ export default function Attendance() {
|
||||
</Box>
|
||||
</Card>
|
||||
</Box>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Leave Modal */}
|
||||
@@ -1273,6 +1256,6 @@ export default function Attendance() {
|
||||
cancelText="Zrušit"
|
||||
confirmVariant="primary"
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +165,7 @@ export default function AttendanceAdmin() {
|
||||
const hasTotals = Object.keys(data.user_totals).length > 0;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -202,14 +197,8 @@ export default function AttendanceAdmin() {
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
{/* Filters */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<FilterBar>
|
||||
<Box sx={{ flex: "0 0 200px" }}>
|
||||
<Field label="Měsíc">
|
||||
@@ -232,15 +221,9 @@ export default function AttendanceAdmin() {
|
||||
</Field>
|
||||
</Box>
|
||||
</FilterBar>
|
||||
</motion.div>
|
||||
|
||||
{/* User Totals (KPI cards) */}
|
||||
{hasTotals && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.09 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
@@ -256,9 +239,7 @@ export default function AttendanceAdmin() {
|
||||
{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";
|
||||
const statColor: StatCardColor = ut.working ? "success" : "default";
|
||||
return (
|
||||
<StatCard
|
||||
key={uid}
|
||||
@@ -404,15 +385,9 @@ export default function AttendanceAdmin() {
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Records Table */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
>
|
||||
<Card>
|
||||
<AttendanceShiftTable
|
||||
records={data.records}
|
||||
@@ -420,7 +395,6 @@ export default function AttendanceAdmin() {
|
||||
onDelete={(record) => setDeleteConfirm({ show: true, record })}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Modals */}
|
||||
<BulkAttendanceModal
|
||||
@@ -481,6 +455,6 @@ export default function AttendanceAdmin() {
|
||||
confirmText="Smazat"
|
||||
confirmVariant="danger"
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
Select,
|
||||
StatusChip,
|
||||
PageHeader,
|
||||
PageEnter,
|
||||
EmptyState,
|
||||
LoadingState,
|
||||
ProgressBar,
|
||||
@@ -392,7 +393,7 @@ export default function AttendanceBalances() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Správa bilancí"
|
||||
actions={
|
||||
@@ -815,6 +816,6 @@ export default function AttendanceBalances() {
|
||||
confirmVariant="danger"
|
||||
loading={resetMutation.isPending}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +118,7 @@ export default function AttendanceCreate() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Přidat záznam docházky"
|
||||
actions={
|
||||
@@ -137,13 +132,7 @@ export default function AttendanceCreate() {
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ maxWidth: 640 }}>
|
||||
<Box component="form" onSubmit={handleSubmit}>
|
||||
{/* Employee + Shift date */}
|
||||
@@ -220,17 +209,13 @@ export default function AttendanceCreate() {
|
||||
<Field label="Příchod - datum">
|
||||
<DateField
|
||||
value={form.arrival_date}
|
||||
onChange={(val) =>
|
||||
setForm({ ...form, arrival_date: val })
|
||||
}
|
||||
onChange={(val) => setForm({ ...form, arrival_date: val })}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Příchod - čas">
|
||||
<TimeField
|
||||
value={form.arrival_time}
|
||||
onChange={(val) =>
|
||||
setForm({ ...form, arrival_time: val })
|
||||
}
|
||||
onChange={(val) => setForm({ ...form, arrival_time: val })}
|
||||
/>
|
||||
</Field>
|
||||
</Box>
|
||||
@@ -341,7 +326,6 @@ export default function AttendanceCreate() {
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +494,7 @@ export default function AttendanceHistory() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Historie docházky"
|
||||
subtitle={computed.monthName}
|
||||
@@ -517,14 +512,8 @@ export default function AttendanceHistory() {
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* Filters */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<FilterBar>
|
||||
<Box sx={{ flex: "1 1 200px" }}>
|
||||
<Field label="Měsíc">
|
||||
@@ -532,14 +521,8 @@ export default function AttendanceHistory() {
|
||||
</Field>
|
||||
</Box>
|
||||
</FilterBar>
|
||||
</motion.div>
|
||||
|
||||
{/* Monthly Fund Card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.08 }}
|
||||
>
|
||||
<Card sx={{ mt: 3 }}>
|
||||
{isPending ? (
|
||||
<LoadingState />
|
||||
@@ -597,8 +580,7 @@ export default function AttendanceHistory() {
|
||||
sx={{ fontSize: "0.8125rem" }}
|
||||
>
|
||||
{fund.business_days} prac. dnů
|
||||
{fund.holiday_count > 0 &&
|
||||
` + ${fund.holiday_count} svátky`}
|
||||
{fund.holiday_count > 0 && ` + ${fund.holiday_count} svátky`}
|
||||
</Typography>
|
||||
</Box>
|
||||
<ProgressBar value={progressValue} color={progressColor} />
|
||||
@@ -661,14 +643,8 @@ export default function AttendanceHistory() {
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Records Table */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
>
|
||||
<Card sx={{ mt: 3 }}>
|
||||
{isPending ? (
|
||||
<LoadingState />
|
||||
@@ -677,13 +653,10 @@ export default function AttendanceHistory() {
|
||||
columns={columns}
|
||||
rows={records}
|
||||
rowKey={(record) => record.id}
|
||||
empty={
|
||||
<EmptyState title="Za tento měsíc nejsou žádné záznamy." />
|
||||
}
|
||||
empty={<EmptyState title="Za tento měsíc nejsou žádné záznamy." />}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Hidden Print Content */}
|
||||
{records.length > 0 && (
|
||||
@@ -888,7 +861,7 @@ export default function AttendanceHistory() {
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = (
|
||||
<svg
|
||||
@@ -187,13 +186,8 @@ export default function AttendanceLocation() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageHeader
|
||||
title="Poloha záznamu"
|
||||
actions={
|
||||
@@ -208,14 +202,8 @@ export default function AttendanceLocation() {
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* Info + map card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
{record.user_name} — {formatDate(record.shift_date)}
|
||||
@@ -354,7 +342,6 @@ export default function AttendanceLocation() {
|
||||
)}
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +267,7 @@ export default function AuditLog() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Audit log"
|
||||
subtitle={
|
||||
@@ -290,7 +285,6 @@ export default function AuditLog() {
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* Cleanup Modal */}
|
||||
<Modal
|
||||
@@ -371,6 +365,6 @@ export default function AuditLog() {
|
||||
onChange={setPage}
|
||||
/>
|
||||
</Card>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
{/* Header */}
|
||||
<Box
|
||||
component={motion.div}
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
sx={{ mb: 3 }}
|
||||
>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h4">
|
||||
Vítejte zpět, {user?.fullName || user?.username}
|
||||
</Typography>
|
||||
@@ -240,11 +233,6 @@ export default function Dashboard() {
|
||||
|
||||
{/* 2FA Required Banner */}
|
||||
{user?.require2FA && !user?.totpEnabled && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card
|
||||
sx={{
|
||||
mb: 3,
|
||||
@@ -308,7 +296,6 @@ export default function Dashboard() {
|
||||
</Button>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Loading spinner */}
|
||||
@@ -339,10 +326,6 @@ export default function Dashboard() {
|
||||
{/* Main content grid */}
|
||||
{!dashLoading && (
|
||||
<Box
|
||||
component={motion.div}
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", lg: "1fr 1fr 1fr" },
|
||||
@@ -523,6 +506,6 @@ export default function Dashboard() {
|
||||
<DashSessions />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 <LoadingState />;
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -1018,14 +1014,9 @@ export default function InvoiceDetail() {
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Info */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Informace
|
||||
@@ -1110,14 +1101,10 @@ export default function InvoiceDetail() {
|
||||
}}
|
||||
>
|
||||
<Field label="Forma úhrady">
|
||||
<Typography variant="body2">
|
||||
{invoice.payment_method}
|
||||
</Typography>
|
||||
<Typography variant="body2">{invoice.payment_method}</Typography>
|
||||
</Field>
|
||||
<Field label="Variabilní symbol">
|
||||
<Typography variant="body2">
|
||||
{invoice.invoice_number}
|
||||
</Typography>
|
||||
<Typography variant="body2">{invoice.invoice_number}</Typography>
|
||||
</Field>
|
||||
<Field label="Vystavil">
|
||||
<Typography variant="body2">
|
||||
@@ -1138,14 +1125,8 @@ export default function InvoiceDetail() {
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Items (read-only) */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Položky
|
||||
@@ -1221,9 +1202,7 @@ export default function InvoiceDetail() {
|
||||
</Box>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell align="center">
|
||||
{item.unit || "—"}
|
||||
</TableCell>
|
||||
<TableCell align="center">{item.unit || "—"}</TableCell>
|
||||
<TableCell
|
||||
align="right"
|
||||
sx={{
|
||||
@@ -1323,14 +1302,8 @@ export default function InvoiceDetail() {
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Notes (read-only) */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.15 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Veřejné poznámky na faktuře
|
||||
@@ -1346,7 +1319,6 @@ export default function InvoiceDetail() {
|
||||
</Typography>
|
||||
)}
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Delete confirm */}
|
||||
<ConfirmDialog
|
||||
@@ -1360,7 +1332,7 @@ export default function InvoiceDetail() {
|
||||
confirmVariant="danger"
|
||||
loading={deleting}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1371,12 +1343,8 @@ export default function InvoiceDetail() {
|
||||
return <LoadingState />;
|
||||
}
|
||||
return (
|
||||
<PageEnter>
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -1501,15 +1469,10 @@ export default function InvoiceDetail() {
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
<form onSubmit={handleCreateSubmit}>
|
||||
{/* Basic info */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Základní údaje
|
||||
@@ -1782,11 +1745,7 @@ export default function InvoiceDetail() {
|
||||
</Field>
|
||||
</Box>
|
||||
|
||||
<Field
|
||||
label="Bankovní účet"
|
||||
error={errors.bank_account_id}
|
||||
required
|
||||
>
|
||||
<Field label="Bankovní účet" error={errors.bank_account_id} required>
|
||||
<Select
|
||||
value={String(form.bank_account_id)}
|
||||
onChange={(val) => {
|
||||
@@ -1808,14 +1767,8 @@ export default function InvoiceDetail() {
|
||||
/>
|
||||
</Field>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Items */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -1975,14 +1928,8 @@ export default function InvoiceDetail() {
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Notes */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.15 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Veřejné poznámky na faktuře
|
||||
@@ -1997,7 +1944,6 @@ export default function InvoiceDetail() {
|
||||
placeholder="Poznámky zobrazené na faktuře..."
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</form>
|
||||
|
||||
{/* Status change confirm (edit mode only) */}
|
||||
@@ -2028,6 +1974,6 @@ export default function InvoiceDetail() {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useAlert } from "../context/AlertContext";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import { Link as RouterLink, 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";
|
||||
@@ -35,6 +34,7 @@ import {
|
||||
FilterBar,
|
||||
Tabs,
|
||||
PageHeader,
|
||||
PageEnter,
|
||||
type DataColumn,
|
||||
type TabDef,
|
||||
type StatCardColor,
|
||||
@@ -644,12 +644,7 @@ export default function Invoices() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Faktury"
|
||||
subtitle={subtitle}
|
||||
@@ -674,13 +669,8 @@ export default function Invoices() {
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -722,14 +712,9 @@ export default function Invoices() {
|
||||
]}
|
||||
/>
|
||||
</Box>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{activeTab === "received" ? (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.1 }}
|
||||
>
|
||||
<Suspense fallback={<LoadingState />}>
|
||||
<ReceivedInvoices
|
||||
statsMonth={statsMonth}
|
||||
@@ -738,22 +723,10 @@ export default function Invoices() {
|
||||
setUploadOpen={setReceivedUploadOpen}
|
||||
/>
|
||||
</Suspense>
|
||||
</motion.div>
|
||||
) : (
|
||||
<>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.1 }}
|
||||
>
|
||||
{renderKpi()}
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
>
|
||||
<Box sx={{ mb: 2.5 }}>
|
||||
<Tabs
|
||||
value={statusFilter}
|
||||
@@ -764,7 +737,6 @@ export default function Invoices() {
|
||||
tabs={statusTabs}
|
||||
/>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
<FilterBar>
|
||||
<Box sx={{ flex: "1 1 320px" }}>
|
||||
@@ -845,14 +817,7 @@ export default function Invoices() {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.15 }}
|
||||
>
|
||||
<Card
|
||||
sx={{ opacity: loading ? 0.6 : 1, transition: "opacity .2s" }}
|
||||
>
|
||||
<Card sx={{ opacity: loading ? 0.6 : 1, transition: "opacity .2s" }}>
|
||||
<DataTable<Invoice>
|
||||
columns={columns}
|
||||
rows={invoices}
|
||||
@@ -878,7 +843,6 @@ export default function Invoices() {
|
||||
onChange={setPage}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<ConfirmDialog
|
||||
isOpen={deleteConfirm.show}
|
||||
@@ -893,6 +857,6 @@ export default function Invoices() {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import { useAlert } from "../context/AlertContext";
|
||||
import { motion } from "framer-motion";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { formatDate, formatDatetime } from "../utils/attendanceHelpers";
|
||||
@@ -23,6 +22,7 @@ import {
|
||||
Field,
|
||||
TextField,
|
||||
PageHeader,
|
||||
PageEnter,
|
||||
EmptyState,
|
||||
LoadingState,
|
||||
Tabs,
|
||||
@@ -304,12 +304,7 @@ export default function LeaveApproval() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Schvalování nepřítomnosti"
|
||||
subtitle={
|
||||
@@ -318,19 +313,12 @@ export default function LeaveApproval() {
|
||||
: "Žádné čekající žádosti"
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={(v) => setActiveTab(v as "pending" | "processed")}
|
||||
tabs={tabs}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* Pending Tab */}
|
||||
<TabPanel value="pending" current={activeTab}>
|
||||
@@ -498,6 +486,6 @@ export default function LeaveApproval() {
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useAlert } from "../context/AlertContext";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import { motion } from "framer-motion";
|
||||
import Box from "@mui/material/Box";
|
||||
import Forbidden from "../components/Forbidden";
|
||||
import { formatDate, formatDatetime } from "../utils/attendanceHelpers";
|
||||
@@ -15,6 +14,7 @@ import {
|
||||
ConfirmDialog,
|
||||
StatusChip,
|
||||
PageHeader,
|
||||
PageEnter,
|
||||
EmptyState,
|
||||
LoadingState,
|
||||
type DataColumn,
|
||||
@@ -237,23 +237,12 @@ export default function LeaveRequests() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Moje žádosti"
|
||||
subtitle="Přehled žádostí o nepřítomnost"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card>
|
||||
<DataTable<LeaveRequest>
|
||||
columns={columns}
|
||||
@@ -267,7 +256,6 @@ export default function LeaveRequests() {
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<ConfirmDialog
|
||||
isOpen={cancelModal.open}
|
||||
@@ -278,6 +266,6 @@ export default function LeaveRequests() {
|
||||
confirmText="Zrušit žádost"
|
||||
loading={cancelMutation.isPending}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { useAlert } from "../context/AlertContext";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
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 IconButton from "@mui/material/IconButton";
|
||||
@@ -72,6 +71,7 @@ import {
|
||||
ConfirmDialog,
|
||||
EmptyState,
|
||||
LoadingState,
|
||||
PageEnter,
|
||||
} from "../ui";
|
||||
|
||||
const API_BASE = "/api/admin";
|
||||
@@ -915,13 +915,9 @@ export default function OfferDetail() {
|
||||
return <LoadingState />;
|
||||
}
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -1032,7 +1028,7 @@ export default function OfferDetail() {
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Lock banner */}
|
||||
{isLockedByOther && (
|
||||
@@ -1072,11 +1068,6 @@ export default function OfferDetail() {
|
||||
)}
|
||||
|
||||
{/* Quotation Form */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Základní údaje
|
||||
@@ -1301,9 +1292,7 @@ export default function OfferDetail() {
|
||||
<Box sx={{ display: "flex", gap: 1, alignItems: "center" }}>
|
||||
<Select
|
||||
value={String(form.vat_rate)}
|
||||
onChange={(val) =>
|
||||
updateForm("vat_rate", parseFloat(val) || 0)
|
||||
}
|
||||
onChange={(val) => updateForm("vat_rate", parseFloat(val) || 0)}
|
||||
disabled={readOnly}
|
||||
sx={{ flex: 1 }}
|
||||
options={(
|
||||
@@ -1331,14 +1320,8 @@ export default function OfferDetail() {
|
||||
</Field>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Items Section with drag-and-drop */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -1542,14 +1525,8 @@ export default function OfferDetail() {
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Scope/Range Section */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.15 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -1574,12 +1551,13 @@ export default function OfferDetail() {
|
||||
(t) => t.id === templateId,
|
||||
);
|
||||
if (template?.scope_template_sections?.length) {
|
||||
const newSections =
|
||||
template.scope_template_sections.map((s) => ({
|
||||
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}"`);
|
||||
}
|
||||
@@ -1772,9 +1750,7 @@ export default function OfferDetail() {
|
||||
onChange={(e) =>
|
||||
setSections((prev) =>
|
||||
prev.map((s, i) =>
|
||||
i === idx
|
||||
? { ...s, title_cz: e.target.value }
|
||||
: s,
|
||||
i === idx ? { ...s, title_cz: e.target.value } : s,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -1804,7 +1780,6 @@ export default function OfferDetail() {
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Order modal */}
|
||||
<Modal
|
||||
@@ -1901,6 +1876,6 @@ export default function OfferDetail() {
|
||||
confirmVariant="danger"
|
||||
loading={deleting}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
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";
|
||||
@@ -34,6 +33,7 @@ import {
|
||||
FilterBar,
|
||||
Tabs,
|
||||
PageHeader,
|
||||
PageEnter,
|
||||
LoadingState,
|
||||
type DataColumn,
|
||||
type TabDef,
|
||||
@@ -686,12 +686,7 @@ export default function Offers() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Nabídky"
|
||||
subtitle={subtitle}
|
||||
@@ -720,13 +715,7 @@ export default function Offers() {
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.1 }}
|
||||
>
|
||||
<Box sx={{ mb: 2.5 }}>
|
||||
<Tabs
|
||||
value={statusFilter}
|
||||
@@ -737,7 +726,6 @@ export default function Offers() {
|
||||
tabs={tabs}
|
||||
/>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
<FilterBar>
|
||||
<Box sx={{ flex: "1 1 320px" }}>
|
||||
@@ -838,11 +826,6 @@ export default function Offers() {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.15 }}
|
||||
>
|
||||
<Card sx={{ opacity: isFetching ? 0.6 : 1, transition: "opacity .2s" }}>
|
||||
<DataTable<Quotation>
|
||||
columns={columns}
|
||||
@@ -875,7 +858,6 @@ export default function Offers() {
|
||||
onChange={setPage}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<ConfirmDialog
|
||||
isOpen={deleteConfirm.show}
|
||||
@@ -935,6 +917,6 @@ export default function Offers() {
|
||||
</Typography>
|
||||
</Field>
|
||||
</Modal>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState, useCallback, useRef } 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 IconButton from "@mui/material/IconButton";
|
||||
@@ -24,6 +23,7 @@ import {
|
||||
CheckboxField,
|
||||
StatusChip,
|
||||
PageHeader,
|
||||
PageEnter,
|
||||
FilterBar,
|
||||
EmptyState,
|
||||
LoadingState,
|
||||
@@ -468,12 +468,7 @@ export default function OffersCustomers() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Zákazníci"
|
||||
subtitle="Správa zákazníků pro nabídky"
|
||||
@@ -485,7 +480,6 @@ export default function OffersCustomers() {
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<FilterBar>
|
||||
<Box sx={{ flex: "1 1 320px" }}>
|
||||
@@ -816,6 +810,6 @@ export default function OffersCustomers() {
|
||||
confirmVariant="danger"
|
||||
loading={deleting}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState, useRef } 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 IconButton from "@mui/material/IconButton";
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
TextField,
|
||||
StatusChip,
|
||||
PageHeader,
|
||||
PageEnter,
|
||||
EmptyState,
|
||||
LoadingState,
|
||||
Tabs,
|
||||
@@ -142,29 +142,17 @@ export default function OffersTemplates() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Šablony"
|
||||
subtitle="Šablony položek a rozsahu projektu"
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={(v) => setActiveTab(v as "items" | "scopes")}
|
||||
tabs={tabs}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<TabPanel value="items" current={activeTab}>
|
||||
<ItemTemplatesTab />
|
||||
@@ -172,7 +160,7 @@ export default function OffersTemplates() {
|
||||
<TabPanel value="scopes" current={activeTab}>
|
||||
<ScopeTemplatesTab />
|
||||
</TabPanel>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import { useApiMutation } from "../lib/queries/mutations";
|
||||
import { useAlert } from "../context/AlertContext";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
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 OrderConfirmationModal from "../components/OrderConfirmationModal";
|
||||
@@ -31,6 +30,7 @@ import {
|
||||
ConfirmDialog,
|
||||
EmptyState,
|
||||
LoadingState,
|
||||
PageEnter,
|
||||
type DataColumn,
|
||||
} from "../ui";
|
||||
|
||||
@@ -390,13 +390,9 @@ export default function OrderDetail() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -498,14 +494,9 @@ export default function OrderDetail() {
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Info card */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Informace
|
||||
@@ -643,14 +634,8 @@ export default function OrderDetail() {
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Items (read-only) */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Položky
|
||||
@@ -722,15 +707,9 @@ export default function OrderDetail() {
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Sections (read-only) */}
|
||||
{order.sections?.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.15 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Rozsah projektu
|
||||
@@ -793,15 +772,9 @@ export default function OrderDetail() {
|
||||
))}
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Notes (editable) */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.2 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Poznámky
|
||||
@@ -830,7 +803,6 @@ export default function OrderDetail() {
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Status change confirmation */}
|
||||
<ConfirmDialog
|
||||
@@ -887,6 +859,6 @@ export default function OrderDetail() {
|
||||
applyVat={!!order.apply_vat}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useQuery, useQueryClient } 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 IconButton from "@mui/material/IconButton";
|
||||
import { useAlert } from "../context/AlertContext";
|
||||
@@ -32,6 +31,7 @@ import {
|
||||
CheckboxField,
|
||||
FileUpload,
|
||||
PageHeader,
|
||||
PageEnter,
|
||||
FilterBar,
|
||||
EmptyState,
|
||||
LoadingState,
|
||||
@@ -463,12 +463,7 @@ export default function Orders() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Objednávky"
|
||||
subtitle={subtitle}
|
||||
@@ -480,7 +475,6 @@ export default function Orders() {
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<FilterBar>
|
||||
<Box sx={{ flex: "1 1 320px" }}>
|
||||
@@ -496,11 +490,6 @@ export default function Orders() {
|
||||
</Box>
|
||||
</FilterBar>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ opacity: isFetching ? 0.6 : 1, transition: "opacity .2s" }}>
|
||||
<DataTable<Order>
|
||||
columns={columns}
|
||||
@@ -522,7 +511,6 @@ export default function Orders() {
|
||||
onChange={setPage}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<ConfirmDialog
|
||||
isOpen={deleteConfirm.show}
|
||||
@@ -711,6 +699,6 @@ export default function Orders() {
|
||||
onChange={(v) => setCreateForm({ ...createForm, create_project: v })}
|
||||
/>
|
||||
</Modal>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import PlanGrid from "../components/PlanGrid";
|
||||
import PlanCellModal from "../components/PlanCellModal";
|
||||
import PlanCategoriesModal from "../components/PlanCategoriesModal";
|
||||
import Forbidden from "../components/Forbidden";
|
||||
import { Button, Alert, LoadingState } from "../ui";
|
||||
import { Button, Alert, LoadingState, PageEnter } from "../ui";
|
||||
import { projectListOptions, type Project } from "../lib/queries/projects";
|
||||
import { planCategoriesOptions, type PlanCategory } from "../lib/queries/plan";
|
||||
// plan.css is imported once globally in AdminApp.tsx — no per-page re-import.
|
||||
@@ -545,38 +545,20 @@ export default function PlanWork() {
|
||||
}
|
||||
|
||||
return (
|
||||
<Box className="plan-work-page">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter sx={{ position: "relative" }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 700, mb: 2 }}>
|
||||
Plán prací
|
||||
</Typography>
|
||||
</motion.div>
|
||||
|
||||
{!canEdit && (
|
||||
<motion.div
|
||||
role="status"
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Box role="status" sx={{ mb: 2 }}>
|
||||
<Alert severity="info">
|
||||
<strong>Režim náhledu</strong> — můžete pouze prohlížet plán.
|
||||
Úpravy jsou dostupné oprávněným uživatelům.
|
||||
<strong>Režim náhledu</strong> — můžete pouze prohlížet plán. Úpravy
|
||||
jsou dostupné oprávněným uživatelům.
|
||||
</Alert>
|
||||
</Box>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -629,11 +611,7 @@ export default function PlanWork() {
|
||||
variant="h6"
|
||||
sx={{ fontWeight: 600, whiteSpace: "nowrap" }}
|
||||
>
|
||||
{formatRangeLabel(
|
||||
isoDate(range.from),
|
||||
isoDate(range.to),
|
||||
view,
|
||||
)}
|
||||
{formatRangeLabel(isoDate(range.from), isoDate(range.to), view)}
|
||||
</Typography>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
@@ -668,7 +646,6 @@ export default function PlanWork() {
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
{gridLoading && <LoadingState />}
|
||||
|
||||
@@ -689,11 +666,6 @@ export default function PlanWork() {
|
||||
patch the cache in place and pulse a single cell via pulseKey.
|
||||
*/}
|
||||
{grid ? (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, ease: "easeOut", delay: 0.18 }}
|
||||
>
|
||||
<PlanGrid
|
||||
data={grid}
|
||||
projects={projects}
|
||||
@@ -702,7 +674,6 @@ export default function PlanWork() {
|
||||
pulseKey={lastMutated}
|
||||
onCellClick={openCell}
|
||||
/>
|
||||
</motion.div>
|
||||
) : null}
|
||||
|
||||
<PlanCellModal
|
||||
@@ -726,6 +697,6 @@ export default function PlanWork() {
|
||||
onClose={() => setCatModalOpen(false)}
|
||||
categories={categories}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useApiMutation } from "../lib/queries/mutations";
|
||||
import { useAlert } from "../context/AlertContext";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
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 IconButton from "@mui/material/IconButton";
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
CheckboxField,
|
||||
ConfirmDialog,
|
||||
LoadingState,
|
||||
PageEnter,
|
||||
} from "../ui";
|
||||
|
||||
const API_BASE = "/api/admin";
|
||||
@@ -286,13 +286,9 @@ export default function ProjectDetail() {
|
||||
const canEdit = hasPermission("projects.edit");
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -347,14 +343,9 @@ export default function ProjectDetail() {
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Basic info form */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Základní údaje
|
||||
@@ -434,14 +425,8 @@ export default function ProjectDetail() {
|
||||
</Field>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Notes */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.08 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Poznámky
|
||||
@@ -573,29 +558,18 @@ export default function ProjectDetail() {
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Project File Manager */}
|
||||
<motion.div
|
||||
style={{ marginBottom: "1rem" }}
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<ProjectFileManager
|
||||
projectId={project.id}
|
||||
projectNumber={project.project_number}
|
||||
hasPermission={hasPermission}
|
||||
hasNasFolder={project.has_nas_folder ?? false}
|
||||
/>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Links */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.15 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Propojení
|
||||
@@ -664,7 +638,6 @@ export default function ProjectDetail() {
|
||||
</Box>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<ConfirmDialog
|
||||
isOpen={deleteConfirm}
|
||||
@@ -687,6 +660,6 @@ export default function ProjectDetail() {
|
||||
/>
|
||||
)}
|
||||
</ConfirmDialog>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +346,7 @@ export default function Projects() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -369,7 +364,6 @@ export default function Projects() {
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<TextField
|
||||
@@ -383,11 +377,6 @@ export default function Projects() {
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ opacity: isFetching ? 0.6 : 1, transition: "opacity .2s" }}>
|
||||
<DataTable<Project>
|
||||
columns={columns}
|
||||
@@ -414,7 +403,6 @@ export default function Projects() {
|
||||
onChange={setPage}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<ConfirmDialog
|
||||
isOpen={!!deleteTarget}
|
||||
@@ -538,6 +526,6 @@ export default function Projects() {
|
||||
/>
|
||||
</Field>
|
||||
</Modal>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +780,7 @@ export default function Settings() {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography variant="h4">Nastavení</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
|
||||
@@ -796,7 +791,6 @@ export default function Settings() {
|
||||
: "Role"}
|
||||
</Typography>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
{tabDefs.length > 0 && (
|
||||
<Tabs
|
||||
@@ -808,11 +802,6 @@ export default function Settings() {
|
||||
|
||||
{/* Roles tab */}
|
||||
<TabPanel value="roles" current={activeTab}>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -833,18 +822,12 @@ export default function Settings() {
|
||||
rowKey={(role) => role.id}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
|
||||
{/* System settings tab */}
|
||||
<TabPanel value="system" current={activeTab}>
|
||||
{canManageSystem && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
style={{ marginBottom: "1.5rem" }}
|
||||
>
|
||||
<Box sx={{ mb: "1.5rem" }}>
|
||||
<Card>
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||
Zabezpečení
|
||||
@@ -908,16 +891,11 @@ export default function Settings() {
|
||||
)}
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{canManageSystem && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.08 }}
|
||||
style={{ marginBottom: "1.5rem" }}
|
||||
>
|
||||
<Box sx={{ mb: "1.5rem" }}>
|
||||
<Card>
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||
Přihlašování
|
||||
@@ -968,7 +946,7 @@ export default function Settings() {
|
||||
</Button>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{sysSettingsLoading && !sysFormInitialized ? (
|
||||
@@ -976,12 +954,7 @@ export default function Settings() {
|
||||
) : (
|
||||
<>
|
||||
{/* Section 1: Docházka */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
style={{ marginBottom: "1.5rem" }}
|
||||
>
|
||||
<Box sx={{ mb: "1.5rem" }}>
|
||||
<Card>
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||
Docházka
|
||||
@@ -1051,15 +1024,10 @@ export default function Settings() {
|
||||
</Field>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Section 2: Emailové notifikace */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
style={{ marginBottom: "1.5rem" }}
|
||||
>
|
||||
<Box sx={{ mb: "1.5rem" }}>
|
||||
<Card>
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||
Emailové notifikace
|
||||
@@ -1124,15 +1092,10 @@ export default function Settings() {
|
||||
</Field>
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Section 4: Omezení požadavků */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.24 }}
|
||||
style={{ marginBottom: "1.5rem" }}
|
||||
>
|
||||
<Box sx={{ mb: "1.5rem" }}>
|
||||
<Card>
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||
Omezení požadavků
|
||||
@@ -1154,39 +1117,26 @@ export default function Settings() {
|
||||
/>
|
||||
</Field>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Section 5: Informace o aplikaci */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.36 }}
|
||||
style={{ marginBottom: "1.5rem" }}
|
||||
>
|
||||
<Box sx={{ mb: "1.5rem" }}>
|
||||
<Card>
|
||||
<Typography variant="h6" sx={{ mb: 2 }}>
|
||||
Informace o aplikaci
|
||||
</Typography>
|
||||
{renderSystemInfo()}
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
|
||||
{/* Save button */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.42 }}
|
||||
>
|
||||
<Button
|
||||
fullWidth
|
||||
onClick={handleSaveSystemSettings}
|
||||
disabled={saveSystemSettingsMutation.isPending}
|
||||
>
|
||||
{saveSystemSettingsMutation.isPending
|
||||
? "Ukládání..."
|
||||
: "Uložit"}
|
||||
{saveSystemSettingsMutation.isPending ? "Ukládání..." : "Uložit"}
|
||||
</Button>
|
||||
</motion.div>
|
||||
</>
|
||||
)}
|
||||
</TabPanel>
|
||||
@@ -1399,6 +1349,6 @@ export default function Settings() {
|
||||
confirmVariant="danger"
|
||||
loading={deleteRoleMutation.isPending}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +418,7 @@ export default function Trips() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -447,14 +442,8 @@ export default function Trips() {
|
||||
Přidat jízdu
|
||||
</Button>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
@@ -491,14 +480,8 @@ export default function Trips() {
|
||||
color="warning"
|
||||
/>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
{/* Recent Trips */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
>
|
||||
<Card sx={{ mt: 3 }}>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -535,7 +518,6 @@ export default function Trips() {
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Add/Edit Modal */}
|
||||
<Modal
|
||||
@@ -696,6 +678,6 @@ export default function Trips() {
|
||||
confirmVariant="danger"
|
||||
loading={deleteMutation.isPending}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +529,7 @@ export default function TripsAdmin() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -568,14 +563,8 @@ export default function TripsAdmin() {
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
{/* Filters */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<FilterBar>
|
||||
<Box sx={{ flex: "0 0 160px" }}>
|
||||
<Field label="Měsíc">
|
||||
@@ -634,14 +623,8 @@ export default function TripsAdmin() {
|
||||
</Field>
|
||||
</Box>
|
||||
</FilterBar>
|
||||
</motion.div>
|
||||
|
||||
{/* Stats Cards */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.08 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
@@ -671,14 +654,8 @@ export default function TripsAdmin() {
|
||||
color="success"
|
||||
/>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
{/* Trips Table */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
>
|
||||
<Card sx={{ mt: 3 }}>
|
||||
{isPending ? (
|
||||
<LoadingState />
|
||||
@@ -693,7 +670,6 @@ export default function TripsAdmin() {
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Edit Modal */}
|
||||
<Modal
|
||||
@@ -962,6 +938,6 @@ export default function TripsAdmin() {
|
||||
</table>
|
||||
</div>
|
||||
)}
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,21 +216,10 @@ export default function TripsHistory() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader title="Historie jízd" subtitle={getMonthName(month)} />
|
||||
</motion.div>
|
||||
|
||||
{/* Filters */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<FilterBar>
|
||||
<Box sx={{ flex: "1 1 200px" }}>
|
||||
<Field label="Měsíc">
|
||||
@@ -253,13 +242,7 @@ export default function TripsHistory() {
|
||||
</Field>
|
||||
</Box>
|
||||
</FilterBar>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.08 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
@@ -291,14 +274,8 @@ export default function TripsHistory() {
|
||||
color="success"
|
||||
/>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
{/* Trips Table */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
>
|
||||
<Card sx={{ mt: 3 }}>
|
||||
{isPending ? (
|
||||
<LoadingState />
|
||||
@@ -313,7 +290,6 @@ export default function TripsHistory() {
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +324,7 @@ export default function Users() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -345,13 +340,7 @@ export default function Users() {
|
||||
Přidat uživatele
|
||||
</Button>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card>
|
||||
<DataTable<User>
|
||||
columns={columns}
|
||||
@@ -370,7 +359,6 @@ export default function Users() {
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<Modal
|
||||
isOpen={showModal}
|
||||
@@ -478,6 +466,6 @@ export default function Users() {
|
||||
confirmVariant="danger"
|
||||
loading={deleteUser.isPending}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +299,7 @@ export default function Vehicles() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -320,13 +315,7 @@ export default function Vehicles() {
|
||||
Přidat vozidlo
|
||||
</Button>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card>
|
||||
<DataTable<Vehicle>
|
||||
columns={columns}
|
||||
@@ -345,7 +334,6 @@ export default function Vehicles() {
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
<Modal
|
||||
isOpen={showModal}
|
||||
@@ -438,6 +426,6 @@ export default function Vehicles() {
|
||||
confirmText="Smazat"
|
||||
confirmVariant="danger"
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +193,7 @@ export default function Warehouse() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Sklad"
|
||||
subtitle="Přehled skladových zásob"
|
||||
@@ -253,14 +248,9 @@ export default function Warehouse() {
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* KPI cards */}
|
||||
<Box
|
||||
component={motion.div}
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: {
|
||||
@@ -296,13 +286,7 @@ export default function Warehouse() {
|
||||
|
||||
{/* Below minimum alert */}
|
||||
{belowMin.length > 0 && (
|
||||
<Box
|
||||
component={motion.div}
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.14 }}
|
||||
sx={{ mb: 3 }}
|
||||
>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Card sx={{ borderColor: "error.main", borderWidth: 1 }}>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -342,12 +326,7 @@ export default function Warehouse() {
|
||||
)}
|
||||
|
||||
{/* Recent movements */}
|
||||
<Box
|
||||
component={motion.div}
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.16 }}
|
||||
>
|
||||
<Box>
|
||||
<Card>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -381,6 +360,6 @@ export default function Warehouse() {
|
||||
)}
|
||||
</Card>
|
||||
</Box>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
Field,
|
||||
TextField,
|
||||
PageHeader,
|
||||
PageEnter,
|
||||
EmptyState,
|
||||
LoadingState,
|
||||
type DataColumn,
|
||||
@@ -238,7 +239,7 @@ export default function WarehouseCategories() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Kategorie skladu"
|
||||
subtitle={subtitle}
|
||||
@@ -327,6 +328,6 @@ export default function WarehouseCategories() {
|
||||
confirmVariant="danger"
|
||||
loading={deleteMutation.isPending}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +127,7 @@ export default function WarehouseInventory() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Inventury"
|
||||
subtitle={subtitle}
|
||||
@@ -145,7 +140,6 @@ export default function WarehouseInventory() {
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<FilterBar>
|
||||
<Box sx={{ flex: "0 0 200px" }}>
|
||||
@@ -199,6 +193,6 @@ export default function WarehouseInventory() {
|
||||
onChange={setPage}
|
||||
/>
|
||||
</Card>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,13 +187,8 @@ export default function WarehouseInventoryDetail() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageHeader
|
||||
title={s.session_number || "Inventura"}
|
||||
actions={
|
||||
@@ -222,14 +217,8 @@ export default function WarehouseInventoryDetail() {
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* Header info */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Základní údaje
|
||||
@@ -278,14 +267,8 @@ export default function WarehouseInventoryDetail() {
|
||||
)}
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Lines table */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.08 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Položky
|
||||
@@ -297,7 +280,6 @@ export default function WarehouseInventoryDetail() {
|
||||
empty={<EmptyState title="Žádné řádky" />}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Confirm modal */}
|
||||
<ConfirmDialog
|
||||
@@ -309,6 +291,6 @@ export default function WarehouseInventoryDetail() {
|
||||
confirmText="Potvrdit inventuru"
|
||||
loading={confirming}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,13 +164,8 @@ export default function WarehouseInventoryForm() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -199,14 +193,8 @@ export default function WarehouseInventoryForm() {
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
{/* Notes */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Základní údaje
|
||||
@@ -221,14 +209,8 @@ export default function WarehouseInventoryForm() {
|
||||
/>
|
||||
</Field>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Lines */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.08 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Položky
|
||||
@@ -297,7 +279,6 @@ export default function WarehouseInventoryForm() {
|
||||
Přidat položku
|
||||
</Button>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,13 +223,8 @@ export default function WarehouseIssueDetail() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageHeader
|
||||
title={iss.issue_number || "Nová výdejka"}
|
||||
subtitle={
|
||||
@@ -285,14 +280,8 @@ export default function WarehouseIssueDetail() {
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* Basic info */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Základní údaje
|
||||
@@ -381,14 +370,8 @@ export default function WarehouseIssueDetail() {
|
||||
)}
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Lines table */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.08 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Položky
|
||||
@@ -400,7 +383,6 @@ export default function WarehouseIssueDetail() {
|
||||
empty={<EmptyState title="Žádné řádky" />}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Cancel confirmation dialog */}
|
||||
<ConfirmDialog
|
||||
@@ -413,6 +395,6 @@ export default function WarehouseIssueDetail() {
|
||||
confirmVariant="danger"
|
||||
loading={cancelling}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,13 +429,8 @@ export default function WarehouseIssueForm() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -466,14 +468,8 @@ export default function WarehouseIssueForm() {
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
{/* Header fields */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Základní údaje
|
||||
@@ -502,14 +498,8 @@ export default function WarehouseIssueForm() {
|
||||
/>
|
||||
</Field>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Lines */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.08 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Položky
|
||||
@@ -614,7 +604,6 @@ export default function WarehouseIssueForm() {
|
||||
Přidat položku
|
||||
</Button>
|
||||
</Card>
|
||||
</motion.div>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +166,7 @@ export default function WarehouseIssues() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Výdejky"
|
||||
subtitle={subtitle}
|
||||
@@ -186,7 +181,6 @@ export default function WarehouseIssues() {
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<FilterBar>
|
||||
<Box sx={{ flex: "1 1 220px" }}>
|
||||
@@ -292,6 +286,6 @@ export default function WarehouseIssues() {
|
||||
onChange={setPage}
|
||||
/>
|
||||
</Card>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,13 +356,8 @@ export default function WarehouseItemDetail() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageHeader
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
@@ -381,14 +376,8 @@ export default function WarehouseItemDetail() {
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* Basic info */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Základní údaje
|
||||
@@ -574,15 +563,9 @@ export default function WarehouseItemDetail() {
|
||||
</Box>
|
||||
)}
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Stock info — only in view mode, for existing items */}
|
||||
{id !== "new" && !editing && item && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.08 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
@@ -619,16 +602,10 @@ export default function WarehouseItemDetail() {
|
||||
color={item.below_minimum ? "error" : "info"}
|
||||
/>
|
||||
</Box>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Batches table — only in view mode, for existing items */}
|
||||
{id !== "new" && !editing && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.1 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Dávky (FIFO)
|
||||
@@ -640,16 +617,10 @@ export default function WarehouseItemDetail() {
|
||||
empty={<EmptyState title="Zatím žádné dávky" />}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Locations table — only in view mode, for existing items */}
|
||||
{id !== "new" && !editing && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.12 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Umístění
|
||||
@@ -661,7 +632,6 @@ export default function WarehouseItemDetail() {
|
||||
empty={<EmptyState title="Zatím žádná umístění" />}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Delete confirmation */}
|
||||
@@ -675,6 +645,6 @@ export default function WarehouseItemDetail() {
|
||||
confirmVariant="danger"
|
||||
loading={deleteMutation.isPending}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +153,7 @@ export default function WarehouseItems() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Skladové položky"
|
||||
subtitle={subtitle}
|
||||
@@ -173,7 +168,6 @@ export default function WarehouseItems() {
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<FilterBar>
|
||||
<Box sx={{ flex: "1 1 280px" }}>
|
||||
@@ -243,6 +237,6 @@ export default function WarehouseItems() {
|
||||
onChange={setPage}
|
||||
/>
|
||||
</Card>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
TextField,
|
||||
StatusChip,
|
||||
PageHeader,
|
||||
PageEnter,
|
||||
EmptyState,
|
||||
LoadingState,
|
||||
type DataColumn,
|
||||
@@ -303,7 +304,7 @@ export default function WarehouseLocations() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Umístění skladu"
|
||||
subtitle={subtitle}
|
||||
@@ -401,6 +402,6 @@ export default function WarehouseLocations() {
|
||||
confirmVariant="danger"
|
||||
loading={deleteMutation.isPending}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,13 +295,8 @@ export default function WarehouseReceiptDetail() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageHeader
|
||||
title={r.receipt_number || "Nový doklad"}
|
||||
subtitle={r.supplier?.name}
|
||||
@@ -353,14 +348,8 @@ export default function WarehouseReceiptDetail() {
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
{/* Basic info */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Základní údaje
|
||||
@@ -461,14 +450,8 @@ export default function WarehouseReceiptDetail() {
|
||||
)}
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Lines table */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.08 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Položky
|
||||
@@ -480,14 +463,8 @@ export default function WarehouseReceiptDetail() {
|
||||
empty={<EmptyState title="Žádné řádky" />}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Attachments */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.1 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Přílohy
|
||||
@@ -499,7 +476,6 @@ export default function WarehouseReceiptDetail() {
|
||||
empty={<EmptyState title="Žádné přílohy" />}
|
||||
/>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Cancel confirmation dialog */}
|
||||
<ConfirmDialog
|
||||
@@ -512,6 +488,6 @@ export default function WarehouseReceiptDetail() {
|
||||
confirmVariant="danger"
|
||||
loading={cancelling}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,13 +410,8 @@ export default function WarehouseReceiptForm() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<PageEnter>
|
||||
{/* Header */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@@ -454,14 +449,8 @@ export default function WarehouseReceiptForm() {
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</motion.div>
|
||||
|
||||
{/* Header fields */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Základní údaje
|
||||
@@ -521,14 +510,8 @@ export default function WarehouseReceiptForm() {
|
||||
/>
|
||||
</Field>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* Lines */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.08 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Položky
|
||||
@@ -580,11 +563,7 @@ export default function WarehouseReceiptForm() {
|
||||
type="number"
|
||||
value={item.unit_price || ""}
|
||||
onChange={(e) =>
|
||||
updateItem(
|
||||
index,
|
||||
"unit_price",
|
||||
parseDecimal(e.target.value),
|
||||
)
|
||||
updateItem(index, "unit_price", parseDecimal(e.target.value))
|
||||
}
|
||||
inputProps={{
|
||||
min: 0,
|
||||
@@ -631,15 +610,9 @@ export default function WarehouseReceiptForm() {
|
||||
Přidat položku
|
||||
</Button>
|
||||
</Card>
|
||||
</motion.div>
|
||||
|
||||
{/* File upload — only in edit mode for existing receipts */}
|
||||
{isEdit && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.1 }}
|
||||
>
|
||||
<Card sx={{ mb: 3 }}>
|
||||
<Typography variant="subtitle2" sx={{ mb: 2, fontWeight: 600 }}>
|
||||
Přílohy
|
||||
@@ -696,8 +669,7 @@ export default function WarehouseReceiptForm() {
|
||||
)}
|
||||
</Box>
|
||||
</Card>
|
||||
</motion.div>
|
||||
)}
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,12 +165,7 @@ export default function WarehouseReceipts() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Příjmové doklady"
|
||||
subtitle={subtitle}
|
||||
@@ -185,7 +180,6 @@ export default function WarehouseReceipts() {
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<FilterBar>
|
||||
<Box sx={{ flex: "1 1 220px" }}>
|
||||
@@ -291,6 +285,6 @@ export default function WarehouseReceipts() {
|
||||
onChange={setPage}
|
||||
/>
|
||||
</Card>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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 <Forbidden />;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader title="Skladové reporty" />
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.06 }}
|
||||
>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={(v) => setActiveTab(v as TabId)}
|
||||
tabs={TABS}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<TabPanel value="stock-status" current={activeTab}>
|
||||
<StockStatusTab categoryId={categoryId} setCategoryId={setCategoryId} />
|
||||
@@ -139,7 +127,7 @@ export default function WarehouseReports() {
|
||||
<TabPanel value="below-minimum" current={activeTab}>
|
||||
<BelowMinimumTab />
|
||||
</TabPanel>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,12 +325,7 @@ export default function WarehouseReservations() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25 }}
|
||||
>
|
||||
<PageEnter>
|
||||
<PageHeader
|
||||
title="Rezervace"
|
||||
subtitle={subtitle}
|
||||
@@ -345,7 +340,6 @@ export default function WarehouseReservations() {
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
|
||||
<FilterBar>
|
||||
<Box sx={{ flex: "0 0 160px" }}>
|
||||
@@ -490,6 +484,6 @@ export default function WarehouseReservations() {
|
||||
confirmVariant="danger"
|
||||
loading={cancelling}
|
||||
/>
|
||||
</Box>
|
||||
</PageEnter>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user