One 0.2s enter-only fade (opacity + 6px rise) in the kit TabPanel covers every tab switch (M3 clean-fade pattern; CSS animation so the global reduced-motion kill switch applies). Orders and Invoices view switchers now render through TabPanel (also fixes their dangling aria-controls). Removed the inconsistent bespoke framer entrances inside tab content (CompanySettings cascade, ReceivedInvoices card). Filter-style status tabs (Projects/Offers/Invoices) deliberately keep the refetch dim only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
196 lines
4.7 KiB
TypeScript
196 lines
4.7 KiB
TypeScript
import { useState, lazy, Suspense } from "react";
|
|
import { useNavigate, useSearchParams } from "react-router-dom";
|
|
import Box from "@mui/material/Box";
|
|
import Typography from "@mui/material/Typography";
|
|
import IconButton from "@mui/material/IconButton";
|
|
import { useAuth } from "../context/AuthContext";
|
|
import Forbidden from "../components/Forbidden";
|
|
import {
|
|
Button,
|
|
Tabs,
|
|
TabPanel,
|
|
PageHeader,
|
|
PageEnter,
|
|
LoadingState,
|
|
} from "../ui";
|
|
import OrdersReceived from "./ReceivedOrders";
|
|
|
|
const IssuedOrders = lazy(() => import("./IssuedOrders"));
|
|
|
|
const MONTH_NAMES = [
|
|
"leden",
|
|
"únor",
|
|
"březen",
|
|
"duben",
|
|
"květen",
|
|
"červen",
|
|
"červenec",
|
|
"srpen",
|
|
"září",
|
|
"říjen",
|
|
"listopad",
|
|
"prosinec",
|
|
];
|
|
|
|
const PlusIcon = (
|
|
<svg
|
|
width="20"
|
|
height="20"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
>
|
|
<line x1="12" y1="5" x2="12" y2="19" />
|
|
<line x1="5" y1="12" x2="19" y2="12" />
|
|
</svg>
|
|
);
|
|
const ChevronLeftIcon = (
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2.5"
|
|
>
|
|
<polyline points="15 18 9 12 15 6" />
|
|
</svg>
|
|
);
|
|
const ChevronRightIcon = (
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2.5"
|
|
>
|
|
<polyline points="9 18 15 12 9 6" />
|
|
</svg>
|
|
);
|
|
|
|
export default function Orders() {
|
|
const { hasPermission } = useAuth();
|
|
const navigate = useNavigate();
|
|
|
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
const activeTab =
|
|
searchParams.get("tab") === "prijate" ? "prijate" : "vydane";
|
|
const setActiveTab = (tab: string) =>
|
|
setSearchParams({ tab }, { replace: true });
|
|
|
|
const [createOpen, setCreateOpen] = useState(false);
|
|
|
|
const now = new Date();
|
|
const [statsMonth, setStatsMonth] = useState(now.getMonth() + 1);
|
|
const [statsYear, setStatsYear] = useState(now.getFullYear());
|
|
|
|
const isCurrentMonth =
|
|
statsMonth === now.getMonth() + 1 && statsYear === now.getFullYear();
|
|
const monthLabel = `${MONTH_NAMES[statsMonth - 1]} ${statsYear}`;
|
|
|
|
const prevMonth = () => {
|
|
if (statsMonth === 1) {
|
|
setStatsMonth(12);
|
|
setStatsYear((y) => y - 1);
|
|
} else {
|
|
setStatsMonth((m) => m - 1);
|
|
}
|
|
};
|
|
|
|
const nextMonth = () => {
|
|
if (isCurrentMonth) return;
|
|
if (statsMonth === 12) {
|
|
setStatsMonth(1);
|
|
setStatsYear((y) => y + 1);
|
|
} else {
|
|
setStatsMonth((m) => m + 1);
|
|
}
|
|
};
|
|
|
|
if (!hasPermission("orders.view")) return <Forbidden />;
|
|
|
|
return (
|
|
<PageEnter>
|
|
<PageHeader
|
|
title="Objednávky"
|
|
actions={
|
|
hasPermission("orders.create") ? (
|
|
activeTab === "vydane" ? (
|
|
<Button
|
|
startIcon={PlusIcon}
|
|
onClick={() => navigate("/orders/issued/new")}
|
|
>
|
|
Vytvořit objednávku
|
|
</Button>
|
|
) : (
|
|
<Button startIcon={PlusIcon} onClick={() => setCreateOpen(true)}>
|
|
Vytvořit objednávku
|
|
</Button>
|
|
)
|
|
) : undefined
|
|
}
|
|
/>
|
|
|
|
<Box>
|
|
<Box
|
|
sx={{
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
gap: 1,
|
|
mb: 2,
|
|
}}
|
|
>
|
|
<IconButton
|
|
size="small"
|
|
onClick={prevMonth}
|
|
aria-label="Předchozí měsíc"
|
|
>
|
|
{ChevronLeftIcon}
|
|
</IconButton>
|
|
<Typography
|
|
sx={{ minWidth: 140, textAlign: "center", fontWeight: 600 }}
|
|
>
|
|
{monthLabel}
|
|
</Typography>
|
|
<IconButton
|
|
size="small"
|
|
onClick={nextMonth}
|
|
disabled={isCurrentMonth}
|
|
aria-label="Následující měsíc"
|
|
>
|
|
{ChevronRightIcon}
|
|
</IconButton>
|
|
</Box>
|
|
|
|
<Box sx={{ display: "flex", justifyContent: "center", mb: 2.5 }}>
|
|
<Tabs
|
|
value={activeTab}
|
|
onChange={(v) => setActiveTab(v)}
|
|
tabs={[
|
|
{ value: "vydane", label: "Vydané" },
|
|
{ value: "prijate", label: "Přijaté" },
|
|
]}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
|
|
<TabPanel value="vydane" current={activeTab} sx={{ pt: 0 }}>
|
|
<Suspense fallback={<LoadingState />}>
|
|
<IssuedOrders month={statsMonth} year={statsYear} />
|
|
</Suspense>
|
|
</TabPanel>
|
|
<TabPanel value="prijate" current={activeTab} sx={{ pt: 0 }}>
|
|
<OrdersReceived
|
|
month={statsMonth}
|
|
year={statsYear}
|
|
createOpen={createOpen}
|
|
setCreateOpen={setCreateOpen}
|
|
/>
|
|
</TabPanel>
|
|
</PageEnter>
|
|
);
|
|
}
|