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:
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user