From 3d3df6db856d81ce39c4e18fa2113435a56515b4 Mon Sep 17 00:00:00 2001 From: BOHA Date: Tue, 9 Jun 2026 13:33:36 +0200 Subject: [PATCH] =?UTF-8?q?feat(orders):=20invoices-style=20landing=20?= =?UTF-8?q?=E2=80=94=20month=20selector=20+=20Vydane-first=20tabs,=20conte?= =?UTF-8?q?nt-only=20tab=20bodies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- src/admin/pages/IssuedOrders.tsx | 54 ++------- src/admin/pages/Orders.tsx | 185 ++++++++++++++++++++++++++--- src/admin/pages/OrdersReceived.tsx | 80 +++++-------- 3 files changed, 210 insertions(+), 109 deletions(-) diff --git a/src/admin/pages/IssuedOrders.tsx b/src/admin/pages/IssuedOrders.tsx index 6e5a385..cd9d701 100644 --- a/src/admin/pages/IssuedOrders.tsx +++ b/src/admin/pages/IssuedOrders.tsx @@ -5,7 +5,7 @@ import IconButton from "@mui/material/IconButton"; import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; -import { formatCurrency, formatDate, czechPlural } from "../utils/formatters"; +import { formatCurrency, formatDate } from "../utils/formatters"; import useTableSort from "../hooks/useTableSort"; import useDebounce from "../hooks/useDebounce"; import { usePaginatedQuery } from "../hooks/usePaginatedQuery"; @@ -16,7 +16,6 @@ import { type IssuedOrder, } from "../lib/queries/issued-orders"; import { - Button, Card, DataTable, Pagination, @@ -24,8 +23,6 @@ import { TextField, Select, StatusChip, - PageHeader, - PageEnter, FilterBar, EmptyState, LoadingState, @@ -58,19 +55,6 @@ const STATUS_OPTIONS = [ ...Object.entries(STATUS_LABELS).map(([value, label]) => ({ value, label })), ]; -const PlusIcon = ( - - - - -); const ViewIcon = ( ); -export default function IssuedOrders() { +interface IssuedOrdersProps { + month: number; + year: number; +} + +export default function IssuedOrders({ month, year }: IssuedOrdersProps) { const alert = useAlert(); const { hasPermission } = useAuth(); const navigate = useNavigate(); @@ -157,6 +146,8 @@ export default function IssuedOrders() { page, perPage: 20, status: status || undefined, + month, + year, }), ); @@ -199,14 +190,6 @@ export default function IssuedOrders() { return ; } - const total = pagination?.total ?? orders.length; - const subtitle = `${total} ${czechPlural( - total, - "objednávka", - "objednávky", - "objednávek", - )}`; - const columns: DataColumn[] = [ { key: "po_number", @@ -305,22 +288,7 @@ export default function IssuedOrders() { ]; return ( - - navigate("/orders/issued/new")} - > - Vytvořit objednávku vydanou - - ) : undefined - } - /> - + <> - + ); } diff --git a/src/admin/pages/Orders.tsx b/src/admin/pages/Orders.tsx index ce4be5b..c227157 100644 --- a/src/admin/pages/Orders.tsx +++ b/src/admin/pages/Orders.tsx @@ -1,36 +1,187 @@ -import { lazy, Suspense } from "react"; +import { useState, lazy, Suspense } from "react"; +import { useNavigate, useSearchParams } from "react-router-dom"; import Box from "@mui/material/Box"; -import { useSearchParams } from "react-router-dom"; -import { Tabs, LoadingState } from "../ui"; +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, PageHeader, PageEnter, LoadingState } from "../ui"; import OrdersReceived from "./OrdersReceived"; const IssuedOrders = lazy(() => import("./IssuedOrders")); +const MONTH_NAMES = [ + "leden", + "únor", + "březen", + "duben", + "květen", + "červen", + "červenec", + "srpen", + "září", + "říjen", + "listopad", + "prosinec", +]; + +const PlusIcon = ( + + + + +); +const ChevronLeftIcon = ( + + + +); +const ChevronRightIcon = ( + + + +); + export default function Orders() { + const { hasPermission } = useAuth(); + const navigate = useNavigate(); + const [searchParams, setSearchParams] = useSearchParams(); - const activeTab = searchParams.get("tab") === "vydane" ? "vydane" : "prijate"; + 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 ; + return ( - <> - - setActiveTab(v)} - tabs={[ - { value: "prijate", label: "Přijaté" }, - { value: "vydane", label: "Vydané" }, - ]} - /> + + navigate("/orders/issued/new")} + > + Vytvořit objednávku vydanou + + ) : ( + + ) + ) : undefined + } + /> + + + + + {ChevronLeftIcon} + + + {monthLabel} + + + {ChevronRightIcon} + + + + + setActiveTab(v)} + tabs={[ + { value: "vydane", label: "Vydané" }, + { value: "prijate", label: "Přijaté" }, + ]} + /> + + {activeTab === "vydane" ? ( }> - + ) : ( - + )} - + ); } diff --git a/src/admin/pages/OrdersReceived.tsx b/src/admin/pages/OrdersReceived.tsx index fd7668e..1177eaf 100644 --- a/src/admin/pages/OrdersReceived.tsx +++ b/src/admin/pages/OrdersReceived.tsx @@ -7,7 +7,7 @@ import { useAlert } from "../context/AlertContext"; import { useAuth } from "../context/AuthContext"; import Forbidden from "../components/Forbidden"; import apiFetch from "../utils/api"; -import { formatCurrency, formatDate, czechPlural } from "../utils/formatters"; +import { formatCurrency, formatDate } from "../utils/formatters"; import useTableSort from "../hooks/useTableSort"; import useDebounce from "../hooks/useDebounce"; import { usePaginatedQuery } from "../hooks/usePaginatedQuery"; @@ -18,7 +18,6 @@ import { import { offerCustomersOptions } from "../lib/queries/offers"; import { useApiMutation } from "../lib/queries/mutations"; import { - Button, Card, DataTable, Pagination, @@ -30,8 +29,6 @@ import { StatusChip, CheckboxField, FileUpload, - PageHeader, - PageEnter, FilterBar, EmptyState, LoadingState, @@ -70,19 +67,6 @@ interface Order { invoice_id?: number; } -const PlusIcon = ( - - - - -); const ViewIcon = ( ); -export default function OrdersReceived() { +interface OrdersReceivedProps { + month: number; + year: number; + createOpen: boolean; + setCreateOpen: (open: boolean) => void; +} + +export default function OrdersReceived({ + month, + year, + createOpen, + setCreateOpen, +}: OrdersReceivedProps) { const alert = useAlert(); const { hasPermission } = useAuth(); @@ -180,7 +176,6 @@ export default function OrdersReceived() { }, }); - const [showCreate, setShowCreate] = useState(false); const [createForm, setCreateForm] = useState({ customer_id: "", customer_order_number: "", @@ -201,14 +196,15 @@ export default function OrdersReceived() { const customersQuery = useQuery({ ...offerCustomersOptions(), - enabled: showCreate, + enabled: createOpen, }); const nextNumberQuery = useQuery({ ...orderNextNumberOptions(), - enabled: showCreate, + enabled: createOpen, }); - const openCreate = () => { + const closeCreate = () => { + setCreateOpen(false); setCreateForm({ customer_id: "", customer_order_number: "", @@ -223,7 +219,6 @@ export default function OrdersReceived() { quantity: "1", }); setOrderAttachment(null); - setShowCreate(true); }; const handleCreate = async () => { @@ -283,7 +278,7 @@ export default function OrdersReceived() { const response = await apiFetch(`${API_BASE}/orders`, fetchOptions); const result = await response.json(); if (result.success) { - setShowCreate(false); + closeCreate(); alert.success(result.message || "Objednávka byla vytvořena"); await queryClient.invalidateQueries({ queryKey: ["orders"] }); await queryClient.invalidateQueries({ queryKey: ["projects"] }); @@ -306,7 +301,14 @@ export default function OrdersReceived() { isPending, isFetching, } = usePaginatedQuery( - orderListOptions({ search: debouncedSearch, sort, order, page }), + orderListOptions({ + search: debouncedSearch, + sort, + order, + page, + month, + year, + }), ); if (!hasPermission("orders.view")) return ; @@ -327,14 +329,6 @@ export default function OrdersReceived() { return ; } - const total = pagination?.total ?? orders.length; - const subtitle = `${total} ${czechPlural( - total, - "objednávka", - "objednávky", - "objednávek", - )}`; - const columns: DataColumn[] = [ { key: "order_number", @@ -463,19 +457,7 @@ export default function OrdersReceived() { ]; return ( - - - Vytvořit objednávku - - ) : undefined - } - /> - + <> setShowCreate(false)} + isOpen={createOpen} + onClose={closeCreate} onSubmit={handleCreate} title="Vytvořit objednávku bez nabídky" subtitle={`Číslo objednávky: ${ @@ -699,6 +681,6 @@ export default function OrdersReceived() { onChange={(v) => setCreateForm({ ...createForm, create_project: v })} /> - + ); }