- 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>
186 lines
4.7 KiB
TypeScript
186 lines
4.7 KiB
TypeScript
import { queryOptions } from "@tanstack/react-query";
|
|
import apiFetch from "../../utils/api";
|
|
import { jsonQuery } from "../apiAdapter";
|
|
|
|
interface LocationRaw {
|
|
users?: { first_name: string; last_name: string };
|
|
user_name?: string;
|
|
shift_date: string;
|
|
arrival_time?: string | null;
|
|
departure_time?: string | null;
|
|
arrival_lat?: string | number | null;
|
|
arrival_lng?: string | number | null;
|
|
arrival_accuracy?: number | null;
|
|
arrival_address?: string | null;
|
|
departure_lat?: string | number | null;
|
|
departure_lng?: string | number | null;
|
|
departure_accuracy?: number | null;
|
|
departure_address?: string | null;
|
|
}
|
|
|
|
export interface LocationRecord {
|
|
user_name: string;
|
|
shift_date: string;
|
|
arrival_time?: string | null;
|
|
departure_time?: string | null;
|
|
arrival_lat?: string | number | null;
|
|
arrival_lng?: string | number | null;
|
|
arrival_accuracy?: number | null;
|
|
arrival_address?: string | null;
|
|
departure_lat?: string | number | null;
|
|
departure_lng?: string | number | null;
|
|
departure_accuracy?: number | null;
|
|
departure_address?: string | null;
|
|
}
|
|
|
|
export interface ProjectLogEntry {
|
|
id?: number;
|
|
project_id?: number;
|
|
project_name?: string;
|
|
started_at?: string;
|
|
ended_at?: string | null;
|
|
hours?: string | number | null;
|
|
minutes?: string | number | null;
|
|
}
|
|
|
|
export interface AttendanceRecord {
|
|
id: number;
|
|
shift_date: string;
|
|
leave_type?: string;
|
|
leave_hours?: number;
|
|
arrival_time?: string | null;
|
|
departure_time?: string | null;
|
|
break_start?: string | null;
|
|
break_end?: string | null;
|
|
notes?: string;
|
|
project_name?: string;
|
|
project_logs?: ProjectLogEntry[];
|
|
}
|
|
|
|
export interface BalanceEntry {
|
|
name: string;
|
|
vacation_total: number;
|
|
vacation_used: number;
|
|
vacation_remaining: number;
|
|
sick_used: number;
|
|
}
|
|
|
|
export interface UserShort {
|
|
id: number | string;
|
|
name: string;
|
|
}
|
|
|
|
export interface BalancesData {
|
|
users: UserShort[];
|
|
balances: Record<string, BalanceEntry>;
|
|
}
|
|
|
|
export interface FundUserData {
|
|
name: string;
|
|
worked: number;
|
|
covered: number;
|
|
overtime: number;
|
|
missing: number;
|
|
}
|
|
|
|
export interface MonthFundData {
|
|
month_name: string;
|
|
fund: number;
|
|
fund_to_date: number;
|
|
business_days: number;
|
|
users?: Record<string, FundUserData>;
|
|
}
|
|
|
|
export interface FundData {
|
|
months: Record<string, MonthFundData>;
|
|
holidays: unknown[];
|
|
users: UserShort[];
|
|
balances: Record<string, unknown>;
|
|
}
|
|
|
|
export interface ProjectUser {
|
|
user_id: number;
|
|
user_name: string;
|
|
hours: number;
|
|
}
|
|
|
|
export interface ProjectEntry {
|
|
project_id: number | null;
|
|
project_number?: string;
|
|
project_name?: string;
|
|
hours: number;
|
|
users: ProjectUser[];
|
|
}
|
|
|
|
export interface MonthProjectData {
|
|
month_name: string;
|
|
projects: ProjectEntry[];
|
|
}
|
|
|
|
export interface ProjectData {
|
|
months: Record<string, MonthProjectData>;
|
|
}
|
|
|
|
export const attendanceHistoryOptions = (filters: {
|
|
month?: string;
|
|
userId?: number;
|
|
}) =>
|
|
queryOptions({
|
|
queryKey: ["attendance", "history", filters],
|
|
queryFn: () => {
|
|
const params = new URLSearchParams();
|
|
params.set("limit", "1000");
|
|
if (filters.month) params.set("month", filters.month);
|
|
if (filters.userId) params.set("user_id", String(filters.userId));
|
|
return jsonQuery<AttendanceRecord[]>(
|
|
`/api/admin/attendance?${params.toString()}`,
|
|
);
|
|
},
|
|
});
|
|
|
|
export const attendanceBalancesOptions = (year: number) =>
|
|
queryOptions({
|
|
queryKey: ["attendance", "balances", year],
|
|
queryFn: () =>
|
|
jsonQuery<BalancesData>(
|
|
`/api/admin/attendance?action=balances&year=${year}`,
|
|
),
|
|
});
|
|
|
|
export const attendanceWorkFundOptions = (year: number) =>
|
|
queryOptions({
|
|
queryKey: ["attendance", "workfund", year],
|
|
queryFn: () =>
|
|
jsonQuery<FundData>(`/api/admin/attendance?action=workfund&year=${year}`),
|
|
});
|
|
|
|
export const attendanceProjectReportOptions = (year: number) =>
|
|
queryOptions({
|
|
queryKey: ["attendance", "project-report", year],
|
|
queryFn: () =>
|
|
jsonQuery<ProjectData>(
|
|
`/api/admin/attendance?action=project_report&year=${year}`,
|
|
),
|
|
});
|
|
|
|
export const attendanceLocationOptions = (id: string | undefined) =>
|
|
queryOptions({
|
|
queryKey: ["attendance", "location", id],
|
|
queryFn: async (): Promise<LocationRecord> => {
|
|
const response = await apiFetch(
|
|
`/api/admin/attendance?action=location&id=${id}`,
|
|
);
|
|
const result = await response.json();
|
|
if (!result.success) {
|
|
throw new Error(result.error || "Záznam nebyl nalezen");
|
|
}
|
|
const raw = (result.data.record || result.data) as LocationRaw;
|
|
const userName = raw.users
|
|
? `${raw.users.first_name} ${raw.users.last_name}`.trim()
|
|
: raw.user_name || "";
|
|
const { users: _users, ...rest } = raw;
|
|
return { ...rest, user_name: userName };
|
|
},
|
|
enabled: !!id,
|
|
});
|