From 5140d23d73af5bbea3a667023cddeca27fd4aa28 Mon Sep 17 00:00:00 2001
From: BOHA
Date: Sat, 6 Jun 2026 11:13:43 +0200
Subject: [PATCH] feat(projects-ui): add project create modal, type badge,
empty-state copy
Co-Authored-By: Claude Sonnet 4.6
---
src/admin/pages/Projects.tsx | 211 ++++++++++++++++++++++++++++++++++-
1 file changed, 209 insertions(+), 2 deletions(-)
diff --git a/src/admin/pages/Projects.tsx b/src/admin/pages/Projects.tsx
index cdbd066..8816546 100644
--- a/src/admin/pages/Projects.tsx
+++ b/src/admin/pages/Projects.tsx
@@ -1,16 +1,24 @@
import { useState } from "react";
+import { useQuery } from "@tanstack/react-query";
import { useAlert } from "../context/AlertContext";
import { useAuth } from "../context/AuthContext";
import { Link } from "react-router-dom";
import Forbidden from "../components/Forbidden";
import { motion } from "framer-motion";
import ConfirmModal from "../components/ConfirmModal";
+import FormModal from "../components/FormModal";
+import FormField from "../components/FormField";
import { formatDate, czechPlural } from "../utils/formatters";
import SortIcon from "../components/SortIcon";
import useTableSort from "../hooks/useTableSort";
import { usePaginatedQuery } from "../hooks/usePaginatedQuery";
-import { projectListOptions } from "../lib/queries/projects";
+import {
+ projectListOptions,
+ projectNextNumberOptions,
+} from "../lib/queries/projects";
+import { offerCustomersOptions } from "../lib/queries/offers";
+import { userListOptions } from "../lib/queries/users";
import { useApiMutation } from "../lib/queries/mutations";
import Pagination from "../components/Pagination";
@@ -73,6 +81,69 @@ export default function Projects() {
},
});
+ const [showCreate, setShowCreate] = useState(false);
+ const [createForm, setCreateForm] = useState({
+ name: "",
+ customer_id: "",
+ responsible_user_id: "",
+ status: "aktivni",
+ start_date: "",
+ end_date: "",
+ notes: "",
+ });
+ const [createErrors, setCreateErrors] = useState>({});
+ const [creating, setCreating] = useState(false);
+
+ const customersQuery = useQuery({
+ ...offerCustomersOptions(),
+ enabled: showCreate,
+ });
+ const usersQuery = useQuery({ ...userListOptions(), enabled: showCreate });
+ const nextNumberQuery = useQuery({
+ ...projectNextNumberOptions(),
+ enabled: showCreate,
+ });
+
+ const createMutation = useApiMutation({
+ url: () => `${API_BASE}/projects`,
+ method: () => "POST",
+ invalidate: ["projects", "orders", "offers", "invoices", "attendance"],
+ onSuccess: () => {
+ setShowCreate(false);
+ alert.success("Projekt byl vytvořen");
+ },
+ });
+
+ const openCreate = () => {
+ setCreateForm({
+ name: "",
+ customer_id: "",
+ responsible_user_id: "",
+ status: "aktivni",
+ start_date: "",
+ end_date: "",
+ notes: "",
+ });
+ setCreateErrors({});
+ setShowCreate(true);
+ };
+
+ const handleCreate = async () => {
+ const errs: Record = {};
+ if (!createForm.name.trim()) errs.name = "Zadejte název projektu";
+ setCreateErrors(errs);
+ if (Object.keys(errs).length > 0) return;
+
+ setCreating(true);
+ try {
+ await createMutation.mutateAsync(createForm);
+ } catch (e) {
+ alert.error(e instanceof Error ? e.message : "Chyba připojení");
+ } finally {
+ setCreating(false);
+ }
+ };
+
const {
items: projects,
pagination,
@@ -126,6 +197,27 @@ export default function Projects() {
)}
+ {hasPermission("projects.create") && (
+
+
+
+
+
+
+ Přidat projekt
+
+
+ )}
- Projekt se vytvoří automaticky při vytvoření objednávky.
+ Projekty vznikají z objednávek nebo je můžete vytvořit ručně
+ tlačítkem „Přidat projekt".
) : (
@@ -233,6 +326,7 @@ export default function Projects() {
order={order}
/>
+ Typ
Objednávka
Akce
@@ -257,6 +351,11 @@ export default function Projects() {
{formatDate(p.start_date)}
{formatDate(p.end_date)}
+
+
+ {p.order_id ? "Z objednávky" : "Samostatný"}
+
+
{p.order_id ? (
+
+ setShowCreate(false)}
+ onSubmit={handleCreate}
+ title="Přidat projekt"
+ submitLabel="Vytvořit"
+ loading={creating}
+ >
+
+
+ {
+ setCreateForm({ ...createForm, name: e.target.value });
+ setCreateErrors((p) => ({ ...p, name: "" }));
+ }}
+ className="admin-form-input"
+ placeholder="Název projektu"
+ aria-invalid={!!createErrors.name}
+ />
+
+
+
+
+
+ setCreateForm({ ...createForm, customer_id: e.target.value })
+ }
+ className="admin-form-input"
+ >
+ — bez zákazníka —
+ {(customersQuery.data ?? []).map((c) => (
+
+ {c.name}
+
+ ))}
+
+
+
+
+
+ setCreateForm({
+ ...createForm,
+ responsible_user_id: e.target.value,
+ })
+ }
+ className="admin-form-input"
+ >
+ — nepřiřazeno —
+ {(usersQuery.data ?? []).map((u) => (
+
+ {u.first_name} {u.last_name}
+
+ ))}
+
+
+
+
+
+
+
+ setCreateForm({ ...createForm, start_date: e.target.value })
+ }
+ className="admin-form-input"
+ />
+
+
+
+ setCreateForm({ ...createForm, end_date: e.target.value })
+ }
+ className="admin-form-input"
+ />
+
+
+
+
+
+
+
+ Číslo projektu:{" "}
+
+ {nextNumberQuery.data?.number ??
+ nextNumberQuery.data?.next_number ??
+ "…"}
+ {" "}
+ (přiděleno automaticky)
+
+
+
);
}