feat(orders): PDF footer = issuer name+email, 'Zobrazit objednávku' button, per-currency list totals
- Issued-order PDF: drop the Vystavil/Schválil signature row; footer now shows 'Vystavil: <name>' + 'E-mail: <email>' from the logged-in user (authData). - IssuedOrderDetail PDF button 'Export PDF' -> 'Zobrazit objednávku' (already opens inline like invoices' 'Zobrazit fakturu'). - New /orders/stats + /issued-orders/stats endpoints sum order total (incl VAT) per currency across the active filter; rendered as 'Celkem: …' beneath both the received and issued lists. formatMultiCurrency lifted to a shared util. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import Forbidden from "../components/Forbidden";
|
||||
import apiFetch from "../utils/api";
|
||||
import {
|
||||
formatCurrency,
|
||||
formatMultiCurrency,
|
||||
formatDate,
|
||||
czechPlural,
|
||||
todayLocalStr,
|
||||
@@ -70,11 +71,6 @@ const MONTH_NAMES = [
|
||||
"prosinec",
|
||||
];
|
||||
|
||||
function formatMultiCurrency(amounts: CurrencyAmount[]): string {
|
||||
if (!Array.isArray(amounts) || amounts.length === 0) return "0 Kč";
|
||||
return amounts.map((a) => formatCurrency(a.amount, a.currency)).join(" · ");
|
||||
}
|
||||
|
||||
function formatCzkWithDetail(
|
||||
amounts: CurrencyAmount[],
|
||||
totalCzk: number | null | undefined,
|
||||
|
||||
@@ -956,7 +956,7 @@ export default function IssuedOrderDetail() {
|
||||
)
|
||||
}
|
||||
>
|
||||
Export PDF
|
||||
Zobrazit objednávku
|
||||
</Button>
|
||||
)}
|
||||
{/* ── Create mode: two-button save ── */}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useNavigate, Link as RouterLink } from "react-router-dom";
|
||||
import Box from "@mui/material/Box";
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import { useAlert } from "../context/AlertContext";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import Forbidden from "../components/Forbidden";
|
||||
import { formatCurrency, formatDate } from "../utils/formatters";
|
||||
import {
|
||||
formatCurrency,
|
||||
formatDate,
|
||||
formatMultiCurrency,
|
||||
} from "../utils/formatters";
|
||||
import useTableSort from "../hooks/useTableSort";
|
||||
import useDebounce from "../hooks/useDebounce";
|
||||
import { usePaginatedQuery } from "../hooks/usePaginatedQuery";
|
||||
@@ -13,6 +18,7 @@ import { useApiMutation } from "../lib/queries/mutations";
|
||||
import apiFetch from "../utils/api";
|
||||
import {
|
||||
issuedOrderListOptions,
|
||||
issuedOrderStatsOptions,
|
||||
type IssuedOrder,
|
||||
} from "../lib/queries/issued-orders";
|
||||
import {
|
||||
@@ -142,6 +148,17 @@ export default function IssuedOrders({ month, year }: IssuedOrdersProps) {
|
||||
}),
|
||||
);
|
||||
|
||||
// Per-currency total over the WHOLE filtered set (not one page). Uses the
|
||||
// SAME filters as the list query so the summary matches what's shown.
|
||||
const statsQuery = useQuery(
|
||||
issuedOrderStatsOptions({
|
||||
search: debouncedSearch,
|
||||
status: status || undefined,
|
||||
month,
|
||||
year,
|
||||
}),
|
||||
);
|
||||
|
||||
// Mark first load done in an effect (not during render) to avoid a
|
||||
// render-phase ref mutation.
|
||||
useEffect(() => {
|
||||
@@ -363,6 +380,27 @@ export default function IssuedOrders({ month, year }: IssuedOrdersProps) {
|
||||
)
|
||||
}
|
||||
/>
|
||||
{(statsQuery.data?.length ?? 0) > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
mt: 1.5,
|
||||
px: 1,
|
||||
gap: 1,
|
||||
color: "text.secondary",
|
||||
fontSize: "0.9rem",
|
||||
}}
|
||||
>
|
||||
<span>Celkem:</span>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{ fontWeight: 700, color: "text.primary" }}
|
||||
>
|
||||
{formatMultiCurrency(statsQuery.data ?? [])}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<Pagination
|
||||
page={page}
|
||||
pageCount={pagination?.total_pages ?? 1}
|
||||
|
||||
@@ -7,12 +7,17 @@ import { useAlert } from "../context/AlertContext";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import Forbidden from "../components/Forbidden";
|
||||
import apiFetch from "../utils/api";
|
||||
import { formatCurrency, formatDate } from "../utils/formatters";
|
||||
import {
|
||||
formatCurrency,
|
||||
formatDate,
|
||||
formatMultiCurrency,
|
||||
} from "../utils/formatters";
|
||||
import useTableSort from "../hooks/useTableSort";
|
||||
import useDebounce from "../hooks/useDebounce";
|
||||
import { usePaginatedQuery } from "../hooks/usePaginatedQuery";
|
||||
import {
|
||||
orderListOptions,
|
||||
orderStatsOptions,
|
||||
orderNextNumberOptions,
|
||||
} from "../lib/queries/orders";
|
||||
import { offerCustomersOptions } from "../lib/queries/offers";
|
||||
@@ -317,6 +322,17 @@ export default function OrdersReceived({
|
||||
}),
|
||||
);
|
||||
|
||||
// Per-currency total over the WHOLE filtered set (not one page). Uses the
|
||||
// SAME filters as the list query so the summary matches what's shown.
|
||||
const statsQuery = useQuery(
|
||||
orderStatsOptions({
|
||||
search: debouncedSearch,
|
||||
status: status || undefined,
|
||||
month,
|
||||
year,
|
||||
}),
|
||||
);
|
||||
|
||||
// Mark first load done in an effect (not during render) to avoid a
|
||||
// render-phase ref mutation.
|
||||
useEffect(() => {
|
||||
@@ -543,6 +559,27 @@ export default function OrdersReceived({
|
||||
)
|
||||
}
|
||||
/>
|
||||
{(statsQuery.data?.length ?? 0) > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
mt: 1.5,
|
||||
px: 1,
|
||||
gap: 1,
|
||||
color: "text.secondary",
|
||||
fontSize: "0.9rem",
|
||||
}}
|
||||
>
|
||||
<span>Celkem:</span>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{ fontWeight: 700, color: "text.primary" }}
|
||||
>
|
||||
{formatMultiCurrency(statsQuery.data ?? [])}
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
<Pagination
|
||||
page={page}
|
||||
pageCount={pagination?.total_pages ?? 1}
|
||||
|
||||
Reference in New Issue
Block a user