add progress bar in list view and fix translations

This commit is contained in:
Yinyin Liu 2026-04-22 16:30:57 +02:00
parent 7e72940eae
commit faec16f6fe
12 changed files with 388 additions and 23 deletions

View File

@ -2864,4 +2864,24 @@ public class Controller : ControllerBase
}
}
[HttpGet(nameof(GetChecklistSummary))]
public ActionResult<IEnumerable<Object>> GetChecklistSummary(Token authToken)
{
var user = Db.GetSession(authToken)?.User;
if (user is null || user.UserType != 2) return Unauthorized();
var summaries = Db.ChecklistItems
.ToList()
.GroupBy(c => c.InstallationId)
.Select(g => new
{
installationId = g.Key,
done = g.Count(c => c.Status == (Int32)ChecklistStatus.Done),
total = g.Count()
})
.ToList();
return Ok(summaries);
}
}

View File

@ -15,30 +15,30 @@ public static class ChecklistStepDefinitions
new( 5, "Information tab filled out (customer, serials, VPN)",
"""
[
{"text":"Customer information (email, address)","checked":false},
{"text":"Installation information (external EMS, grid provider, data collection)","checked":false},
{"text":"Battery serial number","checked":false},
{"text":"Inverter serial number","checked":false},
{"text":"Data logger serial number","checked":false},
{"text":"VPN details","checked":false}
{"text":"checklistStep5Sub1","checked":false},
{"text":"checklistStep5Sub2","checked":false},
{"text":"checklistStep5Sub3","checked":false},
{"text":"checklistStep5Sub4","checked":false},
{"text":"checklistStep5Sub5","checked":false},
{"text":"checklistStep5Sub6","checked":false}
]
"""),
new( 6, "Installation configured and tested electrically / hardware-wise at Vebo",
"""
[
{"text":"Inverter firmware and configuration verified","checked":false},
{"text":"Battery firmware and configuration verified","checked":false},
{"text":"Internet for gateway configured","checked":false},
{"text":"Communication cable between gateway and inverter correct","checked":false}
{"text":"checklistStep6Sub1","checked":false},
{"text":"checklistStep6Sub2","checked":false},
{"text":"checklistStep6Sub3","checked":false},
{"text":"checklistStep6Sub4","checked":false}
]
"""),
new( 7, "Installation tested software-wise at Vebo",
"""
[
{"text":"S3 bucket number and key credentials copied from Information tab into config.json","checked":false},
{"text":"Product ID configured in config.json","checked":false},
{"text":"USB ID configured in config.json","checked":false},
{"text":"Inverter data reading from inverter tested","checked":false}
{"text":"checklistStep7Sub1","checked":false},
{"text":"checklistStep7Sub2","checked":false},
{"text":"checklistStep7Sub3","checked":false},
{"text":"checklistStep7Sub4","checked":false}
]
"""),
new( 8, "Installation delivered to customer site", NoSubtasks),

View File

@ -109,6 +109,33 @@ public static partial class Db
Connection.Execute("UPDATE User SET Name = 'inesco energy Master Admin' WHERE Name = 'InnovEnergy Master Admin'");
Connection.Execute("UPDATE User SET Name = 'inesco energy Master Admin' WHERE Name = 'inesco Energy Master Admin'");
// One-time migration: rewrite early-seeded English subtask text to translation keys so the
// frontend can localize them. Idempotent: rows already containing keys match nothing.
var subtaskTextToKey = new (String Old, String Key)[]
{
("Customer information (email, address)", "checklistStep5Sub1"),
("Installation information (external EMS, grid provider, data collection)", "checklistStep5Sub2"),
("Installation information (external EMS, network provider, data collection)", "checklistStep5Sub2"),
("Battery serial number", "checklistStep5Sub3"),
("Inverter serial number", "checklistStep5Sub4"),
("Data logger serial number", "checklistStep5Sub5"),
("VPN details", "checklistStep5Sub6"),
("Inverter firmware and configuration verified", "checklistStep6Sub1"),
("Battery firmware and configuration verified", "checklistStep6Sub2"),
("Internet for gateway configured", "checklistStep6Sub3"),
("Communication cable between gateway and inverter correct", "checklistStep6Sub4"),
("S3 bucket number and key credentials copied from Information tab into config.json","checklistStep7Sub1"),
("Product ID configured in config.json", "checklistStep7Sub2"),
("USB ID configured in config.json", "checklistStep7Sub3"),
("Inverter data reading from inverter tested", "checklistStep7Sub4")
};
foreach (var (oldText, key) in subtaskTextToKey)
{
Connection.Execute(
"UPDATE ChecklistItem SET Subtasks = REPLACE(Subtasks, ?, ?) WHERE Subtasks LIKE ?",
$"\"{oldText}\"", $"\"{key}\"", $"%\"{oldText}\"%");
}
//UpdateKeys();
CleanupSessions().SupressAwaitWarning();
DeleteSnapshots().SupressAwaitWarning();

