feat(mui): migrate Dashboard (Přehled) onto MUI kit
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
import { useState } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link as RouterLink } from "react-router-dom";
|
||||
import { motion } from "framer-motion";
|
||||
import Box from "@mui/material/Box";
|
||||
import { useAuth } from "../../context/AuthContext";
|
||||
import { useAlert } from "../../context/AlertContext";
|
||||
import { formatKm, todayLocalStr } from "../../utils/formatters";
|
||||
import AdminDatePicker from "../AdminDatePicker";
|
||||
import { Button, Modal, Field, Select, DateField, TextField } from "../../ui";
|
||||
import apiFetch from "../../utils/api";
|
||||
import FormModal from "../FormModal";
|
||||
|
||||
const API_BASE = "/api/admin";
|
||||
|
||||
@@ -46,6 +46,14 @@ interface DashQuickActionsProps {
|
||||
onPunch: () => void;
|
||||
}
|
||||
|
||||
// Maps the legacy quick-action color tokens to the MUI Button palette color.
|
||||
const ACTION_COLOR: Record<string, "success" | "error" | "info" | "warning"> = {
|
||||
success: "success",
|
||||
danger: "error",
|
||||
info: "info",
|
||||
warning: "warning",
|
||||
};
|
||||
|
||||
export default function DashQuickActions({
|
||||
dashData,
|
||||
punching,
|
||||
@@ -284,252 +292,192 @@ export default function DashQuickActions({
|
||||
|
||||
return (
|
||||
<>
|
||||
<motion.div
|
||||
className="dash-quick-actions"
|
||||
<Box
|
||||
component={motion.div}
|
||||
initial={{ opacity: 0, y: 12 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.25, delay: 0.08 }}
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: {
|
||||
xs: "1fr",
|
||||
sm: "repeat(2, 1fr)",
|
||||
md: "repeat(4, 1fr)",
|
||||
},
|
||||
gap: 2,
|
||||
mb: 3,
|
||||
}}
|
||||
>
|
||||
{quickActions.map((action) =>
|
||||
action.onClick ? (
|
||||
<button
|
||||
{quickActions.map((action) => {
|
||||
const color = ACTION_COLOR[action.color] ?? "primary";
|
||||
const common = {
|
||||
variant: "contained" as const,
|
||||
color,
|
||||
startIcon: action.icon,
|
||||
sx: { py: 1.5, justifyContent: "flex-start" },
|
||||
};
|
||||
return action.onClick ? (
|
||||
<Button
|
||||
key={action.label}
|
||||
onClick={action.onClick}
|
||||
disabled={action.disabled}
|
||||
className={`dash-quick-btn dash-quick-btn-${action.color}`}
|
||||
{...common}
|
||||
>
|
||||
{action.icon}
|
||||
<span>{action.label}</span>
|
||||
</button>
|
||||
{action.label}
|
||||
</Button>
|
||||
) : (
|
||||
<Link
|
||||
<Button
|
||||
key={action.label}
|
||||
component={RouterLink}
|
||||
to={action.path!}
|
||||
className={`dash-quick-btn dash-quick-btn-${action.color}`}
|
||||
{...common}
|
||||
>
|
||||
{action.icon}
|
||||
<span>{action.label}</span>
|
||||
</Link>
|
||||
),
|
||||
)}
|
||||
</motion.div>
|
||||
{action.label}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
|
||||
<FormModal
|
||||
<Modal
|
||||
isOpen={showTripModal}
|
||||
onClose={() => setShowTripModal(false)}
|
||||
title="Přidat jízdu"
|
||||
size="lg"
|
||||
maxWidth="md"
|
||||
onSubmit={handleTripSubmit}
|
||||
submitLabel="Uložit"
|
||||
submitText="Uložit"
|
||||
loading={tripSubmitting}
|
||||
>
|
||||
<div className="admin-form">
|
||||
<div className="admin-form-row">
|
||||
<div
|
||||
className={`admin-form-group${tripErrors.vehicle_id ? " has-error" : ""}`}
|
||||
>
|
||||
<label className="admin-form-label required">Vozidlo</label>
|
||||
<select
|
||||
value={tripForm.vehicle_id}
|
||||
onChange={(e) => {
|
||||
handleTripVehicleChange(e.target.value);
|
||||
setTripErrors((prev) => ({
|
||||
...prev,
|
||||
vehicle_id: undefined,
|
||||
}));
|
||||
}}
|
||||
className="admin-form-select"
|
||||
>
|
||||
<option value="">Vyberte vozidlo</option>
|
||||
{tripVehicles.map((v) => (
|
||||
<option key={v.id} value={v.id}>
|
||||
{v.spz} - {v.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{tripErrors.vehicle_id && (
|
||||
<span className="admin-form-error">
|
||||
{tripErrors.vehicle_id}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`admin-form-group${tripErrors.trip_date ? " has-error" : ""}`}
|
||||
>
|
||||
<label className="admin-form-label required">Datum jízdy</label>
|
||||
<AdminDatePicker
|
||||
mode="date"
|
||||
value={tripForm.trip_date}
|
||||
onChange={(val: string) => {
|
||||
setTripForm((prev) => ({ ...prev, trip_date: val }));
|
||||
setTripErrors((prev) => ({
|
||||
...prev,
|
||||
trip_date: undefined,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
{tripErrors.trip_date && (
|
||||
<span className="admin-form-error">{tripErrors.trip_date}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="admin-form-row admin-form-row-3">
|
||||
<div
|
||||
className={`admin-form-group${tripErrors.start_km ? " has-error" : ""}`}
|
||||
>
|
||||
<label className="admin-form-label required">
|
||||
Počáteční stav km
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
value={tripForm.start_km}
|
||||
onChange={(e) => {
|
||||
setTripForm((prev) => ({
|
||||
...prev,
|
||||
start_km: e.target.value,
|
||||
}));
|
||||
setTripErrors((prev) => ({
|
||||
...prev,
|
||||
start_km: undefined,
|
||||
}));
|
||||
}}
|
||||
className="admin-form-input"
|
||||
min="0"
|
||||
/>
|
||||
{tripErrors.start_km && (
|
||||
<span className="admin-form-error">{tripErrors.start_km}</span>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`admin-form-group${tripErrors.end_km ? " has-error" : ""}`}
|
||||
>
|
||||
<label className="admin-form-label required">
|
||||
Konečný stav km
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
value={tripForm.end_km}
|
||||
onChange={(e) => {
|
||||
setTripForm((prev) => ({
|
||||
...prev,
|
||||
end_km: e.target.value,
|
||||
}));
|
||||
setTripErrors((prev) => ({
|
||||
...prev,
|
||||
end_km: undefined,
|
||||
}));
|
||||
}}
|
||||
className="admin-form-input"
|
||||
min="0"
|
||||
/>
|
||||
{tripErrors.end_km && (
|
||||
<span className="admin-form-error">{tripErrors.end_km}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="admin-form-group">
|
||||
<label className="admin-form-label">Vzdálenost</label>
|
||||
<input
|
||||
type="text"
|
||||
value={`${formatKm(tripDistance())} km`}
|
||||
className="admin-form-input"
|
||||
readOnly
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="admin-form-row">
|
||||
<div
|
||||
className={`admin-form-group${tripErrors.route_from ? " has-error" : ""}`}
|
||||
>
|
||||
<label className="admin-form-label required">Místo odjezdu</label>
|
||||
<input
|
||||
type="text"
|
||||
value={tripForm.route_from}
|
||||
onChange={(e) => {
|
||||
setTripForm((prev) => ({
|
||||
...prev,
|
||||
route_from: e.target.value,
|
||||
}));
|
||||
setTripErrors((prev) => ({
|
||||
...prev,
|
||||
route_from: undefined,
|
||||
}));
|
||||
}}
|
||||
className="admin-form-input"
|
||||
placeholder="Např. Praha"
|
||||
/>
|
||||
{tripErrors.route_from && (
|
||||
<span className="admin-form-error">
|
||||
{tripErrors.route_from}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
className={`admin-form-group${tripErrors.route_to ? " has-error" : ""}`}
|
||||
>
|
||||
<label className="admin-form-label required">
|
||||
Místo příjezdu
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={tripForm.route_to}
|
||||
onChange={(e) => {
|
||||
setTripForm((prev) => ({
|
||||
...prev,
|
||||
route_to: e.target.value,
|
||||
}));
|
||||
setTripErrors((prev) => ({
|
||||
...prev,
|
||||
route_to: undefined,
|
||||
}));
|
||||
}}
|
||||
className="admin-form-input"
|
||||
placeholder="Např. Brno"
|
||||
/>
|
||||
{tripErrors.route_to && (
|
||||
<span className="admin-form-error">{tripErrors.route_to}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="admin-form-group">
|
||||
<label className="admin-form-label">Typ jízdy</label>
|
||||
<select
|
||||
value={tripForm.is_business}
|
||||
onChange={(e) =>
|
||||
setTripForm((prev) => ({
|
||||
...prev,
|
||||
is_business: parseInt(e.target.value),
|
||||
}))
|
||||
}
|
||||
className="admin-form-select"
|
||||
>
|
||||
<option value={1}>Služební</option>
|
||||
<option value={0}>Soukromá</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="admin-form-group">
|
||||
<label className="admin-form-label">Poznámky</label>
|
||||
<textarea
|
||||
value={tripForm.notes}
|
||||
onChange={(e) =>
|
||||
setTripForm((prev) => ({
|
||||
...prev,
|
||||
notes: e.target.value,
|
||||
}))
|
||||
}
|
||||
className="admin-form-textarea"
|
||||
rows={2}
|
||||
placeholder="Volitelné poznámky..."
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr" },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Field label="Vozidlo" required error={tripErrors.vehicle_id}>
|
||||
<Select
|
||||
value={tripForm.vehicle_id}
|
||||
onChange={(val) => {
|
||||
handleTripVehicleChange(val);
|
||||
setTripErrors((prev) => ({ ...prev, vehicle_id: undefined }));
|
||||
}}
|
||||
options={[
|
||||
{ value: "", label: "Vyberte vozidlo" },
|
||||
...tripVehicles.map((v) => ({
|
||||
value: String(v.id),
|
||||
label: `${v.spz} - ${v.name}`,
|
||||
})),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</FormModal>
|
||||
</Field>
|
||||
<Field label="Datum jízdy" required error={tripErrors.trip_date}>
|
||||
<DateField
|
||||
value={tripForm.trip_date}
|
||||
onChange={(val) => {
|
||||
setTripForm((prev) => ({ ...prev, trip_date: val }));
|
||||
setTripErrors((prev) => ({ ...prev, trip_date: undefined }));
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", sm: "repeat(3, 1fr)" },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Field label="Počáteční stav km" required error={tripErrors.start_km}>
|
||||
<TextField
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
value={tripForm.start_km}
|
||||
onChange={(e) => {
|
||||
setTripForm((prev) => ({ ...prev, start_km: e.target.value }));
|
||||
setTripErrors((prev) => ({ ...prev, start_km: undefined }));
|
||||
}}
|
||||
slotProps={{ htmlInput: { min: 0 } }}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Konečný stav km" required error={tripErrors.end_km}>
|
||||
<TextField
|
||||
type="number"
|
||||
inputMode="numeric"
|
||||
value={tripForm.end_km}
|
||||
onChange={(e) => {
|
||||
setTripForm((prev) => ({ ...prev, end_km: e.target.value }));
|
||||
setTripErrors((prev) => ({ ...prev, end_km: undefined }));
|
||||
}}
|
||||
slotProps={{ htmlInput: { min: 0 } }}
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Vzdálenost">
|
||||
<TextField
|
||||
value={`${formatKm(tripDistance())} km`}
|
||||
InputProps={{ readOnly: true }}
|
||||
disabled
|
||||
/>
|
||||
</Field>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr" },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Field label="Místo odjezdu" required error={tripErrors.route_from}>
|
||||
<TextField
|
||||
value={tripForm.route_from}
|
||||
onChange={(e) => {
|
||||
setTripForm((prev) => ({
|
||||
...prev,
|
||||
route_from: e.target.value,
|
||||
}));
|
||||
setTripErrors((prev) => ({ ...prev, route_from: undefined }));
|
||||
}}
|
||||
placeholder="Např. Praha"
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Místo příjezdu" required error={tripErrors.route_to}>
|
||||
<TextField
|
||||
value={tripForm.route_to}
|
||||
onChange={(e) => {
|
||||
setTripForm((prev) => ({ ...prev, route_to: e.target.value }));
|
||||
setTripErrors((prev) => ({ ...prev, route_to: undefined }));
|
||||
}}
|
||||
placeholder="Např. Brno"
|
||||
/>
|
||||
</Field>
|
||||
</Box>
|
||||
|
||||
<Field label="Typ jízdy">
|
||||
<Select
|
||||
value={String(tripForm.is_business)}
|
||||
onChange={(val) =>
|
||||
setTripForm((prev) => ({ ...prev, is_business: parseInt(val) }))
|
||||
}
|
||||
options={[
|
||||
{ value: "1", label: "Služební" },
|
||||
{ value: "0", label: "Soukromá" },
|
||||
]}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field label="Poznámky">
|
||||
<TextField
|
||||
multiline
|
||||
minRows={2}
|
||||
value={tripForm.notes}
|
||||
onChange={(e) =>
|
||||
setTripForm((prev) => ({ ...prev, notes: e.target.value }))
|
||||
}
|
||||
placeholder="Volitelné poznámky..."
|
||||
/>
|
||||
</Field>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user