This commit is contained in:
2025-11-28 11:51:29 +01:00
parent bb22213d19
commit 30cd0c51f5
14 changed files with 3622 additions and 1011 deletions

View File

@@ -22,6 +22,11 @@ import {
Chip,
Tooltip,
CircularProgress,
useMediaQuery,
useTheme,
Stack,
Fab,
Zoom,
} from "@mui/material";
import {
Add as AddIcon,
@@ -38,6 +43,11 @@ import type { ReportTemplateDto } from "../types/report";
export default function ReportTemplatesPage() {
const navigate = useNavigate();
const queryClient = useQueryClient();
const theme = useTheme();
// Breakpoints
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
const [filterCategoria, setFilterCategoria] = useState<string>("");
const [deleteDialog, setDeleteDialog] = useState<{
open: boolean;
@@ -138,34 +148,54 @@ export default function ReportTemplatesPage() {
}
return (
<Box>
<Box sx={{ pb: isMobile ? 10 : 0 }}>
{/* Header */}
<Box
display="flex"
justifyContent="space-between"
alignItems="center"
mb={3}
sx={{
display: "flex",
flexDirection: { xs: "column", sm: "row" },
justifyContent: "space-between",
alignItems: { xs: "stretch", sm: "center" },
gap: 2,
mb: 3,
}}
>
<Typography variant="h4">Template Report</Typography>
<Box display="flex" gap={2}>
<Button
variant="outlined"
startIcon={<UploadIcon />}
onClick={() => setImportDialog(true)}
>
Importa
</Button>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => navigate("/report-editor")}
>
Nuovo Template
</Button>
</Box>
<Typography variant={isMobile ? "h5" : "h4"} sx={{ fontWeight: 600 }}>
Template Report
</Typography>
{/* Desktop/Tablet buttons */}
{!isMobile && (
<Stack direction="row" spacing={2}>
<Button
variant="outlined"
startIcon={<UploadIcon />}
onClick={() => setImportDialog(true)}
>
Importa
</Button>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={() => navigate("/report-editor")}
>
Nuovo Template
</Button>
</Stack>
)}
</Box>
<Box mb={3}>
<FormControl size="small" sx={{ minWidth: 200 }}>
{/* Filter */}
<Box
sx={{
mb: 3,
display: "flex",
flexDirection: { xs: "column", sm: "row" },
gap: 2,
alignItems: { xs: "stretch", sm: "center" },
}}
>
<FormControl size="small" sx={{ minWidth: { xs: "100%", sm: 200 } }}>
<InputLabel>Filtra per categoria</InputLabel>
<Select
value={filterCategoria}
@@ -180,13 +210,36 @@ export default function ReportTemplatesPage() {
))}
</Select>
</FormControl>
{/* Mobile import button inline with filter */}
{isMobile && (
<Button
variant="outlined"
startIcon={<UploadIcon />}
onClick={() => setImportDialog(true)}
fullWidth
>
Importa Template
</Button>
)}
</Box>
{/* Empty State */}
{templates.length === 0 ? (
<Card>
<CardContent sx={{ textAlign: "center", py: 6 }}>
<CardContent
sx={{
textAlign: "center",
py: { xs: 4, sm: 6 },
px: { xs: 2, sm: 3 },
}}
>
<DescriptionIcon
sx={{ fontSize: 64, color: "text.secondary", mb: 2 }}
sx={{
fontSize: { xs: 48, sm: 64 },
color: "text.secondary",
mb: 2,
}}
/>
<Typography variant="h6" color="text.secondary" gutterBottom>
Nessun template trovato
@@ -194,11 +247,16 @@ export default function ReportTemplatesPage() {
<Typography color="text.secondary" mb={3}>
Crea il tuo primo template di report o importane uno esistente
</Typography>
<Box display="flex" gap={2} justifyContent="center">
<Stack
direction={{ xs: "column", sm: "row" }}
spacing={2}
justifyContent="center"
>
<Button
variant="outlined"
startIcon={<UploadIcon />}
onClick={() => setImportDialog(true)}
fullWidth={isMobile}
>
Importa Template
</Button>
@@ -206,27 +264,44 @@ export default function ReportTemplatesPage() {
variant="contained"
startIcon={<AddIcon />}
onClick={() => navigate("/report-editor")}
fullWidth={isMobile}
>
Crea Template
</Button>
</Box>
</Stack>
</CardContent>
</Card>
) : (
<Grid container spacing={3}>
/* Template Grid */
<Grid container spacing={{ xs: 2, sm: 3 }}>
{templates.map((template) => (
<Grid size={{ xs: 12, sm: 6, md: 4, lg: 3 }} key={template.id}>
<Grid
size={{
xs: 12,
sm: 6,
md: 4,
lg: 3,
xl: 2,
}}
key={template.id}
>
<Card
sx={{
height: "100%",
display: "flex",
flexDirection: "column",
transition: "transform 0.2s, box-shadow 0.2s",
"&:hover": {
transform: { sm: "translateY(-4px)" },
boxShadow: { sm: 4 },
},
}}
>
{/* Thumbnail */}
{template.thumbnailBase64 ? (
<CardMedia
component="img"
height="160"
height={isMobile ? 120 : 160}
image={`data:image/png;base64,${template.thumbnailBase64}`}
alt={template.nome}
sx={{ objectFit: "contain", bgcolor: "grey.100" }}
@@ -234,37 +309,56 @@ export default function ReportTemplatesPage() {
) : (
<Box
sx={{
height: 160,
height: isMobile ? 120 : 160,
display: "flex",
alignItems: "center",
justifyContent: "center",
bgcolor: "grey.100",
}}
>
<DescriptionIcon sx={{ fontSize: 64, color: "grey.400" }} />
<DescriptionIcon
sx={{ fontSize: { xs: 48, sm: 64 }, color: "grey.400" }}
/>
</Box>
)}
<CardContent sx={{ flexGrow: 1 }}>
{/* Content */}
<CardContent sx={{ flexGrow: 1, p: { xs: 1.5, sm: 2 } }}>
<Box
display="flex"
justifyContent="space-between"
alignItems="flex-start"
mb={1}
gap={1}
>
<Typography variant="h6" noWrap sx={{ maxWidth: "70%" }}>
<Typography
variant={isMobile ? "body1" : "h6"}
noWrap
sx={{
flex: 1,
fontWeight: 600,
}}
>
{template.nome}
</Typography>
<Chip
label={template.categoria}
size="small"
color={getCategoriaColor(template.categoria)}
sx={{ flexShrink: 0 }}
/>
</Box>
{template.descrizione && (
<Typography
variant="body2"
color="text.secondary"
sx={{ mb: 1 }}
sx={{
mb: 1,
display: "-webkit-box",
WebkitLineClamp: 2,
WebkitBoxOrient: "vertical",
overflow: "hidden",
}}
>
{template.descrizione}
</Typography>
@@ -276,7 +370,17 @@ export default function ReportTemplatesPage() {
: "Orizzontale"}
</Typography>
</CardContent>
<CardActions sx={{ justifyContent: "space-between" }}>
{/* Actions */}
<CardActions
sx={{
justifyContent: "space-between",
px: { xs: 1, sm: 1.5 },
py: 1,
borderTop: 1,
borderColor: "divider",
}}
>
<Box>
<Tooltip title="Modifica">
<IconButton
@@ -285,7 +389,7 @@ export default function ReportTemplatesPage() {
navigate(`/report-editor/${template.id}`)
}
>
<EditIcon />
<EditIcon fontSize={isMobile ? "small" : "medium"} />
</IconButton>
</Tooltip>
<Tooltip title="Duplica">
@@ -293,7 +397,7 @@ export default function ReportTemplatesPage() {
size="small"
onClick={() => cloneMutation.mutate(template.id)}
>
<CopyIcon />
<CopyIcon fontSize={isMobile ? "small" : "medium"} />
</IconButton>
</Tooltip>
<Tooltip title="Esporta">
@@ -301,7 +405,9 @@ export default function ReportTemplatesPage() {
size="small"
onClick={() => handleExport(template)}
>
<DownloadIcon />
<DownloadIcon
fontSize={isMobile ? "small" : "medium"}
/>
</IconButton>
</Tooltip>
</Box>
@@ -311,7 +417,7 @@ export default function ReportTemplatesPage() {
color="error"
onClick={() => setDeleteDialog({ open: true, template })}
>
<DeleteIcon />
<DeleteIcon fontSize={isMobile ? "small" : "medium"} />
</IconButton>
</Tooltip>
</CardActions>
@@ -321,10 +427,32 @@ export default function ReportTemplatesPage() {
</Grid>
)}
{/* Mobile FAB */}
{isMobile && (
<Zoom in>
<Fab
color="primary"
aria-label="Nuovo template"
onClick={() => navigate("/report-editor")}
sx={{
position: "fixed",
bottom: 16,
right: 16,
zIndex: 1000,
}}
>
<AddIcon />
</Fab>
</Zoom>
)}
{/* Delete Dialog */}
<Dialog
open={deleteDialog.open}
onClose={() => setDeleteDialog({ open: false, template: null })}
fullWidth
maxWidth="xs"
fullScreen={isMobile}
>
<DialogTitle>Conferma Eliminazione</DialogTitle>
<DialogContent>
@@ -336,9 +464,10 @@ export default function ReportTemplatesPage() {
Questa azione non può essere annullata.
</Typography>
</DialogContent>
<DialogActions>
<DialogActions sx={{ p: { xs: 2, sm: 1 } }}>
<Button
onClick={() => setDeleteDialog({ open: false, template: null })}
fullWidth={isMobile}
>
Annulla
</Button>
@@ -350,6 +479,7 @@ export default function ReportTemplatesPage() {
deleteMutation.mutate(deleteDialog.template.id)
}
disabled={deleteMutation.isPending}
fullWidth={isMobile}
>
{deleteMutation.isPending ? "Eliminazione..." : "Elimina"}
</Button>
@@ -363,6 +493,9 @@ export default function ReportTemplatesPage() {
setImportDialog(false);
setImportFile(null);
}}
fullWidth
maxWidth="xs"
fullScreen={isMobile}
>
<DialogTitle>Importa Template</DialogTitle>
<DialogContent>
@@ -379,12 +512,13 @@ export default function ReportTemplatesPage() {
/>
</Button>
</DialogContent>
<DialogActions>
<DialogActions sx={{ p: { xs: 2, sm: 1 } }}>
<Button
onClick={() => {
setImportDialog(false);
setImportFile(null);
}}
fullWidth={isMobile}
>
Annulla
</Button>
@@ -392,6 +526,7 @@ export default function ReportTemplatesPage() {
variant="contained"
onClick={handleImport}
disabled={!importFile || importMutation.isPending}
fullWidth={isMobile}
>
{importMutation.isPending ? "Importazione..." : "Importa"}
</Button>