diff --git a/src/admin/lib/queries/projects.ts b/src/admin/lib/queries/projects.ts index f539f34..b549155 100644 --- a/src/admin/lib/queries/projects.ts +++ b/src/admin/lib/queries/projects.ts @@ -40,6 +40,7 @@ export const projectListOptions = (filters: { order?: string; page?: number; perPage?: number; + status?: string; }) => queryOptions({ queryKey: ["projects", "list", filters], @@ -50,6 +51,7 @@ export const projectListOptions = (filters: { if (filters.order) params.set("order", filters.order); if (filters.page) params.set("page", String(filters.page)); if (filters.perPage) params.set("per_page", String(filters.perPage)); + if (filters.status) params.set("status", filters.status); const qs = params.toString(); return paginatedJsonQuery( `/api/admin/projects${qs ? `?${qs}` : ""}`, diff --git a/src/admin/pages/Projects.tsx b/src/admin/pages/Projects.tsx index c7f9ce7..bcfaf4b 100644 --- a/src/admin/pages/Projects.tsx +++ b/src/admin/pages/Projects.tsx @@ -34,6 +34,8 @@ import { CheckboxField, LoadingState, PageEnter, + Tabs, + type TabDef, type DataColumn, } from "../ui"; @@ -54,6 +56,12 @@ const STATUS_COLORS: Record< zruseny: "default", }; +// Status filter tabs (mirrors the offers page): "" = all. +const STATUS_TABS: TabDef[] = [ + { value: "", label: "Všechny" }, + ...Object.entries(STATUS_LABELS).map(([value, label]) => ({ value, label })), +]; + interface Project { id: number; project_number: string; @@ -120,6 +128,7 @@ export default function Projects() { const [search, setSearch] = useState(""); const debouncedSearch = useDebounce(search, 300); const [page, setPage] = useState(1); + const [statusFilter, setStatusFilter] = useState(""); const [deleteTarget, setDeleteTarget] = useState(null); const [deleteFiles, setDeleteFiles] = useState(false); @@ -230,7 +239,13 @@ export default function Projects() { isPending, isFetching, } = usePaginatedQuery( - projectListOptions({ search: debouncedSearch, sort, order, page }), + projectListOptions({ + search: debouncedSearch, + sort, + order, + page, + status: statusFilter || undefined, + }), ); if (!hasPermission("projects.view")) return ; @@ -372,6 +387,31 @@ export default function Projects() { }, ]; + // Per-status row tints (mirrors the offers list): LOW-ALPHA washes via the + // palette channel vars in the badge's color — never solid `.light` fills + // (invisible white text in dark mode). Cancelled rows are faded/muted like + // invalidated offers. + const rowSx = (p: Project) => { + if (p.status === "dokonceny") { + return { + backgroundColor: "rgba(var(--mui-palette-info-mainChannel) / 0.12)", + "&:hover": { + backgroundColor: "rgba(var(--mui-palette-info-mainChannel) / 0.18)", + }, + }; + } + if (p.status === "zruseny") { + return { + opacity: 0.6, + "& td": { color: "var(--mui-palette-text-secondary)" }, + }; + } + return {}; + }; + + // Search/status filter active → empty means "nothing matches" (no CTA). + const isFiltered = !!debouncedSearch || !!statusFilter; + return ( + + { + setStatusFilter(v); + setPage(1); + }} + tabs={STATUS_TABS} + /> + + p.id} + rowSx={rowSx} sortBy={sort} sortDir={order} onSort={handleSort} empty={ - - - Zatím nejsou žádné projekty. - - - Projekty vznikají z objednávek nebo je můžete vytvořit ručně - tlačítkem „Přidat projekt". - - + isFiltered ? ( + + + Žádné projekty neodpovídají filtru. + + + Zkuste změnit filtr nebo hledaný výraz. + + + ) : ( + + + Zatím nejsou žádné projekty. + + + Projekty vznikají z objednávek nebo je můžete vytvořit ručně + tlačítkem „Přidat projekt". + + + ) } />