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:
BOHA
2026-05-17 16:21:34 +02:00
parent 1db5060c42
commit cd6d3daf43
75 changed files with 1748 additions and 1169 deletions

View File

@@ -5,8 +5,12 @@ import { useAuth } from "../context/AuthContext";
import { useParams, useNavigate, Link } from "react-router-dom";
import { motion } from "framer-motion";
import { projectDetailOptions } from "../lib/queries/projects";
import { userListOptions } from "../lib/queries/users";
import {
projectDetailOptions,
type ProjectData,
type ProjectNote,
} from "../lib/queries/projects";
import { userListOptions, type User as ApiUser } from "../lib/queries/users";
import Forbidden from "../components/Forbidden";
import { Skeleton } from "boneyard-js/react";
import ProjectDetailFixture from "../fixtures/ProjectDetailFixture";
@@ -35,36 +39,11 @@ function formatNoteDate(dateStr: string) {
return `${day}. ${month}. ${year} ${hours}:${mins}`;
}
interface Note {
id: number;
content: string;
user_name: string;
created_at: string;
}
interface User {
id: number;
name: string;
}
interface ProjectData {
id: number;
project_number: string;
name: string;
status: string;
start_date: string;
end_date: string;
customer_name: string;
responsible_user_id: string;
notes?: string;
order_id?: number;
order_number?: string;
order_status?: string;
quotation_id?: number;
quotation_number?: string;
has_nas_folder?: boolean;
}
interface ProjectForm {
name: string;
status: string;
@@ -99,22 +78,15 @@ export default function ProjectDetail() {
const queryClient = useQueryClient();
const projectQuery = useQuery(projectDetailOptions(id));
const project = projectQuery.data as
| (ProjectData & { project_notes?: Note[] })
| undefined;
const project = projectQuery.data;
const isPending = projectQuery.isPending;
const notes: Note[] = project?.project_notes || [];
const notes: ProjectNote[] = project?.project_notes || [];
const { data: usersData } = useQuery(userListOptions("projects.view"));
const rawUsers = ((usersData as Record<string, unknown>)?.items ??
usersData ??
[]) as any[];
const users: User[] = Array.isArray(rawUsers)
? rawUsers.map((u: any) => ({
id: u.id,
name: `${u.first_name || ""} ${u.last_name || ""}`.trim() || u.username,
}))
: [];
const users: User[] = (usersData ?? []).map((u: ApiUser) => ({
id: u.id,
name: `${u.first_name || ""} ${u.last_name || ""}`.trim() || u.username,
}));
// Reset form sync when navigating to a different project
const formInitialized = useRef(false);
@@ -174,6 +146,8 @@ export default function ProjectDetail() {
queryClient.invalidateQueries({ queryKey: ["projects"] });
queryClient.invalidateQueries({ queryKey: ["orders"] });
queryClient.invalidateQueries({ queryKey: ["offers"] });
queryClient.invalidateQueries({ queryKey: ["invoices"] });
queryClient.invalidateQueries({ queryKey: ["attendance"] });
} else {
alert.error(result.error || "Nepodařilo se uložit projekt");
}
@@ -197,6 +171,8 @@ export default function ProjectDetail() {
queryClient.invalidateQueries({ queryKey: ["projects"] });
queryClient.invalidateQueries({ queryKey: ["orders"] });
queryClient.invalidateQueries({ queryKey: ["offers"] });
queryClient.invalidateQueries({ queryKey: ["invoices"] });
queryClient.invalidateQueries({ queryKey: ["attendance"] });
navigate("/projects");
setTimeout(() => alert.success("Projekt byl smazán"), 300);
} else {
@@ -223,7 +199,11 @@ export default function ProjectDetail() {
if (result.success) {
setNewNote("");
alert.success("Poznámka byla přidána");
queryClient.invalidateQueries({ queryKey: ["projects", id] });
queryClient.invalidateQueries({ queryKey: ["projects"] });
queryClient.invalidateQueries({ queryKey: ["orders"] });
queryClient.invalidateQueries({ queryKey: ["offers"] });
queryClient.invalidateQueries({ queryKey: ["invoices"] });
queryClient.invalidateQueries({ queryKey: ["attendance"] });
} else {
alert.error(result.error || "Nepodařilo se přidat poznámku");
}
@@ -246,7 +226,11 @@ export default function ProjectDetail() {
const result = await response.json();
if (result.success) {
alert.success("Poznámka byla smazána");
queryClient.invalidateQueries({ queryKey: ["projects", id] });
queryClient.invalidateQueries({ queryKey: ["projects"] });
queryClient.invalidateQueries({ queryKey: ["orders"] });
queryClient.invalidateQueries({ queryKey: ["offers"] });
queryClient.invalidateQueries({ queryKey: ["invoices"] });
queryClient.invalidateQueries({ queryKey: ["attendance"] });
} else {
alert.error(result.error || "Nepodařilo se smazat poznámku");
}