v1.6.5: fix attendance unique constraint, schema sync, seed script
- Change attendance idx_attendance_user_date from unique to index (allow multiple shifts per day) - Reset migrations to single baseline init migration - Add seed script with admin user (admin/admin) - Update CLAUDE.md with migration workflow documentation - Various frontend fixes (queries, pages, hooks) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,14 @@
|
||||
import { useState, useEffect, useMemo, useRef, type ReactNode } from "react";
|
||||
import DOMPurify from "dompurify";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { orderDetailOptions } from "../lib/queries/orders";
|
||||
import {
|
||||
orderDetailOptions,
|
||||
type OrderData,
|
||||
type OrderItem,
|
||||
type OrderSection,
|
||||
type OrderInvoice,
|
||||
type OrderProject,
|
||||
} from "../lib/queries/orders";
|
||||
import { useAlert } from "../context/AlertContext";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
import { useParams, useNavigate, Link } from "react-router-dom";
|
||||
@@ -42,60 +49,6 @@ const TRANSITION_CLASSES: Record<string, string> = {
|
||||
dokoncena: "admin-btn admin-btn-primary",
|
||||
};
|
||||
|
||||
interface OrderItem {
|
||||
id?: number;
|
||||
description: string;
|
||||
item_description?: string;
|
||||
quantity: number;
|
||||
unit: string;
|
||||
unit_price: number;
|
||||
is_included_in_total: number | boolean;
|
||||
}
|
||||
|
||||
interface OrderSection {
|
||||
id?: number;
|
||||
title: string;
|
||||
title_cz?: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
interface Invoice {
|
||||
id: number;
|
||||
invoice_number: string;
|
||||
}
|
||||
|
||||
interface Project {
|
||||
id: number;
|
||||
project_number: string;
|
||||
name: string;
|
||||
has_nas_folder?: boolean;
|
||||
}
|
||||
|
||||
interface OrderData {
|
||||
id: number;
|
||||
order_number: string;
|
||||
quotation_id: number;
|
||||
quotation_number: string;
|
||||
project_code?: string;
|
||||
customer_name: string;
|
||||
customer_order_number: string;
|
||||
currency: string;
|
||||
created_at: string;
|
||||
status: string;
|
||||
notes: string;
|
||||
attachment_name?: string;
|
||||
apply_vat: number | boolean;
|
||||
vat_rate: number;
|
||||
language?: string;
|
||||
items: OrderItem[];
|
||||
sections: OrderSection[];
|
||||
scope_title?: string;
|
||||
scope_description?: string;
|
||||
valid_transitions?: string[];
|
||||
invoice?: Invoice;
|
||||
project?: Project;
|
||||
}
|
||||
|
||||
export default function OrderDetail() {
|
||||
const { id } = useParams();
|
||||
const alert = useAlert();
|
||||
@@ -104,7 +57,7 @@ export default function OrderDetail() {
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const orderQuery = useQuery(orderDetailOptions(id));
|
||||
const order = orderQuery.data as OrderData | undefined;
|
||||
const order = orderQuery.data;
|
||||
const loading = orderQuery.isPending;
|
||||
|
||||
const [notes, setNotes] = useState("");
|
||||
@@ -132,7 +85,7 @@ export default function OrderDetail() {
|
||||
// Sync order data to local form state on first load
|
||||
useEffect(() => {
|
||||
if (orderQuery.data && !formInitializedRef.current) {
|
||||
const orderData = orderQuery.data as OrderData;
|
||||
const orderData = orderQuery.data!;
|
||||
setNotes(orderData.notes || "");
|
||||
initialNotesRef.current = orderData.notes || "";
|
||||
formInitializedRef.current = true;
|
||||
@@ -199,9 +152,10 @@ export default function OrderDetail() {
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert.success(result.message || "Stav byl změněn");
|
||||
queryClient.invalidateQueries({ queryKey: ["orders", id] });
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["offers"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["projects"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["invoices"] });
|
||||
} else {
|
||||
alert.error(result.error || "Nepodařilo se změnit stav");
|
||||
}
|
||||
@@ -224,9 +178,10 @@ export default function OrderDetail() {
|
||||
if (result.success) {
|
||||
alert.success("Poznámky byly uloženy");
|
||||
initialNotesRef.current = notes;
|
||||
queryClient.invalidateQueries({ queryKey: ["orders", id] });
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["offers"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["projects"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["invoices"] });
|
||||
} else {
|
||||
alert.error(result.error || "Nepodařilo se uložit poznámky");
|
||||
}
|
||||
@@ -315,9 +270,10 @@ export default function OrderDetail() {
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert.success(result.message || "Objednávka byla smazána");
|
||||
queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["offers"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["projects"] });
|
||||
await queryClient.invalidateQueries({ queryKey: ["orders"] });
|
||||
await queryClient.invalidateQueries({ queryKey: ["offers"] });
|
||||
await queryClient.invalidateQueries({ queryKey: ["projects"] });
|
||||
await queryClient.invalidateQueries({ queryKey: ["invoices"] });
|
||||
navigate("/orders");
|
||||
} else {
|
||||
alert.error(result.error || "Nepodařilo se smazat objednávku");
|
||||
@@ -416,24 +372,26 @@ export default function OrderDetail() {
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
<button
|
||||
onClick={() => setShowConfirmationModal(true)}
|
||||
className="admin-btn admin-btn-secondary"
|
||||
disabled={confirmationLoading}
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
{hasPermission("orders.export") && (
|
||||
<button
|
||||
onClick={() => setShowConfirmationModal(true)}
|
||||
className="admin-btn admin-btn-secondary"
|
||||
disabled={confirmationLoading}
|
||||
>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
</svg>
|
||||
Potvrzení objednávky
|
||||
</button>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
>
|
||||
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
||||
<polyline points="14 2 14 8 20 8" />
|
||||
</svg>
|
||||
Potvrzení objednávky
|
||||
</button>
|
||||
)}
|
||||
{hasPermission("orders.edit") &&
|
||||
order.valid_transitions?.filter((s) => s !== "stornovana")
|
||||
.length! > 0 &&
|
||||
|
||||
Reference in New Issue
Block a user