View File

@ -144,7 +144,12 @@ function ChecklistStepRow({ item, adminUsers, onUpdate, onNotify }: Props) {
<TableCell sx={{ verticalAlign: 'top', minWidth: 240 }}>
<Stack direction="row" spacing={1} alignItems="center">
<Typography variant="body2">{item.stepTitle}</Typography>
<Typography variant="body2">
{intl.formatMessage({
id: `checklistStep${item.stepNumber}`,
defaultMessage: item.stepTitle
})}
</Typography>
{subtasks.length > 0 && (
<>
<Chip
@ -275,7 +280,7 @@ function ChecklistStepRow({ item, adminUsers, onUpdate, onNotify }: Props) {
{subtasks.length > 0 && subtasksOpen && (
<TableRow>
<TableCell colSpan={6} sx={{ backgroundColor: '#fafafa', py: 1 }}>
<Box pl={6}>
<Box pl={6} display="flex" flexDirection="column">
{subtasks.map((s, i) => (
<FormControlLabel
key={i}
@ -286,8 +291,12 @@ function ChecklistStepRow({ item, adminUsers, onUpdate, onNotify }: Props) {
onChange={() => handleSubtaskToggle(i)}
/>
}
label={<Typography variant="body2">{s.text}</Typography>}
sx={{ display: 'block', ml: 0 }}
label={
<Typography variant="body2" component="span">
{intl.formatMessage({ id: s.text, defaultMessage: s.text })}
</Typography>
}
sx={{ ml: 0 }}
/>
))}
</Box>

View File

@ -0,0 +1,56 @@
import React from 'react';
import { Box, LinearProgress, Tooltip, Typography } from '@mui/material';
import { FormattedMessage, useIntl } from 'react-intl';
interface Props {
done: number | null;
total: number | null;
}
function phaseId(done: number, total: number): string {
if (total === 0) return 'checklistPhaseEmpty';
if (done >= total) return 'checklistPhaseComplete';
if (done <= 5) return 'checklistPhasePreparation';
if (done <= 12) return 'checklistPhaseOnSite';
return 'checklistPhaseHandover';
}
function SetupProgress({ done, total }: Props) {
const intl = useIntl();
if (done === null || total === null || total === 0) {
return (
<Typography variant="body2" color="text.secondary" sx={{ fontSize: 'small' }}>
--
</Typography>
);
}
const percent = Math.round((done / total) * 100);
const color = percent >= 100 ? 'success' : percent > 0 ? 'warning' : 'inherit';
const tooltip = intl.formatMessage({
id: phaseId(done, total),
defaultMessage: 'Progress'
});
return (
<Tooltip title={tooltip}>
<Box display="flex" alignItems="center" sx={{ minWidth: 120 }}>
<Box sx={{ width: 70, mr: 1 }}>
<LinearProgress
variant="determinate"
value={percent}
color={color === 'inherit' ? 'primary' : (color as any)}
sx={{ height: 6, borderRadius: 3 }}
/>
</Box>
<Typography variant="body2" sx={{ fontSize: 'small', whiteSpace: 'nowrap' }}>
{done}/{total}
</Typography>
</Box>
</Tooltip>
);
}
export default SetupProgress;

View File

@ -27,6 +27,14 @@ import { useLocation, useNavigate } from 'react-router-dom';
import routes from '../../../Resources/routes.json';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
import { UserContext } from 'src/contexts/userContext';
import { UserType } from 'src/interfaces/UserTypes';
import axiosConfig from 'src/Resources/axiosConfig';
import {
CHECKLIST_ENABLED_PRODUCTS,
ChecklistSummary
} from 'src/interfaces/ChecklistTypes';
import SetupProgress from '../Checklist/SetupProgress';
interface FlatInstallationViewProps {
installations: I_Installation[];
@ -46,6 +54,28 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
const [searchTerm, setSearchTerm] = useState('');
const [sortByStatus, setSortByStatus] = useState('All Installations');
const [sortByAction, setSortByAction] = useState('All Installations');
const { currentUser } = useContext(UserContext);
const showChecklistColumn =
currentUser?.userType === UserType.admin &&
CHECKLIST_ENABLED_PRODUCTS.has(product ?? -1);
const [progressMap, setProgressMap] = useState<Map<number, ChecklistSummary>>(
new Map()
);
useEffect(() => {
if (!showChecklistColumn) return;
axiosConfig
.get('/GetChecklistSummary')
.then((res) => {
if (!Array.isArray(res.data)) return;
const map = new Map<number, ChecklistSummary>();
res.data.forEach((s: ChecklistSummary) => {
map.set(s.installationId, s);
});
setProgressMap(map);
})
.catch(() => setProgressMap(new Map()));
}, [showChecklistColumn]);
const HoverableTableRow = styled(TableRow)(({ theme }) => ({
cursor: 'pointer',
@ -315,6 +345,14 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
<TableCell>
<FormattedMessage id="status" defaultMessage="Status" />
</TableCell>
{showChecklistColumn && (
<TableCell>
<FormattedMessage
id="setupProgress"
defaultMessage="Setup Progress"
/>
</TableCell>
)}
</TableRow>
</TableHead>
<TableBody>
@ -461,6 +499,19 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
)}
</div>
</TableCell>
{showChecklistColumn && (
<TableCell>
{(() => {
const summary = progressMap.get(installation.id);
return (
<SetupProgress
done={summary?.done ?? 0}
total={summary?.total ?? 16}
/>
);
})()}
</TableCell>
)}
</TableRow>
);
})}

View File

@ -1,4 +1,4 @@
import React, { useMemo, useState } from 'react';
import React, { useContext, useEffect, useMemo, useState } from 'react';
import {
Card,
CircularProgress,
@ -20,6 +20,14 @@ import routes from '../../../Resources/routes.json';
import CancelIcon from '@mui/icons-material/Cancel';
import BuildIcon from '@mui/icons-material/Build';
import { getDeviceTypeName } from '../Information/installationSetupUtils';
import { UserContext } from 'src/contexts/userContext';
import { UserType } from 'src/interfaces/UserTypes';
import axiosConfig from 'src/Resources/axiosConfig';
import {
CHECKLIST_ENABLED_PRODUCTS,
ChecklistSummary
} from 'src/interfaces/ChecklistTypes';
import SetupProgress from '../Checklist/SetupProgress';
interface FlatInstallationViewProps {
installations: I_Installation[];
@ -32,6 +40,29 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
const currentLocation = useLocation();
const baseRoute = props.product === 5 ? routes.sodistorepro_installations : routes.sodiohome_installations;
const { currentUser } = useContext(UserContext);
const showChecklistColumn =
currentUser?.userType === UserType.admin &&
CHECKLIST_ENABLED_PRODUCTS.has(props.product ?? -1);
const [progressMap, setProgressMap] = useState<Map<number, ChecklistSummary>>(
new Map()
);
useEffect(() => {
if (!showChecklistColumn) return;
axiosConfig
.get('/GetChecklistSummary')
.then((res) => {
if (!Array.isArray(res.data)) return;
const map = new Map<number, ChecklistSummary>();
res.data.forEach((s: ChecklistSummary) => {
map.set(s.installationId, s);
});
setProgressMap(map);
})
.catch(() => setProgressMap(new Map()));
}, [showChecklistColumn]);
const sortedInstallations = useMemo(() => {
return [...props.installations].sort((a, b) => {
// Data-collection-disabled installations sink below everything (even offline).
@ -118,6 +149,14 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
<TableCell>
<FormattedMessage id="status" defaultMessage="Status" />
</TableCell>
{showChecklistColumn && (
<TableCell>
<FormattedMessage
id="setupProgress"
defaultMessage="Setup Progress"
/>
</TableCell>
)}
</TableRow>
</TableHead>
<TableBody>
@ -282,6 +321,19 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
)}
</div>
</TableCell>
{showChecklistColumn && (
<TableCell>
{(() => {
const summary = progressMap.get(installation.id);
return (
<SetupProgress
done={summary?.done ?? 0}
total={summary?.total ?? 16}
/>
);
})()}
</TableCell>
)}
</HoverableTableRow>
);
})}

View File

@ -25,6 +25,12 @@ export type ChecklistItem = {
export const CHECKLIST_ENABLED_PRODUCTS: ReadonlySet<number> = new Set([2, 4, 5]);
export type ChecklistSummary = {
installationId: number;
done: number;
total: number;
};
export function parseSubtasks(raw: string | null | undefined): ChecklistSubtask[] {
if (!raw) return [];
try {

View File

@ -712,5 +712,41 @@
"checklistCommentsPlaceholder": "Notizen, Kontaktinformationen, Beobachtungen…",
"checklistEmailSent": "E-Mail an {name} gesendet",
"checklistEmailFailed": "E-Mail-Versand fehlgeschlagen — bitte erneut versuchen",
"checklistSaveFailed": "Änderung konnte nicht gespeichert werden"
"checklistSaveFailed": "Änderung konnte nicht gespeichert werden",
"checklistStep1": "Auftrag erstellt, Kunden- und Partnerinformationen im CRM erfasst",
"checklistStep2": "Hardware bei Vebo zusammengebaut",
"checklistStep3": "Installation auf Monitor unter korrektem Produkt und Ordner erstellt",
"checklistStep4": "Gateway-SD-Karte konfiguriert, VPN und Gateway-Name registriert",
"checklistStep5": "Informations-Tab ausgefüllt (Kunde, Seriennummern, VPN)",
"checklistStep6": "Installation bei Vebo elektrisch/hardwareseitig konfiguriert und getestet",
"checklistStep7": "Installation bei Vebo softwareseitig getestet",
"checklistStep8": "Installation am Kundenstandort ausgeliefert",
"checklistStep9": "Installation ans Netz angeschlossen",
"checklistStep10": "Hardware vor Ort verifiziert",
"checklistStep11": "Software vor Ort verifiziert",
"checklistStep12": "Installation online auf Monitor",
"checklistStep13": "Kunde über Monitor-Konto und Reports informiert",
"checklistStep14": "Benutzerkonto mit richtigen Ordnern und Zugriffen erstellt",
"checklistStep15": "Kundennachverfolgung abgeschlossen, Feedback eingeholt",
"checklistStep16": "Weitere Anliegen werden über das Ticket-System verfolgt",
"checklistStep5Sub1": "Kundeninformationen (E-Mail, Adresse)",
"checklistStep5Sub2": "Installationsinformationen (externes EMS, Stromanbieter, Datenerfassung)",
"checklistStep5Sub3": "Batterie-Seriennummer",
"checklistStep5Sub4": "Wechselrichter-Seriennummer",
"checklistStep5Sub5": "Datenlogger-Seriennummer",
"checklistStep5Sub6": "VPN",
"checklistStep6Sub1": "Wechselrichter-Firmware und Konfiguration geprüft",
"checklistStep6Sub2": "Batterie-Firmware und Konfiguration geprüft",
"checklistStep6Sub3": "Internet für Gateway konfiguriert",
"checklistStep6Sub4": "Kommunikationskabel zwischen Gateway und Wechselrichter korrekt",
"checklistStep7Sub1": "S3-Bucket-Nummer und Schlüssel-Credentials aus Informations-Tab in config.json übernommen",
"checklistStep7Sub2": "Produkt-ID in config.json konfiguriert",
"checklistStep7Sub3": "USB-ID in config.json konfiguriert",
"checklistStep7Sub4": "Datenlesung vom Wechselrichter getestet",
"setupProgress": "Setup-Fortschritt",
"checklistPhaseEmpty": "Nicht gestartet",
"checklistPhasePreparation": "Vorbereitung",
"checklistPhaseOnSite": "Vor Ort",
"checklistPhaseHandover": "Kundenübergabe",
"checklistPhaseComplete": "Abgeschlossen"
}

View File

@ -460,5 +460,41 @@
"checklistCommentsPlaceholder": "Notes, contact info, observations…",
"checklistEmailSent": "Email sent to {name}",
"checklistEmailFailed": "Failed to send email — try again",
"checklistSaveFailed": "Failed to save change"
"checklistSaveFailed": "Failed to save change",
"checklistStep1": "Order created, customer and partner info recorded in CRM",
"checklistStep2": "Hardware assembled at Vebo",
"checklistStep3": "Installation created on Monitor under correct product and folder",
"checklistStep4": "Gateway SD card configured, VPN and gateway name registered",
"checklistStep5": "Information tab filled out (customer, serials, VPN)",
"checklistStep6": "Installation configured and tested electrically / hardware-wise at Vebo",
"checklistStep7": "Installation tested software-wise at Vebo",
"checklistStep8": "Installation delivered to customer site",
"checklistStep9": "Installation connected to grid",
"checklistStep10": "Hardware verified on site",
"checklistStep11": "Software verified on site",
"checklistStep12": "Installation online on Monitor",
"checklistStep13": "Customer informed about Monitor account and reports",
"checklistStep14": "User account created with correct folders and access",
"checklistStep15": "Customer follow-up completed, feedback collected",
"checklistStep16": "Further issues tracked via Ticket system",
"checklistStep5Sub1": "Customer information (email, address)",
"checklistStep5Sub2": "Installation information (external EMS, grid provider, data collection)",
"checklistStep5Sub3": "Battery serial number",
"checklistStep5Sub4": "Inverter serial number",
"checklistStep5Sub5": "Data logger serial number",
"checklistStep5Sub6": "VPN",
"checklistStep6Sub1": "Inverter firmware and configuration verified",
"checklistStep6Sub2": "Battery firmware and configuration verified",
"checklistStep6Sub3": "Internet for gateway configured",
"checklistStep6Sub4": "Communication cable between gateway and inverter correct",
"checklistStep7Sub1": "S3 bucket number and key credentials copied from Information tab into config.json",
"checklistStep7Sub2": "Product ID configured in config.json",
"checklistStep7Sub3": "USB ID configured in config.json",
"checklistStep7Sub4": "Inverter data reading from inverter tested",
"setupProgress": "Setup Progress",
"checklistPhaseEmpty": "Not started",
"checklistPhasePreparation": "Preparation",
"checklistPhaseOnSite": "On-site",
"checklistPhaseHandover": "Customer handover",
"checklistPhaseComplete": "Complete"
}

View File

@ -712,5 +712,41 @@
"checklistCommentsPlaceholder": "Notes, coordonnées, observations…",
"checklistEmailSent": "E-mail envoyé à {name}",
"checklistEmailFailed": "Échec de l'envoi — veuillez réessayer",
"checklistSaveFailed": "Échec de l'enregistrement"
"checklistSaveFailed": "Échec de l'enregistrement",
"checklistStep1": "Commande créée, informations client et partenaire enregistrées dans le CRM",
"checklistStep2": "Matériel assemblé chez Vebo",
"checklistStep3": "Installation créée sur Monitor sous le bon produit et dossier",
"checklistStep4": "Carte SD du gateway configurée, VPN et nom du gateway enregistrés",
"checklistStep5": "Onglet Informations rempli (client, numéros de série, VPN)",
"checklistStep6": "Installation configurée et testée électriquement/matériel chez Vebo",
"checklistStep7": "Installation testée côté logiciel chez Vebo",
"checklistStep8": "Installation livrée sur le site du client",
"checklistStep9": "Installation raccordée au réseau",
"checklistStep10": "Matériel vérifié sur site",
"checklistStep11": "Logiciel vérifié sur site",
"checklistStep12": "Installation en ligne sur Monitor",
"checklistStep13": "Client informé du compte Monitor et des rapports",
"checklistStep14": "Compte utilisateur créé avec les dossiers et accès corrects",
"checklistStep15": "Suivi client effectué, retour recueilli",
"checklistStep16": "Problèmes ultérieurs suivis via le système de tickets",
"checklistStep5Sub1": "Informations client (e-mail, adresse)",
"checklistStep5Sub2": "Informations d'installation (EMS externe, fournisseur réseau, collecte de données)",
"checklistStep5Sub3": "Numéro de série de la batterie",
"checklistStep5Sub4": "Numéro de série de l'onduleur",
"checklistStep5Sub5": "Numéro de série de l'enregistreur de données",
"checklistStep5Sub6": "VPN",
"checklistStep6Sub1": "Firmware et configuration de l'onduleur vérifiés",
"checklistStep6Sub2": "Firmware et configuration de la batterie vérifiés",
"checklistStep6Sub3": "Internet pour le gateway configuré",
"checklistStep6Sub4": "Câble de communication entre gateway et onduleur correct",
"checklistStep7Sub1": "Numéro de bucket S3 et identifiants copiés depuis l'onglet Informations dans config.json",
"checklistStep7Sub2": "ID produit configuré dans config.json",
"checklistStep7Sub3": "ID USB configuré dans config.json",
"checklistStep7Sub4": "Lecture des données de l'onduleur testée",
"setupProgress": "Progression installation",
"checklistPhaseEmpty": "Non commencé",
"checklistPhasePreparation": "Préparation",
"checklistPhaseOnSite": "Sur site",
"checklistPhaseHandover": "Transfert client",
"checklistPhaseComplete": "Terminé"
}

View File

@ -712,5 +712,41 @@
"checklistCommentsPlaceholder": "Note, contatti, osservazioni…",
"checklistEmailSent": "E-mail inviata a {name}",
"checklistEmailFailed": "Invio e-mail non riuscito — riprovare",
"checklistSaveFailed": "Salvataggio non riuscito"
"checklistSaveFailed": "Salvataggio non riuscito",
"checklistStep1": "Ordine creato, informazioni cliente e partner registrate nel CRM",
"checklistStep2": "Hardware assemblato presso Vebo",
"checklistStep3": "Installazione creata su Monitor sotto il prodotto e la cartella corretti",
"checklistStep4": "Scheda SD del gateway configurata, VPN e nome gateway registrati",
"checklistStep5": "Scheda Informazioni compilata (cliente, seriali, VPN)",
"checklistStep6": "Installazione configurata e testata elettricamente/hardware presso Vebo",
"checklistStep7": "Installazione testata a livello software presso Vebo",
"checklistStep8": "Installazione consegnata presso il sito del cliente",
"checklistStep9": "Installazione collegata alla rete",
"checklistStep10": "Hardware verificato in sito",
"checklistStep11": "Software verificato in sito",
"checklistStep12": "Installazione online su Monitor",
"checklistStep13": "Cliente informato su account Monitor e report",
"checklistStep14": "Account utente creato con cartelle e accessi corretti",
"checklistStep15": "Follow-up cliente completato, feedback raccolto",
"checklistStep16": "Ulteriori problemi tracciati tramite il sistema di ticket",
"checklistStep5Sub1": "Informazioni cliente (e-mail, indirizzo)",
"checklistStep5Sub2": "Informazioni installazione (EMS esterno, fornitore di rete, raccolta dati)",
"checklistStep5Sub3": "Numero di serie batteria",
"checklistStep5Sub4": "Numero di serie inverter",
"checklistStep5Sub5": "Numero di serie data logger",
"checklistStep5Sub6": "VPN",
"checklistStep6Sub1": "Firmware e configurazione inverter verificati",
"checklistStep6Sub2": "Firmware e configurazione batteria verificati",
"checklistStep6Sub3": "Internet per gateway configurato",
"checklistStep6Sub4": "Cavo di comunicazione tra gateway e inverter corretto",
"checklistStep7Sub1": "Numero bucket S3 e credenziali copiati dalla scheda Informazioni in config.json",
"checklistStep7Sub2": "ID prodotto configurato in config.json",
"checklistStep7Sub3": "ID USB configurato in config.json",
"checklistStep7Sub4": "Lettura dati inverter testata",
"setupProgress": "Avanzamento installazione",
"checklistPhaseEmpty": "Non avviato",
"checklistPhasePreparation": "Preparazione",
"checklistPhaseOnSite": "In sito",
"checklistPhaseHandover": "Consegna cliente",
"checklistPhaseComplete": "Completato"
}