This commit is contained in:
2025-11-29 13:30:28 +01:00
parent 824a761bf6
commit bb2d0729e1
16 changed files with 3102 additions and 37 deletions

View File

@@ -0,0 +1,386 @@
import { useState } from "react";
import { useParams, useNavigate, useLocation } from "react-router-dom";
import { useMutation } from "@tanstack/react-query";
import {
Box,
Card,
CardContent,
Typography,
Button,
ToggleButton,
ToggleButtonGroup,
Alert,
CircularProgress,
Chip,
Divider,
List,
ListItem,
ListItemIcon,
ListItemText,
Paper,
} from "@mui/material";
import {
CheckCircle as CheckIcon,
ArrowBack as BackIcon,
ShoppingCart as CartIcon,
CalendarMonth as MonthlyIcon,
CalendarToday as AnnualIcon,
Warning as WarningIcon,
} from "@mui/icons-material";
import { useModule, useModules } from "../contexts/ModuleContext";
import {
SubscriptionType,
formatPrice,
getSubscriptionTypeName,
} from "../types/module";
export default function ModulePurchasePage() {
const { code } = useParams<{ code: string }>();
const navigate = useNavigate();
const location = useLocation();
const module = useModule(code || "");
const { enableModule, isModuleEnabled } = useModules();
const [subscriptionType, setSubscriptionType] = useState<SubscriptionType>(
SubscriptionType.Annual,
);
const [autoRenew, setAutoRenew] = useState(true);
// Mutation per attivare il modulo
const enableMutation = useMutation({
mutationFn: async () => {
if (!code) throw new Error("Codice modulo mancante");
return enableModule(code, {
subscriptionType,
autoRenew,
});
},
onSuccess: () => {
// Redirect alla pagina originale o alla home del modulo
const from = (location.state as { from?: string })?.from;
navigate(from || module?.routePath || "/");
},
});
// Se il modulo è già abilitato, redirect
if (code && isModuleEnabled(code)) {
navigate(module?.routePath || "/");
return null;
}
if (!module) {
return (
<Box sx={{ p: 3, textAlign: "center" }}>
<Typography variant="h5" color="error" gutterBottom>
Modulo non trovato
</Typography>
<Typography color="text.secondary" paragraph>
Il modulo richiesto non esiste.
</Typography>
<Button
variant="contained"
startIcon={<BackIcon />}
onClick={() => navigate("/")}
>
Torna alla Home
</Button>
</Box>
);
}
// Calcola il prezzo in base al tipo di subscription
const price =
subscriptionType === SubscriptionType.Monthly
? module.monthlyPrice
: module.basePrice;
const priceLabel =
subscriptionType === SubscriptionType.Monthly ? "/mese" : "/anno";
// Calcola risparmio annuale
const annualSavings = module.monthlyPrice * 12 - module.basePrice;
const savingsPercent = Math.round(
(annualSavings / (module.monthlyPrice * 12)) * 100,
);
// Verifica dipendenze mancanti
const missingDependencies = module.dependencies.filter(
(dep) => !isModuleEnabled(dep),
);
return (
<Box sx={{ p: 3, maxWidth: 800, mx: "auto" }}>
{/* Header */}
<Box sx={{ mb: 4 }}>
<Button
startIcon={<BackIcon />}
onClick={() => navigate(-1)}
sx={{ mb: 2 }}
>
Indietro
</Button>
<Typography variant="h4" gutterBottom>
Attiva Modulo
</Typography>
<Typography color="text.secondary">
Scegli il piano di abbonamento per il modulo {module.name}
</Typography>
</Box>
{/* Alert dipendenze mancanti */}
{missingDependencies.length > 0 && (
<Alert severity="warning" sx={{ mb: 3 }} icon={<WarningIcon />}>
<Typography variant="subtitle2" gutterBottom>
Questo modulo richiede i seguenti moduli che non sono attivi:
</Typography>
<Box sx={{ display: "flex", gap: 1, mt: 1, flexWrap: "wrap" }}>
{missingDependencies.map((dep) => (
<Chip
key={dep}
label={dep}
size="small"
color="warning"
onClick={() => navigate(`/modules/purchase/${dep}`)}
/>
))}
</Box>
</Alert>
)}
{/* Card principale */}
<Card elevation={3}>
<CardContent sx={{ p: 4 }}>
{/* Info modulo */}
<Box sx={{ mb: 4 }}>
<Typography variant="h5" gutterBottom>
{module.name}
</Typography>
<Typography color="text.secondary" paragraph>
{module.description}
</Typography>
</Box>
<Divider sx={{ my: 3 }} />
{/* Selezione tipo abbonamento */}
<Box sx={{ mb: 4 }}>
<Typography variant="subtitle1" gutterBottom fontWeight="medium">
Tipo di abbonamento
</Typography>
<ToggleButtonGroup
value={subscriptionType}
exclusive
onChange={(_, value) => value && setSubscriptionType(value)}
fullWidth
sx={{ mb: 2 }}
>
<ToggleButton value={SubscriptionType.Monthly}>
<Box sx={{ py: 1, textAlign: "center" }}>
<MonthlyIcon sx={{ mb: 0.5 }} />
<Typography variant="body2" display="block">
Mensile
</Typography>
<Typography variant="h6" color="primary">
{formatPrice(module.monthlyPrice)}
<Typography
component="span"
variant="body2"
color="text.secondary"
>
/mese
</Typography>
</Typography>
</Box>
</ToggleButton>
<ToggleButton value={SubscriptionType.Annual}>
<Box sx={{ py: 1, textAlign: "center" }}>
<AnnualIcon sx={{ mb: 0.5 }} />
<Typography variant="body2" display="block">
Annuale
</Typography>
<Typography variant="h6" color="primary">
{formatPrice(module.basePrice)}
<Typography
component="span"
variant="body2"
color="text.secondary"
>
/anno
</Typography>
</Typography>
{savingsPercent > 0 && (
<Chip
label={`Risparmi ${savingsPercent}%`}
size="small"
color="success"
sx={{ mt: 0.5 }}
/>
)}
</Box>
</ToggleButton>
</ToggleButtonGroup>
{/* Opzione auto-rinnovo */}
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
<ToggleButton
value="autoRenew"
selected={autoRenew}
onChange={() => setAutoRenew(!autoRenew)}
size="small"
>
{autoRenew ? <CheckIcon /> : null}
</ToggleButton>
<Typography variant="body2">
Rinnovo automatico alla scadenza
</Typography>
</Box>
</Box>
<Divider sx={{ my: 3 }} />
{/* Riepilogo */}
<Paper
variant="outlined"
sx={{ p: 2, mb: 3, bgcolor: "action.hover" }}
>
<Typography variant="subtitle2" gutterBottom>
Riepilogo ordine
</Typography>
<Box
sx={{ display: "flex", justifyContent: "space-between", mb: 1 }}
>
<Typography>Modulo {module.name}</Typography>
<Typography>{formatPrice(price)}</Typography>
</Box>
<Box
sx={{ display: "flex", justifyContent: "space-between", mb: 1 }}
>
<Typography color="text.secondary">
Abbonamento{" "}
{getSubscriptionTypeName(subscriptionType).toLowerCase()}
</Typography>
<Typography color="text.secondary">{priceLabel}</Typography>
</Box>
<Divider sx={{ my: 1 }} />
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
<Typography variant="h6">Totale</Typography>
<Typography variant="h6" color="primary">
{formatPrice(price)}
{priceLabel}
</Typography>
</Box>
</Paper>
{/* Errore */}
{enableMutation.isError && (
<Alert severity="error" sx={{ mb: 3 }}>
{(enableMutation.error as Error)?.message ||
"Errore durante l'attivazione del modulo"}
</Alert>
)}
{/* Pulsante attivazione */}
<Button
variant="contained"
size="large"
fullWidth
startIcon={
enableMutation.isPending ? (
<CircularProgress size={20} color="inherit" />
) : (
<CartIcon />
)
}
onClick={() => enableMutation.mutate()}
disabled={
enableMutation.isPending || missingDependencies.length > 0
}
>
{enableMutation.isPending
? "Attivazione in corso..."
: "Attiva Modulo"}
</Button>
{/* Note */}
<Typography
variant="caption"
color="text.secondary"
display="block"
textAlign="center"
sx={{ mt: 2 }}
>
Potrai disattivare il modulo in qualsiasi momento dalle
impostazioni. I dati inseriti rimarranno disponibili.
</Typography>
</CardContent>
</Card>
{/* Funzionalità incluse */}
<Card sx={{ mt: 3 }}>
<CardContent>
<Typography variant="h6" gutterBottom>
Funzionalità incluse
</Typography>
<List dense>
{getModuleFeatures(module.code).map((feature, index) => (
<ListItem key={index}>
<ListItemIcon sx={{ minWidth: 36 }}>
<CheckIcon color="success" fontSize="small" />
</ListItemIcon>
<ListItemText primary={feature} />
</ListItem>
))}
</List>
</CardContent>
</Card>
</Box>
);
}
// Helper per ottenere le funzionalità di un modulo
function getModuleFeatures(code: string): string[] {
const features: Record<string, string[]> = {
warehouse: [
"Gestione anagrafica articoli",
"Movimenti di magazzino (carico/scarico)",
"Giacenze in tempo reale",
"Valorizzazione scorte (FIFO, LIFO, medio ponderato)",
"Inventario e rettifiche",
"Report giacenze e movimenti",
],
purchases: [
"Gestione ordini a fornitore",
"DDT di entrata",
"Fatture passive",
"Scadenziario pagamenti",
"Analisi acquisti per fornitore/articolo",
"Storico prezzi di acquisto",
],
sales: [
"Gestione ordini cliente",
"DDT di uscita",
"Fatturazione elettronica",
"Scadenziario incassi",
"Analisi vendite per cliente/articolo",
"Listini prezzi",
],
production: [
"Distinte base multilivello",
"Cicli di lavoro",
"Ordini di produzione",
"Pianificazione MRP",
"Avanzamento produzione",
"Costi di produzione",
],
quality: [
"Piani di controllo",
"Registrazione controlli",
"Gestione non conformità",
"Azioni correttive/preventive",
"Certificazioni e audit",
"Statistiche qualità",
],
};
return features[code] || ["Funzionalità complete del modulo"];
}