diff --git a/src/admin/pages/Attendance.tsx b/src/admin/pages/Attendance.tsx
index 93c0102..6497629 100644
--- a/src/admin/pages/Attendance.tsx
+++ b/src/admin/pages/Attendance.tsx
@@ -1,7 +1,6 @@
import { useState, useEffect, useRef } from "react";
import { useQuery } from "@tanstack/react-query";
import { Link as RouterLink } from "react-router-dom";
-import { motion } from "framer-motion";
import { parseISO, isValid } from "date-fns";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
@@ -30,6 +29,7 @@ import {
StatusChip,
TextField,
ProgressBar,
+ PageEnter,
EmptyState,
LoadingState,
type DataColumn,
@@ -614,24 +614,18 @@ export default function Attendance() {
];
return (
-
-
-
- Docházka
-
- {new Date().toLocaleDateString("cs-CZ", {
- weekday: "long",
- day: "numeric",
- month: "long",
- year: "numeric",
- })}
-
-
-
+
+
+ Docházka
+
+ {new Date().toLocaleDateString("cs-CZ", {
+ weekday: "long",
+ day: "numeric",
+ month: "long",
+ year: "numeric",
+ })}
+
+
{/* Left Column - Clock In/Out */}
-
+ <>
)}
-
+ >
{/* Right Column - Stats & Quick Links */}
-
-
- {/* Leave Balance Card */}
+
+ {/* Leave Balance Card */}
+
+ {vacationDaysRemaining}{" "}
+
+ {czechPlural(vacationDaysRemaining, "den", "dny", "dnů")}
+ {vacationHoursRemaining > 0 && ` ${vacationHoursRemaining}h`}
+
+ >
+ }
+ footer={
+
+
+ Celkem: {leaveBalance.vacation_total}h
+ Čerpáno: {leaveBalance.vacation_used}h
+
+ 0
+ ? (leaveBalance.vacation_remaining /
+ leaveBalance.vacation_total) *
+ 100
+ : 0
+ }
+ color="success"
+ height={6}
+ />
+
+ }
+ />
+
+ {/* Monthly Fund Card */}
+ {data.monthly_fund && (
- {vacationDaysRemaining}{" "}
-
- {czechPlural(vacationDaysRemaining, "den", "dny", "dnů")}
- {vacationHoursRemaining > 0 &&
- ` ${vacationHoursRemaining}h`}
-
- >
- }
+ icon={CalendarIcon}
+ color="info"
+ label={data.monthly_fund.month_name}
+ value={`${data.monthly_fund.worked}h / ${data.monthly_fund.fund}h`}
footer={
- Celkem: {leaveBalance.vacation_total}h
- Čerpáno: {leaveBalance.vacation_used}h
+ Odpracováno: {data.monthly_fund.worked}h
+ {data.monthly_fund.overtime > 0 ? (
+
+ Přesčas: +{data.monthly_fund.overtime}h
+
+ ) : (
+ Zbývá: {data.monthly_fund.remaining}h
+ )}
0
- ? (leaveBalance.vacation_remaining /
- leaveBalance.vacation_total) *
- 100
+ data.monthly_fund.fund > 0
+ ? Math.min(
+ 100,
+ (data.monthly_fund.covered /
+ data.monthly_fund.fund) *
+ 100,
+ )
: 0
}
- color="success"
+ color={getFundBarColor(data.monthly_fund)}
height={6}
/>
+ {data.monthly_fund.leave_hours > 0 && (
+
+ {"Pokryto: "}
+ {data.monthly_fund.covered}h (práce{" "}
+ {data.monthly_fund.worked}h
+ {data.monthly_fund.vacation_hours > 0 &&
+ ` + dovolená ${data.monthly_fund.vacation_hours}h`}
+ {data.monthly_fund.sick_hours > 0 &&
+ ` + nemoc ${data.monthly_fund.sick_hours}h`}
+ {data.monthly_fund.unpaid_hours > 0 &&
+ ` + neplacené ${data.monthly_fund.unpaid_hours}h`}
+ )
+
+ )}
}
/>
+ )}
- {/* Monthly Fund Card */}
- {data.monthly_fund && (
-
-
- Odpracováno: {data.monthly_fund.worked}h
- {data.monthly_fund.overtime > 0 ? (
-
- Přesčas: +{data.monthly_fund.overtime}h
-
- ) : (
- Zbývá: {data.monthly_fund.remaining}h
- )}
-
- 0
- ? Math.min(
- 100,
- (data.monthly_fund.covered /
- data.monthly_fund.fund) *
- 100,
- )
- : 0
- }
- color={getFundBarColor(data.monthly_fund)}
- height={6}
- />
- {data.monthly_fund.leave_hours > 0 && (
-
- {"Pokryto: "}
- {data.monthly_fund.covered}h (práce{" "}
- {data.monthly_fund.worked}h
- {data.monthly_fund.vacation_hours > 0 &&
- ` + dovolená ${data.monthly_fund.vacation_hours}h`}
- {data.monthly_fund.sick_hours > 0 &&
- ` + nemoc ${data.monthly_fund.sick_hours}h`}
- {data.monthly_fund.unpaid_hours > 0 &&
- ` + neplacené ${data.monthly_fund.unpaid_hours}h`}
- )
-
- )}
-
- }
+ {/* Sick Leave Card */}
+
+
+ {/* Quick Links */}
+
+
+ Rychlé odkazy
+
+
+
- )}
-
- {/* Sick Leave Card */}
-
-
- {/* Quick Links */}
-
-
- Rychlé odkazy
-
-
+
+ {hasPermission("attendance.manage") && (
+ )}
+ {hasPermission("attendance.balances") && (
- {hasPermission("attendance.manage") && (
-
- )}
- {hasPermission("attendance.balances") && (
-
- )}
-
-
-
-
+ )}
+
+
+
{/* Leave Modal */}
@@ -1273,6 +1256,6 @@ export default function Attendance() {
cancelText="Zrušit"
confirmVariant="primary"
/>
-
+
);
}
diff --git a/src/admin/pages/AttendanceAdmin.tsx b/src/admin/pages/AttendanceAdmin.tsx
index a14ba23..dc99121 100644
--- a/src/admin/pages/AttendanceAdmin.tsx
+++ b/src/admin/pages/AttendanceAdmin.tsx
@@ -1,4 +1,3 @@
-import { motion } from "framer-motion";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { useAlert } from "../context/AlertContext";
@@ -18,6 +17,7 @@ import {
FilterBar,
LoadingState,
MonthField,
+ PageEnter,
Select,
StatCard,
StatusChip,
@@ -165,262 +165,236 @@ export default function AttendanceAdmin() {
const hasTotals = Object.keys(data.user_totals).length > 0;
return (
-
-
+
+ Správa docházky
+
+ {hasData && (
+
+ )}
+
+
+
+
+
+ {/* Filters */}
+
+
+
+ setMonth(val)} />
+
+
+
+
+
+
+
+
+ {/* User Totals (KPI cards) */}
+ {hasTotals && (
- Správa docházky
-
- {hasData && (
-
- )}
-
-
-
-
-
-
- {/* Filters */}
-
-
-
-
- setMonth(val)} />
-
-
-
-
-
-
-
-
-
- {/* User Totals (KPI cards) */}
- {hasTotals && (
-
-
- {Object.entries(data.user_totals).map(([uid, userData]) => {
- const ut = userData as UserTotalData;
- const balance = data.leave_balances[uid];
- const statColor: StatCardColor = ut.working
- ? "success"
- : "default";
- return (
- {
+ const ut = userData as UserTotalData;
+ const balance = data.leave_balances[uid];
+ const statColor: StatCardColor = ut.working ? "success" : "default";
+ return (
+
+ {formatMinutes(ut.minutes)}
+
+
+ }
+ color={statColor}
+ footer={
+
+ {/* Leave-type badges */}
- {formatMinutes(ut.minutes)}
-
+ {ut.vacation_hours > 0 && (
+
+ )}
+ {ut.sick_hours > 0 && (
+
+ )}
+ {ut.svatek !== undefined && ut.svatek > 0 && (
+
+ )}
+ {ut.unpaid_hours > 0 && (
+
+ )}
- }
- color={statColor}
- footer={
-
- {/* Leave-type badges */}
-
- {ut.vacation_hours > 0 && (
-
- )}
- {ut.sick_hours > 0 && (
-
- )}
- {ut.svatek !== undefined && ut.svatek > 0 && (
-
- )}
- {ut.unpaid_hours > 0 && (
-
- )}
-
- {/* Fond usage */}
- {ut.fund !== null &&
- (() => {
- // Fond "used" = real_worked + vacation +
- // worked_holiday + free_holiday (everything the user
- // actually got paid for this month).
- const fondUsed = getFondUsed(ut);
- const fundVal = ut.fund ?? 0;
- const delta = fondUsed - fundVal;
- const pct = Math.min(
- 100,
- (fondUsed / (ut.fund || 1)) * 100,
- );
- return (
-
- {
+ // Fond "used" = real_worked + vacation +
+ // worked_holiday + free_holiday (everything the user
+ // actually got paid for this month).
+ const fondUsed = getFondUsed(ut);
+ const fundVal = ut.fund ?? 0;
+ const delta = fondUsed - fundVal;
+ const pct = Math.min(
+ 100,
+ (fondUsed / (ut.fund || 1)) * 100,
+ );
+ return (
+
+
+
+ Fond: {formatHoursDecimal(fondUsed * 60)}h /{" "}
+ {formatHoursDecimal(fundVal * 60)}h
+
+ {delta > 0 && (
- Fond: {formatHoursDecimal(fondUsed * 60)}h /{" "}
- {formatHoursDecimal(fundVal * 60)}h
+ +{formatHoursDecimal(delta * 60)}h
- {delta > 0 && (
-
- +{formatHoursDecimal(delta * 60)}h
-
- )}
- {delta < 0 && (
-
- -{formatHoursDecimal(Math.abs(delta) * 60)}h
-
- )}
-
-
+ )}
+ {delta < 0 && (
+
+ -{formatHoursDecimal(Math.abs(delta) * 60)}h
+
+ )}
- );
- })()}
+
+
+ );
+ })()}
- {/* Remaining vacation */}
-
- {balance ? (
-
- Zbývá dovolené:{" "}
- {balance.vacation_remaining.toFixed(1)}h /{" "}
- {balance.vacation_total}h
-
- ) : (
-
- —
-
- )}
-
+ {/* Remaining vacation */}
+
+ {balance ? (
+
+ Zbývá dovolené:{" "}
+ {balance.vacation_remaining.toFixed(1)}h /{" "}
+ {balance.vacation_total}h
+
+ ) : (
+
+ —
+
+ )}
- }
- />
- );
- })}
-
-
+
+ }
+ />
+ );
+ })}
+
)}
{/* Records Table */}
-
-
- setDeleteConfirm({ show: true, record })}
- />
-
-
+
+ setDeleteConfirm({ show: true, record })}
+ />
+
{/* Modals */}
-
+
);
}
diff --git a/src/admin/pages/AttendanceBalances.tsx b/src/admin/pages/AttendanceBalances.tsx
index 9555f2e..08017eb 100644
--- a/src/admin/pages/AttendanceBalances.tsx
+++ b/src/admin/pages/AttendanceBalances.tsx
@@ -25,6 +25,7 @@ import {
Select,
StatusChip,
PageHeader,
+ PageEnter,
EmptyState,
LoadingState,
ProgressBar,
@@ -392,7 +393,7 @@ export default function AttendanceBalances() {
];
return (
-
+
-
+
);
}
diff --git a/src/admin/pages/AttendanceCreate.tsx b/src/admin/pages/AttendanceCreate.tsx
index 9de2318..e2a095c 100644
--- a/src/admin/pages/AttendanceCreate.tsx
+++ b/src/admin/pages/AttendanceCreate.tsx
@@ -3,7 +3,6 @@ import { useQuery } from "@tanstack/react-query";
import { Link as RouterLink } from "react-router-dom";
import { useNavigate } from "react-router-dom";
import Box from "@mui/material/Box";
-import { motion } from "framer-motion";
import { userListOptions } from "../lib/queries/users";
import { useAlert } from "../context/AlertContext";
@@ -17,6 +16,7 @@ import {
DateField,
Field,
LoadingState,
+ PageEnter,
PageHeader,
Select,
TextField,
@@ -118,230 +118,214 @@ export default function AttendanceCreate() {
}
return (
-
-
-
+
+ ← Zpět na správu
+
+ }
+ />
+
+
+
+ {/* Employee + Shift date */}
+
+
+
+
+ handleShiftDateChange(val)}
+ />
+
+
+
+ {/* Record type */}
+
+
+
+ {/* Leave hours — shown only for non-work types */}
+ {!isWorkType && (
+
+
+ setForm({
+ ...form,
+ leave_hours: parseFloat(e.target.value),
+ })
+ }
+ slotProps={{ htmlInput: { min: 0.5, max: 24, step: 0.5 } }}
+ />
+
+ )}
+
+ {/* Work-type time fields */}
+ {isWorkType && (
+ <>
+ {/* Arrival */}
+
+
+ setForm({ ...form, arrival_date: val })}
+ />
+
+
+ setForm({ ...form, arrival_time: val })}
+ />
+
+
+
+ {/* Break start */}
+
+
+
+ setForm({ ...form, break_start_date: val })
+ }
+ />
+
+
+
+ setForm({ ...form, break_start_time: val })
+ }
+ />
+
+
+
+ {/* Break end */}
+
+
+
+ setForm({ ...form, break_end_date: val })
+ }
+ />
+
+
+
+ setForm({ ...form, break_end_time: val })
+ }
+ />
+
+
+
+ {/* Departure */}
+
+
+
+ setForm({ ...form, departure_date: val })
+ }
+ />
+
+
+
+ setForm({ ...form, departure_time: val })
+ }
+ />
+
+
+ >
+ )}
+
+ {/* Notes */}
+
+ setForm({ ...form, notes: e.target.value })}
+ />
+
+
+ {/* Actions */}
+
+
- }
- />
-
-
-
-
-
- {/* Employee + Shift date */}
-
-
-
-
- handleShiftDateChange(val)}
- />
-
-
-
- {/* Record type */}
-
-
-
- {/* Leave hours — shown only for non-work types */}
- {!isWorkType && (
-
-
- setForm({
- ...form,
- leave_hours: parseFloat(e.target.value),
- })
- }
- slotProps={{ htmlInput: { min: 0.5, max: 24, step: 0.5 } }}
- />
-
- )}
-
- {/* Work-type time fields */}
- {isWorkType && (
- <>
- {/* Arrival */}
-
-
-
- setForm({ ...form, arrival_date: val })
- }
- />
-
-
-
- setForm({ ...form, arrival_time: val })
- }
- />
-
-
-
- {/* Break start */}
-
-
-
- setForm({ ...form, break_start_date: val })
- }
- />
-
-
-
- setForm({ ...form, break_start_time: val })
- }
- />
-
-
-
- {/* Break end */}
-
-
-
- setForm({ ...form, break_end_date: val })
- }
- />
-
-
-
- setForm({ ...form, break_end_time: val })
- }
- />
-
-
-
- {/* Departure */}
-
-
-
- setForm({ ...form, departure_date: val })
- }
- />
-
-
-
- setForm({ ...form, departure_time: val })
- }
- />
-
-
- >
- )}
-
- {/* Notes */}
-
- setForm({ ...form, notes: e.target.value })}
- />
-
-
- {/* Actions */}
-
-
-
-
-
-
-
+
+
+
);
}
diff --git a/src/admin/pages/AttendanceHistory.tsx b/src/admin/pages/AttendanceHistory.tsx
index 0d1d9dc..9f9b6d5 100644
--- a/src/admin/pages/AttendanceHistory.tsx
+++ b/src/admin/pages/AttendanceHistory.tsx
@@ -1,6 +1,5 @@
import { useState, useMemo, useRef } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
-import { motion } from "framer-motion";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { useAuth } from "../context/AuthContext";
@@ -36,6 +35,7 @@ import {
ProgressBar,
StatusChip,
PageHeader,
+ PageEnter,
EmptyState,
LoadingState,
type DataColumn,
@@ -494,196 +494,169 @@ export default function AttendanceHistory() {
];
return (
-
-
- 0 ? (
-
- ) : undefined
- }
- />
-
+
+ 0 ? (
+
+ ) : undefined
+ }
+ />
{/* Filters */}
-
-
-
-
- setMonth(val)} />
-
-
-
-
+
+
+
+ setMonth(val)} />
+
+
+
{/* Monthly Fund Card */}
-
-
- {isPending ? (
-
- ) : (
+
+ {isPending ? (
+
+ ) : (
+
+
+
+
-
+ {fund.business_days} prac. dnů
+ {fund.holiday_count > 0 && ` + ${fund.holiday_count} svátky`}
+
-
-
-
- Fond: {fund.worked}h / {fund.fund}h
-
-
- {fund.business_days} prac. dnů
- {fund.holiday_count > 0 &&
- ` + ${fund.holiday_count} svátky`}
-
+
+
+
+ {computed.totalMinutes > 0 && (
+
+ )}
+ {computed.vacationHours > 0 && (
+
+ )}
+ {computed.sickHours > 0 && (
+
+ )}
+ {computed.freeHolidayHours > 0 && (
+
+ )}
+ {computed.unpaidHours > 0 && (
+
+ )}
-
-
-
- {computed.totalMinutes > 0 && (
-
- )}
- {computed.vacationHours > 0 && (
-
- )}
- {computed.sickHours > 0 && (
-
- )}
- {computed.freeHolidayHours > 0 && (
-
- )}
- {computed.unpaidHours > 0 && (
-
- )}
-
-
- {computed.delta > 0
- ? `Přesčas: +${formatHoursDecimal(computed.delta * 60)}h`
- : computed.delta < 0
- ? `Zbývá: ${formatHoursDecimal(-computed.delta * 60)}h`
- : "Fond splněn"}
-
-
+ {computed.delta > 0
+ ? `Přesčas: +${formatHoursDecimal(computed.delta * 60)}h`
+ : computed.delta < 0
+ ? `Zbývá: ${formatHoursDecimal(-computed.delta * 60)}h`
+ : "Fond splněn"}
+
- )}
-
-
+
+ )}
+
{/* Records Table */}
-
-
- {isPending ? (
-
- ) : (
-
- columns={columns}
- rows={records}
- rowKey={(record) => record.id}
- empty={
-
- }
- />
- )}
-
-
+
+ {isPending ? (
+
+ ) : (
+
+ columns={columns}
+ rows={records}
+ rowKey={(record) => record.id}
+ empty={}
+ />
+ )}
+
{/* Hidden Print Content */}
{records.length > 0 && (
@@ -888,7 +861,7 @@ export default function AttendanceHistory() {
)}
-
+
);
}
diff --git a/src/admin/pages/AttendanceLocation.tsx b/src/admin/pages/AttendanceLocation.tsx
index 8ece091..b8ed5e5 100644
--- a/src/admin/pages/AttendanceLocation.tsx
+++ b/src/admin/pages/AttendanceLocation.tsx
@@ -4,7 +4,6 @@ import { useAlert } from "../context/AlertContext";
import { useAuth } from "../context/AuthContext";
import Forbidden from "../components/Forbidden";
import { useNavigate, useParams, Link as RouterLink } from "react-router-dom";
-import { motion } from "framer-motion";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
@@ -16,7 +15,7 @@ import {
attendanceLocationOptions,
type LocationRecord,
} from "../lib/queries/attendance";
-import { Button, Card, LoadingState, PageHeader } from "../ui";
+import { Button, Card, LoadingState, PageEnter, PageHeader } from "../ui";
const BackIcon = (