|
|
|
@@ -18,6 +18,7 @@ type ToastKey = "config" | "order" | "downtime" | "scrap";
|
|
|
|
type ModuleKey = "atelier" | "machine" | "trs" | "orders" | "downtimes" | "scrap" | "config";
|
|
|
|
type ModuleKey = "atelier" | "machine" | "trs" | "orders" | "downtimes" | "scrap" | "config";
|
|
|
|
type FormErrors = Record<ToastKey, Record<string, string>>;
|
|
|
|
type FormErrors = Record<ToastKey, Record<string, string>>;
|
|
|
|
type FormControl = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
|
|
type FormControl = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
|
|
|
|
|
|
|
|
type Language = "en" | "fr";
|
|
|
|
|
|
|
|
|
|
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
|
|
const initialFormErrors: FormErrors = {
|
|
|
|
const initialFormErrors: FormErrors = {
|
|
|
|
@@ -27,15 +28,15 @@ const initialFormErrors: FormErrors = {
|
|
|
|
scrap: {},
|
|
|
|
scrap: {},
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const moduleTabs: Array<{ key: ModuleKey; label: string; description: string }> = [
|
|
|
|
function FieldLabel({ label, error, children }: { label: string; error?: ReactNode; children: ReactNode }) {
|
|
|
|
{ key: "atelier", label: "Atelier", description: "Vue machines" },
|
|
|
|
return (
|
|
|
|
{ key: "machine", label: "Machine", description: "Cycles et evenements" },
|
|
|
|
<label className="form-field">
|
|
|
|
{ key: "trs", label: "TRS", description: "Performance jour" },
|
|
|
|
<span className="input-label">{label}</span>
|
|
|
|
{ key: "orders", label: "OF", description: "Ordres fabrication" },
|
|
|
|
{children}
|
|
|
|
{ key: "downtimes", label: "Arrets", description: "Qualification" },
|
|
|
|
{error}
|
|
|
|
{ key: "scrap", label: "Rebuts", description: "Declaration" },
|
|
|
|
</label>
|
|
|
|
{ key: "config", label: "Config", description: "Parametrage signal" },
|
|
|
|
);
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default function App() {
|
|
|
|
export default function App() {
|
|
|
|
const [dashboard, setDashboard] = useState<DashboardSnapshot | null>(null);
|
|
|
|
const [dashboard, setDashboard] = useState<DashboardSnapshot | null>(null);
|
|
|
|
@@ -47,6 +48,7 @@ export default function App() {
|
|
|
|
const [dailyOee, setDailyOee] = useState<DailyOee | null>(null);
|
|
|
|
const [dailyOee, setDailyOee] = useState<DailyOee | null>(null);
|
|
|
|
const [selectedMachineId, setSelectedMachineId] = useState<number | null>(null);
|
|
|
|
const [selectedMachineId, setSelectedMachineId] = useState<number | null>(null);
|
|
|
|
const [selectedOrderId, setSelectedOrderId] = useState<number | null>(null);
|
|
|
|
const [selectedOrderId, setSelectedOrderId] = useState<number | null>(null);
|
|
|
|
|
|
|
|
const [language, setLanguage] = useState<Language>(() => (localStorage.getItem("plast-track-language") as Language | null) ?? "en");
|
|
|
|
const [activeModule, setActiveModule] = useState<ModuleKey>("atelier");
|
|
|
|
const [activeModule, setActiveModule] = useState<ModuleKey>("atelier");
|
|
|
|
const [machineFilter, setMachineFilter] = useState("all");
|
|
|
|
const [machineFilter, setMachineFilter] = useState("all");
|
|
|
|
const [machineSort, setMachineSort] = useState("code");
|
|
|
|
const [machineSort, setMachineSort] = useState("code");
|
|
|
|
@@ -71,6 +73,20 @@ export default function App() {
|
|
|
|
const refreshTimer = useRef<number | null>(null);
|
|
|
|
const refreshTimer = useRef<number | null>(null);
|
|
|
|
const toastTimers = useRef<Record<string, number>>({});
|
|
|
|
const toastTimers = useRef<Record<string, number>>({});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function t(en: string, fr: string) {
|
|
|
|
|
|
|
|
return language === "fr" ? fr : en;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const moduleTabs: Array<{ key: ModuleKey; label: string; description: string }> = [
|
|
|
|
|
|
|
|
{ key: "atelier", label: t("Workshop", "Atelier"), description: t("Machines view", "Vue machines") },
|
|
|
|
|
|
|
|
{ key: "machine", label: "Machine", description: t("Cycles and events", "Cycles et evenements") },
|
|
|
|
|
|
|
|
{ key: "trs", label: "OEE/TRS", description: t("Daily performance", "Performance jour") },
|
|
|
|
|
|
|
|
{ key: "orders", label: t("Orders", "OF"), description: t("Production orders", "Ordres fabrication") },
|
|
|
|
|
|
|
|
{ key: "downtimes", label: t("Stops", "Arrets"), description: "Qualification" },
|
|
|
|
|
|
|
|
{ key: "scrap", label: t("Scrap", "Rebuts"), description: t("Declaration", "Declaration") },
|
|
|
|
|
|
|
|
{ key: "config", label: "Config", description: t("Signal setup", "Parametrage signal") },
|
|
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
async function loadDashboard() {
|
|
|
|
async function loadDashboard() {
|
|
|
|
const snapshot = await fetchJson<DashboardSnapshot>("/api/dashboard");
|
|
|
|
const snapshot = await fetchJson<DashboardSnapshot>("/api/dashboard");
|
|
|
|
setDashboard(snapshot);
|
|
|
|
setDashboard(snapshot);
|
|
|
|
@@ -128,6 +144,10 @@ export default function App() {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}, []);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
|
|
localStorage.setItem("plast-track-language", language);
|
|
|
|
|
|
|
|
}, [language]);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
useEffect(() => {
|
|
|
|
const timer = window.setInterval(() => setNowMs(Date.now()), 30000);
|
|
|
|
const timer = window.setInterval(() => setNowMs(Date.now()), 30000);
|
|
|
|
return () => window.clearInterval(timer);
|
|
|
|
return () => window.clearInterval(timer);
|
|
|
|
@@ -291,17 +311,17 @@ export default function App() {
|
|
|
|
form.reset();
|
|
|
|
form.reset();
|
|
|
|
setSelectedOrderId(createdOrder.id);
|
|
|
|
setSelectedOrderId(createdOrder.id);
|
|
|
|
setFormErrors((current) => ({ ...current, order: {} }));
|
|
|
|
setFormErrors((current) => ({ ...current, order: {} }));
|
|
|
|
showToast("order", "Production order created.");
|
|
|
|
showToast("order", t("Production order created.", "Ordre de fabrication cree."));
|
|
|
|
await loadAll();
|
|
|
|
await loadAll();
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
setActionErrors((current) => ({ ...current, order: error instanceof Error ? error.message : "Order creation failed." }));
|
|
|
|
setActionErrors((current) => ({ ...current, order: error instanceof Error ? error.message : t("Order creation failed.", "Creation OF echouee.") }));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function submitOrderUpdate(event: FormEvent<HTMLFormElement>) {
|
|
|
|
async function submitOrderUpdate(event: FormEvent<HTMLFormElement>) {
|
|
|
|
event.preventDefault();
|
|
|
|
event.preventDefault();
|
|
|
|
if (!selectedOrder) {
|
|
|
|
if (!selectedOrder) {
|
|
|
|
setActionErrors((current) => ({ ...current, order: "Select an OF before updating." }));
|
|
|
|
setActionErrors((current) => ({ ...current, order: t("Select an OF before updating.", "Selectionner un OF avant modification.") }));
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const form = event.currentTarget;
|
|
|
|
const form = event.currentTarget;
|
|
|
|
@@ -325,10 +345,10 @@ export default function App() {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
setFormErrors((current) => ({ ...current, order: {} }));
|
|
|
|
setFormErrors((current) => ({ ...current, order: {} }));
|
|
|
|
showToast("order", "Production order updated.");
|
|
|
|
showToast("order", t("Production order updated.", "Ordre de fabrication modifie."));
|
|
|
|
await loadAll();
|
|
|
|
await loadAll();
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
setActionErrors((current) => ({ ...current, order: error instanceof Error ? error.message : "Order update failed." }));
|
|
|
|
setActionErrors((current) => ({ ...current, order: error instanceof Error ? error.message : t("Order update failed.", "Modification OF echouee.") }));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -350,20 +370,20 @@ export default function App() {
|
|
|
|
|
|
|
|
|
|
|
|
form.reset();
|
|
|
|
form.reset();
|
|
|
|
setFormErrors((current) => ({ ...current, downtime: {} }));
|
|
|
|
setFormErrors((current) => ({ ...current, downtime: {} }));
|
|
|
|
showToast("downtime", "Downtime qualified.");
|
|
|
|
showToast("downtime", t("Downtime qualified.", "Arret qualifie."));
|
|
|
|
await loadAll();
|
|
|
|
await loadAll();
|
|
|
|
if (selectedMachineId !== null) {
|
|
|
|
if (selectedMachineId !== null) {
|
|
|
|
await loadDetail(selectedMachineId);
|
|
|
|
await loadDetail(selectedMachineId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
setActionErrors((current) => ({ ...current, downtime: error instanceof Error ? error.message : "Downtime qualification failed." }));
|
|
|
|
setActionErrors((current) => ({ ...current, downtime: error instanceof Error ? error.message : t("Downtime qualification failed.", "Qualification arret echouee.") }));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function submitScrap(event: FormEvent<HTMLFormElement>) {
|
|
|
|
async function submitScrap(event: FormEvent<HTMLFormElement>) {
|
|
|
|
event.preventDefault();
|
|
|
|
event.preventDefault();
|
|
|
|
if (!selectedMachine) {
|
|
|
|
if (!selectedMachine) {
|
|
|
|
setActionErrors((current) => ({ ...current, scrap: "Select a machine before declaring scrap." }));
|
|
|
|
setActionErrors((current) => ({ ...current, scrap: t("Select a machine before declaring scrap.", "Selectionner une machine avant de declarer le rebut.") }));
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const form = event.currentTarget;
|
|
|
|
const form = event.currentTarget;
|
|
|
|
@@ -385,10 +405,10 @@ export default function App() {
|
|
|
|
|
|
|
|
|
|
|
|
form.reset();
|
|
|
|
form.reset();
|
|
|
|
setFormErrors((current) => ({ ...current, scrap: {} }));
|
|
|
|
setFormErrors((current) => ({ ...current, scrap: {} }));
|
|
|
|
showToast("scrap", "Scrap declaration recorded.");
|
|
|
|
showToast("scrap", t("Scrap declaration recorded.", "Declaration rebut enregistree."));
|
|
|
|
await loadAll();
|
|
|
|
await loadAll();
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
setActionErrors((current) => ({ ...current, scrap: error instanceof Error ? error.message : "Scrap declaration failed." }));
|
|
|
|
setActionErrors((current) => ({ ...current, scrap: error instanceof Error ? error.message : t("Scrap declaration failed.", "Declaration rebut echouee.") }));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -398,14 +418,14 @@ export default function App() {
|
|
|
|
await postJson(`/api/production-orders/${orderId}/${action}`);
|
|
|
|
await postJson(`/api/production-orders/${orderId}/${action}`);
|
|
|
|
await loadAll();
|
|
|
|
await loadAll();
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
setActionErrors((current) => ({ ...current, order: error instanceof Error ? error.message : "Order status change failed." }));
|
|
|
|
setActionErrors((current) => ({ ...current, order: error instanceof Error ? error.message : t("Order status change failed.", "Changement statut OF echoue.") }));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function submitMachineConfig(event: FormEvent<HTMLFormElement>) {
|
|
|
|
async function submitMachineConfig(event: FormEvent<HTMLFormElement>) {
|
|
|
|
event.preventDefault();
|
|
|
|
event.preventDefault();
|
|
|
|
if (!selectedMachine) {
|
|
|
|
if (!selectedMachine) {
|
|
|
|
setActionErrors((current) => ({ ...current, config: "Select a machine before saving configuration." }));
|
|
|
|
setActionErrors((current) => ({ ...current, config: t("Select a machine before saving configuration.", "Selectionner une machine avant d'enregistrer la configuration.") }));
|
|
|
|
return;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -430,11 +450,11 @@ export default function App() {
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
setFormErrors((current) => ({ ...current, config: {} }));
|
|
|
|
setFormErrors((current) => ({ ...current, config: {} }));
|
|
|
|
showToast("config", "Machine configuration saved.");
|
|
|
|
showToast("config", t("Machine configuration saved.", "Configuration machine enregistree."));
|
|
|
|
await loadAll();
|
|
|
|
await loadAll();
|
|
|
|
await loadDetail(selectedMachine.id);
|
|
|
|
await loadDetail(selectedMachine.id);
|
|
|
|
} catch (error) {
|
|
|
|
} catch (error) {
|
|
|
|
setActionErrors((current) => ({ ...current, config: error instanceof Error ? error.message : "Machine configuration failed." }));
|
|
|
|
setActionErrors((current) => ({ ...current, config: error instanceof Error ? error.message : t("Machine configuration failed.", "Configuration machine echouee.") }));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -468,7 +488,7 @@ export default function App() {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function downtimeOptionLabel(downtime: Downtime) {
|
|
|
|
function downtimeOptionLabel(downtime: Downtime) {
|
|
|
|
const state = downtime.end_time === null ? "Open" : "Ended";
|
|
|
|
const state = downtime.end_time === null ? t("Open", "Ouvert") : t("Ended", "Termine");
|
|
|
|
const parts = [`#${downtime.id}`, state, downtimeMachineLabel(downtime)];
|
|
|
|
const parts = [`#${downtime.id}`, state, downtimeMachineLabel(downtime)];
|
|
|
|
if (downtime.order_number) {
|
|
|
|
if (downtime.order_number) {
|
|
|
|
parts.push(downtime.order_number);
|
|
|
|
parts.push(downtime.order_number);
|
|
|
|
@@ -480,12 +500,12 @@ export default function App() {
|
|
|
|
if (downtime.reason_code) {
|
|
|
|
if (downtime.reason_code) {
|
|
|
|
return downtime.reason_code;
|
|
|
|
return downtime.reason_code;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return downtime.end_time === null ? "Open - waiting qualification" : "Ended - waiting qualification";
|
|
|
|
return downtime.end_time === null ? t("Open - waiting qualification", "Ouvert - en attente de qualification") : t("Ended - waiting qualification", "Termine - en attente de qualification");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function formatDurationSeconds(durationSec: number | null) {
|
|
|
|
function formatDurationSeconds(durationSec: number | null) {
|
|
|
|
if (durationSec === null) {
|
|
|
|
if (durationSec === null) {
|
|
|
|
return "In progress";
|
|
|
|
return t("In progress", "En cours");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (durationSec < 60) {
|
|
|
|
if (durationSec < 60) {
|
|
|
|
return `${durationSec} s`;
|
|
|
|
return `${durationSec} s`;
|
|
|
|
@@ -507,29 +527,45 @@ export default function App() {
|
|
|
|
return machines.find((machine) => machine.id === order.machine_id)?.code ?? `Machine ${order.machine_id}`;
|
|
|
|
return machines.find((machine) => machine.id === order.machine_id)?.code ?? `Machine ${order.machine_id}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function connectionLabel(status: string) {
|
|
|
|
|
|
|
|
if (status === "Connecting") {
|
|
|
|
|
|
|
|
return t("Connecting", "Connexion");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status === "Load failed") {
|
|
|
|
|
|
|
|
return t("Load failed", "Chargement echoue");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status === "Live") {
|
|
|
|
|
|
|
|
return t("Live", "Direct");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status === "Reconnecting") {
|
|
|
|
|
|
|
|
return t("Reconnecting", "Reconnexion");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function renderModule(): ReactNode {
|
|
|
|
function renderModule(): ReactNode {
|
|
|
|
if (activeModule === "atelier") {
|
|
|
|
if (activeModule === "atelier") {
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<Panel
|
|
|
|
<Panel
|
|
|
|
title="Dashboard Atelier"
|
|
|
|
title={t("Workshop dashboard", "Tableau atelier")}
|
|
|
|
subtitle="Machine status"
|
|
|
|
subtitle={t("Machine status", "Etat machines")}
|
|
|
|
actions={
|
|
|
|
actions={
|
|
|
|
<div className="panel-actions">
|
|
|
|
<div className="panel-actions">
|
|
|
|
<select value={machineFilter} onChange={(event) => setMachineFilter(event.target.value)} aria-label="Filter machines by status">
|
|
|
|
<select value={machineFilter} onChange={(event) => setMachineFilter(event.target.value)} aria-label={t("Filter machines by status", "Filtrer les machines par etat")}>
|
|
|
|
<option value="all">All statuses</option>
|
|
|
|
<option value="all">{t("All statuses", "Tous les etats")}</option>
|
|
|
|
{machineStatuses.map((status) => (
|
|
|
|
{machineStatuses.map((status) => (
|
|
|
|
<option key={status} value={status}>
|
|
|
|
<option key={status} value={status}>
|
|
|
|
{status}
|
|
|
|
{status}
|
|
|
|
</option>
|
|
|
|
</option>
|
|
|
|
))}
|
|
|
|
))}
|
|
|
|
</select>
|
|
|
|
</select>
|
|
|
|
<select value={machineSort} onChange={(event) => setMachineSort(event.target.value)} aria-label="Sort machines">
|
|
|
|
<select value={machineSort} onChange={(event) => setMachineSort(event.target.value)} aria-label={t("Sort machines", "Trier les machines")}>
|
|
|
|
<option value="code">Sort by code</option>
|
|
|
|
<option value="code">{t("Sort by code", "Trier par code")}</option>
|
|
|
|
<option value="oee">Sort by OEE</option>
|
|
|
|
<option value="oee">{t("Sort by OEE", "Trier par TRS")}</option>
|
|
|
|
<option value="stops">Stops first</option>
|
|
|
|
<option value="stops">{t("Stops first", "Arrets en premier")}</option>
|
|
|
|
</select>
|
|
|
|
</select>
|
|
|
|
<button type="button" onClick={() => setIsWorkshopMode((current) => !current)}>
|
|
|
|
<button type="button" onClick={() => setIsWorkshopMode((current) => !current)}>
|
|
|
|
{isWorkshopMode ? "Exit fullscreen" : "Fullscreen atelier"}
|
|
|
|
{isWorkshopMode ? t("Exit fullscreen", "Quitter plein ecran") : t("Fullscreen workshop", "Atelier plein ecran")}
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@@ -541,6 +577,7 @@ export default function App() {
|
|
|
|
machine={machine}
|
|
|
|
machine={machine}
|
|
|
|
selected={machine.id === selectedMachineId}
|
|
|
|
selected={machine.id === selectedMachineId}
|
|
|
|
nowMs={nowMs}
|
|
|
|
nowMs={nowMs}
|
|
|
|
|
|
|
|
language={language}
|
|
|
|
onSelect={() => selectMachine(machine.id, "machine")}
|
|
|
|
onSelect={() => selectMachine(machine.id, "machine")}
|
|
|
|
onCreateOrder={() => selectMachine(machine.id, "orders")}
|
|
|
|
onCreateOrder={() => selectMachine(machine.id, "orders")}
|
|
|
|
onDeclareScrap={() => selectMachine(machine.id, "scrap")}
|
|
|
|
onDeclareScrap={() => selectMachine(machine.id, "scrap")}
|
|
|
|
@@ -548,54 +585,54 @@ export default function App() {
|
|
|
|
/>
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
))}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{visibleMachines.length === 0 ? <div className="empty-state">No machines match the current filter.</div> : null}
|
|
|
|
{visibleMachines.length === 0 ? <div className="empty-state">{t("No machines match the current filter.", "Aucune machine ne correspond au filtre.")}</div> : null}
|
|
|
|
</Panel>
|
|
|
|
</Panel>
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (activeModule === "machine") {
|
|
|
|
if (activeModule === "machine") {
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<Panel title="Detail Machine" subtitle={selectedMachine ? selectedMachine.code : "Select a machine"}>
|
|
|
|
<Panel title={t("Machine detail", "Detail machine")} subtitle={selectedMachine ? selectedMachine.code : t("Select a machine", "Selectionner une machine")}>
|
|
|
|
{selectedMachine ? (
|
|
|
|
{selectedMachine ? (
|
|
|
|
<div className="detail-stack">
|
|
|
|
<div className="detail-stack">
|
|
|
|
<div className="metric-row">
|
|
|
|
<div className="metric-row">
|
|
|
|
<KpiBlock label="Status" value={<StatusBadge status={selectedMachine.status} />} />
|
|
|
|
<KpiBlock label={t("Status", "Etat")} value={<StatusBadge status={selectedMachine.status} language={language} />} />
|
|
|
|
<KpiBlock label="Current order" value={selectedMachine.active_order?.order_number ?? "None"} />
|
|
|
|
<KpiBlock label={t("Current order", "OF courant")} value={selectedMachine.active_order?.order_number ?? t("None", "Aucun")} />
|
|
|
|
<KpiBlock label="Avg. cycle" value={`${selectedMachine.cycle_real_avg_sec.toFixed(1)} s`} />
|
|
|
|
<KpiBlock label={t("Avg. cycle", "Cycle moyen")} value={`${selectedMachine.cycle_real_avg_sec.toFixed(1)} s`} />
|
|
|
|
<KpiBlock label="Daily OEE" value={`${Math.round(selectedMachine.today_oee.oee * 100)}%`} />
|
|
|
|
<KpiBlock label={t("Daily OEE", "TRS jour")} value={`${Math.round(selectedMachine.today_oee.oee * 100)}%`} />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="machine-context">
|
|
|
|
<div className="machine-context">
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<span className="meta-label">Machine</span>
|
|
|
|
<span className="meta-label">{t("Machine", "Machine")}</span>
|
|
|
|
<strong>
|
|
|
|
<strong>
|
|
|
|
{selectedMachine.brand} {selectedMachine.model}
|
|
|
|
{selectedMachine.brand} {selectedMachine.model}
|
|
|
|
</strong>
|
|
|
|
</strong>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<span className="meta-label">Signal setup</span>
|
|
|
|
<span className="meta-label">{t("Signal setup", "Parametrage signal")}</span>
|
|
|
|
<strong>
|
|
|
|
<strong>
|
|
|
|
{selectedMachine.config.cycle_signal_edge} edge / {selectedMachine.config.debounce_ms} ms /{" "}
|
|
|
|
{selectedMachine.config.cycle_signal_edge} edge / {selectedMachine.config.debounce_ms} ms /{" "}
|
|
|
|
{selectedMachine.config.stop_detection_delay_sec} s
|
|
|
|
{selectedMachine.config.stop_detection_delay_sec} s
|
|
|
|
</strong>
|
|
|
|
</strong>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<span className="meta-label">Good parts</span>
|
|
|
|
<span className="meta-label">{t("Good parts", "Pieces bonnes")}</span>
|
|
|
|
<strong>{selectedMachine.produced_qty - selectedMachine.scrap_qty}</strong>
|
|
|
|
<strong>{selectedMachine.produced_qty - selectedMachine.scrap_qty}</strong>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<section className="cycle-panel">
|
|
|
|
<section className="cycle-panel">
|
|
|
|
<div className="cycle-panel__header">
|
|
|
|
<div className="cycle-panel__header">
|
|
|
|
<h3>Recent cycles</h3>
|
|
|
|
<h3>{t("Recent cycles", "Cycles recents")}</h3>
|
|
|
|
<span>{detail.cycles.length} samples</span>
|
|
|
|
<span>{detail.cycles.length} {t("samples", "echantillons")}</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<CycleBarChart cycles={detail.cycles} theoreticalCycleTimeSec={selectedMachine.active_order?.theoretical_cycle_time_sec ?? 0} />
|
|
|
|
<CycleBarChart cycles={detail.cycles} theoreticalCycleTimeSec={selectedMachine.active_order?.theoretical_cycle_time_sec ?? 0} />
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="detail-columns">
|
|
|
|
<div className="detail-columns">
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<h3>Recent events</h3>
|
|
|
|
<h3>{t("Recent events", "Evenements recents")}</h3>
|
|
|
|
<ul className="list">
|
|
|
|
<ul className="list">
|
|
|
|
{detail.events.slice(0, 8).map((item) => (
|
|
|
|
{detail.events.slice(0, 8).map((item) => (
|
|
|
|
<li key={item.id}>
|
|
|
|
<li key={item.id}>
|
|
|
|
@@ -606,7 +643,7 @@ export default function App() {
|
|
|
|
</ul>
|
|
|
|
</ul>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<h3>Downtime history</h3>
|
|
|
|
<h3>{t("Downtime history", "Historique arrets")}</h3>
|
|
|
|
<ul className="list">
|
|
|
|
<ul className="list">
|
|
|
|
{detail.downtimes.slice(0, 8).map((item) => (
|
|
|
|
{detail.downtimes.slice(0, 8).map((item) => (
|
|
|
|
<li key={item.id}>
|
|
|
|
<li key={item.id}>
|
|
|
|
@@ -622,7 +659,7 @@ export default function App() {
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
) : (
|
|
|
|
<p>No machine selected.</p>
|
|
|
|
<p>{t("No machine selected.", "Aucune machine selectionnee.")}</p>
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|
</Panel>
|
|
|
|
</Panel>
|
|
|
|
);
|
|
|
|
);
|
|
|
|
@@ -630,14 +667,14 @@ export default function App() {
|
|
|
|
|
|
|
|
|
|
|
|
if (activeModule === "trs") {
|
|
|
|
if (activeModule === "trs") {
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<Panel title="Vue TRS Journalier" subtitle={today}>
|
|
|
|
<Panel title={t("Daily OEE view", "Vue TRS journalier")} subtitle={today}>
|
|
|
|
{dailyOee ? (
|
|
|
|
{dailyOee ? (
|
|
|
|
<div className="module-grid module-grid--split">
|
|
|
|
<div className="module-grid module-grid--split">
|
|
|
|
<div className="oee-grid">
|
|
|
|
<div className="oee-grid">
|
|
|
|
<KpiBlock label="OEE" value={`${Math.round(dailyOee.overall.oee * 100)}%`} tone="dark" />
|
|
|
|
<KpiBlock label="OEE" value={`${Math.round(dailyOee.overall.oee * 100)}%`} tone="dark" />
|
|
|
|
<KpiBlock label="Availability" value={`${Math.round(dailyOee.overall.availability * 100)}%`} />
|
|
|
|
<KpiBlock label={t("Availability", "Disponibilite")} value={`${Math.round(dailyOee.overall.availability * 100)}%`} />
|
|
|
|
<KpiBlock label="Performance" value={`${Math.round(dailyOee.overall.performance * 100)}%`} />
|
|
|
|
<KpiBlock label={t("Performance", "Performance")} value={`${Math.round(dailyOee.overall.performance * 100)}%`} />
|
|
|
|
<KpiBlock label="Quality" value={`${Math.round(dailyOee.overall.quality * 100)}%`} />
|
|
|
|
<KpiBlock label={t("Quality", "Qualite")} value={`${Math.round(dailyOee.overall.quality * 100)}%`} />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="oee-mini-list">
|
|
|
|
<div className="oee-mini-list">
|
|
|
|
{machines.map((machine) => (
|
|
|
|
{machines.map((machine) => (
|
|
|
|
@@ -655,7 +692,10 @@ export default function App() {
|
|
|
|
|
|
|
|
|
|
|
|
if (activeModule === "orders") {
|
|
|
|
if (activeModule === "orders") {
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<Panel title="Ordres de Fabrication" subtitle={selectedOrder ? `Selected ${selectedOrder.order_number}` : "Create and manage production orders"}>
|
|
|
|
<Panel
|
|
|
|
|
|
|
|
title={t("Production orders", "Ordres de fabrication")}
|
|
|
|
|
|
|
|
subtitle={selectedOrder ? `${t("Selected", "Selectionne")} ${selectedOrder.order_number}` : t("Create and manage production orders", "Creer et gerer les ordres de fabrication")}
|
|
|
|
|
|
|
|
>
|
|
|
|
<div className="module-grid module-grid--form-table">
|
|
|
|
<div className="module-grid module-grid--form-table">
|
|
|
|
<form
|
|
|
|
<form
|
|
|
|
className="form-grid module-form"
|
|
|
|
className="form-grid module-form"
|
|
|
|
@@ -665,33 +705,44 @@ export default function App() {
|
|
|
|
onChangeCapture={(event) => validateForm(event.currentTarget, "order")}
|
|
|
|
onChangeCapture={(event) => validateForm(event.currentTarget, "order")}
|
|
|
|
noValidate
|
|
|
|
noValidate
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<input name="order_number" placeholder="OF number" required />
|
|
|
|
<FieldLabel label={t("OF number", "Numero OF")} error={fieldError("order", "order_number")}>
|
|
|
|
{fieldError("order", "order_number")}
|
|
|
|
<input name="order_number" placeholder="OF-2001" required />
|
|
|
|
<select name="machine_id" required defaultValue={selectedMachine?.id ?? ""}>
|
|
|
|
</FieldLabel>
|
|
|
|
<option value="" disabled>
|
|
|
|
<FieldLabel label={t("Machine", "Machine")} error={fieldError("order", "machine_id")}>
|
|
|
|
Machine
|
|
|
|
<select name="machine_id" required defaultValue={selectedMachine?.id ?? ""}>
|
|
|
|
</option>
|
|
|
|
<option value="" disabled>
|
|
|
|
{machines.map((machine) => (
|
|
|
|
{t("Select machine", "Selectionner machine")}
|
|
|
|
<option key={machine.id} value={machine.id}>
|
|
|
|
|
|
|
|
{machine.code}
|
|
|
|
|
|
|
|
</option>
|
|
|
|
</option>
|
|
|
|
))}
|
|
|
|
{machines.map((machine) => (
|
|
|
|
</select>
|
|
|
|
<option key={machine.id} value={machine.id}>
|
|
|
|
{fieldError("order", "machine_id")}
|
|
|
|
{machine.code}
|
|
|
|
<input name="article_ref" placeholder="Article ref" required />
|
|
|
|
</option>
|
|
|
|
{fieldError("order", "article_ref")}
|
|
|
|
))}
|
|
|
|
<input name="article_name" placeholder="Article name" required />
|
|
|
|
</select>
|
|
|
|
{fieldError("order", "article_name")}
|
|
|
|
</FieldLabel>
|
|
|
|
<input name="mold_ref" placeholder="Mold ref" />
|
|
|
|
<FieldLabel label={t("Article reference", "Reference article")} error={fieldError("order", "article_ref")}>
|
|
|
|
<input name="material_ref" placeholder="Material ref" />
|
|
|
|
<input name="article_ref" placeholder="ART-001" required />
|
|
|
|
<input name="planned_qty" type="number" min="1" placeholder="Planned qty" required />
|
|
|
|
</FieldLabel>
|
|
|
|
{fieldError("order", "planned_qty")}
|
|
|
|
<FieldLabel label={t("Article name", "Nom article")} error={fieldError("order", "article_name")}>
|
|
|
|
<input name="cavities" type="number" min="1" defaultValue="1" placeholder="Cavities" required />
|
|
|
|
<input name="article_name" placeholder={t("Part name", "Nom piece")} required />
|
|
|
|
{fieldError("order", "cavities")}
|
|
|
|
</FieldLabel>
|
|
|
|
<input name="theoretical_cycle_time_sec" type="number" min="1" step="0.1" placeholder="Theoretical cycle" required />
|
|
|
|
<FieldLabel label={t("Mold reference", "Reference moule")}>
|
|
|
|
{fieldError("order", "theoretical_cycle_time_sec")}
|
|
|
|
<input name="mold_ref" placeholder="M-001" />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
|
|
|
|
<FieldLabel label={t("Material reference", "Reference matiere")}>
|
|
|
|
|
|
|
|
<input name="material_ref" placeholder="PP" />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
|
|
|
|
<FieldLabel label={t("Planned quantity", "Quantite prevue")} error={fieldError("order", "planned_qty")}>
|
|
|
|
|
|
|
|
<input name="planned_qty" type="number" min="1" placeholder="1000" required />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
|
|
|
|
<FieldLabel label={t("Cavities", "Empreintes")} error={fieldError("order", "cavities")}>
|
|
|
|
|
|
|
|
<input name="cavities" type="number" min="1" defaultValue="1" required />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
|
|
|
|
<FieldLabel label={t("Theoretical cycle time", "Temps de cycle theorique")} error={fieldError("order", "theoretical_cycle_time_sec")}>
|
|
|
|
|
|
|
|
<input name="theoretical_cycle_time_sec" type="number" min="1" step="0.1" placeholder="20" required />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
<button type="submit" className="primary-button" disabled={Object.keys(formErrors.order).length > 0}>
|
|
|
|
<button type="submit" className="primary-button" disabled={Object.keys(formErrors.order).length > 0}>
|
|
|
|
Create
|
|
|
|
{t("Create", "Creer")}
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
{actionError("order")}
|
|
|
|
{actionError("order")}
|
|
|
|
{toast("order")}
|
|
|
|
{toast("order")}
|
|
|
|
@@ -701,12 +752,12 @@ export default function App() {
|
|
|
|
<table>
|
|
|
|
<table>
|
|
|
|
<thead>
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<tr>
|
|
|
|
<th>OF</th>
|
|
|
|
<th>{t("OF", "OF")}</th>
|
|
|
|
<th>Machine</th>
|
|
|
|
<th>{t("Machine", "Machine")}</th>
|
|
|
|
<th>Article</th>
|
|
|
|
<th>{t("Article", "Article")}</th>
|
|
|
|
<th>Planned</th>
|
|
|
|
<th>{t("Planned", "Prevu")}</th>
|
|
|
|
<th>Status</th>
|
|
|
|
<th>{t("Status", "Etat")}</th>
|
|
|
|
<th>Actions</th>
|
|
|
|
<th>{t("Actions", "Actions")}</th>
|
|
|
|
</tr>
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
</thead>
|
|
|
|
<tbody>
|
|
|
|
<tbody>
|
|
|
|
@@ -723,13 +774,13 @@ export default function App() {
|
|
|
|
<td>{order.status}</td>
|
|
|
|
<td>{order.status}</td>
|
|
|
|
<td className="table-actions">
|
|
|
|
<td className="table-actions">
|
|
|
|
<button type="button" onClick={() => changeOrderStatus(order.id, "start")}>
|
|
|
|
<button type="button" onClick={() => changeOrderStatus(order.id, "start")}>
|
|
|
|
Start
|
|
|
|
{t("Start", "Demarrer")}
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
<button type="button" onClick={() => changeOrderStatus(order.id, "pause")}>
|
|
|
|
<button type="button" onClick={() => changeOrderStatus(order.id, "pause")}>
|
|
|
|
Pause
|
|
|
|
{t("Pause", "Pause")}
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
<button type="button" onClick={() => changeOrderStatus(order.id, "close")}>
|
|
|
|
<button type="button" onClick={() => changeOrderStatus(order.id, "close")}>
|
|
|
|
Close
|
|
|
|
{t("Close", "Cloturer")}
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
</td>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</tr>
|
|
|
|
@@ -742,28 +793,28 @@ export default function App() {
|
|
|
|
<section className="order-detail">
|
|
|
|
<section className="order-detail">
|
|
|
|
<div className="order-detail__header">
|
|
|
|
<div className="order-detail__header">
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<span className="meta-label">OF detail</span>
|
|
|
|
<span className="meta-label">{t("OF detail", "Detail OF")}</span>
|
|
|
|
<h3>{selectedOrder.order_number}</h3>
|
|
|
|
<h3>{selectedOrder.order_number}</h3>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<StatusBadge status={selectedOrder.status} />
|
|
|
|
<StatusBadge status={selectedOrder.status} language={language} />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="order-detail__meta">
|
|
|
|
<div className="order-detail__meta">
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<span className="meta-label">Machine</span>
|
|
|
|
<span className="meta-label">{t("Machine", "Machine")}</span>
|
|
|
|
<strong>{orderMachineLabel(selectedOrder)}</strong>
|
|
|
|
<strong>{orderMachineLabel(selectedOrder)}</strong>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<span className="meta-label">Article</span>
|
|
|
|
<span className="meta-label">{t("Article", "Article")}</span>
|
|
|
|
<strong>{selectedOrder.article_ref} / {selectedOrder.article_name}</strong>
|
|
|
|
<strong>{selectedOrder.article_ref} / {selectedOrder.article_name}</strong>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<span className="meta-label">Cycle / cavities</span>
|
|
|
|
<span className="meta-label">{t("Cycle / cavities", "Cycle / empreintes")}</span>
|
|
|
|
<strong>{selectedOrder.theoretical_cycle_time_sec} s / {selectedOrder.cavities}</strong>
|
|
|
|
<strong>{selectedOrder.theoretical_cycle_time_sec} s / {selectedOrder.cavities}</strong>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<span className="meta-label">Dates</span>
|
|
|
|
<span className="meta-label">{t("Dates", "Dates")}</span>
|
|
|
|
<strong>
|
|
|
|
<strong>
|
|
|
|
{selectedOrder.started_at ? new Date(selectedOrder.started_at).toLocaleString() : "Not started"}
|
|
|
|
{selectedOrder.started_at ? new Date(selectedOrder.started_at).toLocaleString() : t("Not started", "Non demarre")}
|
|
|
|
{selectedOrder.ended_at ? ` -> ${new Date(selectedOrder.ended_at).toLocaleString()}` : ""}
|
|
|
|
{selectedOrder.ended_at ? ` -> ${new Date(selectedOrder.ended_at).toLocaleString()}` : ""}
|
|
|
|
</strong>
|
|
|
|
</strong>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
@@ -777,38 +828,48 @@ export default function App() {
|
|
|
|
onChangeCapture={(event) => validateForm(event.currentTarget, "order")}
|
|
|
|
onChangeCapture={(event) => validateForm(event.currentTarget, "order")}
|
|
|
|
noValidate
|
|
|
|
noValidate
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<input name="order_number" defaultValue={selectedOrder.order_number} placeholder="OF number" required />
|
|
|
|
<FieldLabel label={t("OF number", "Numero OF")}>
|
|
|
|
<select name="machine_id" required defaultValue={selectedOrder.machine_id}>
|
|
|
|
<input name="order_number" defaultValue={selectedOrder.order_number} required />
|
|
|
|
{machines.map((machine) => (
|
|
|
|
</FieldLabel>
|
|
|
|
<option key={machine.id} value={machine.id}>
|
|
|
|
<FieldLabel label={t("Machine", "Machine")}>
|
|
|
|
{machine.code}
|
|
|
|
<select name="machine_id" required defaultValue={selectedOrder.machine_id}>
|
|
|
|
</option>
|
|
|
|
{machines.map((machine) => (
|
|
|
|
))}
|
|
|
|
<option key={machine.id} value={machine.id}>
|
|
|
|
</select>
|
|
|
|
{machine.code}
|
|
|
|
<input name="article_ref" defaultValue={selectedOrder.article_ref} placeholder="Article ref" required />
|
|
|
|
</option>
|
|
|
|
<input name="article_name" defaultValue={selectedOrder.article_name} placeholder="Article name" required />
|
|
|
|
))}
|
|
|
|
<input name="mold_ref" defaultValue={selectedOrder.mold_ref ?? ""} placeholder="Mold ref" />
|
|
|
|
</select>
|
|
|
|
<input name="material_ref" defaultValue={selectedOrder.material_ref ?? ""} placeholder="Material ref" />
|
|
|
|
</FieldLabel>
|
|
|
|
<input name="planned_qty" type="number" min="1" defaultValue={selectedOrder.planned_qty} placeholder="Planned qty" required />
|
|
|
|
<FieldLabel label={t("Article reference", "Reference article")}>
|
|
|
|
<input name="cavities" type="number" min="1" defaultValue={selectedOrder.cavities} placeholder="Cavities" required />
|
|
|
|
<input name="article_ref" defaultValue={selectedOrder.article_ref} required />
|
|
|
|
<input
|
|
|
|
</FieldLabel>
|
|
|
|
name="theoretical_cycle_time_sec"
|
|
|
|
<FieldLabel label={t("Article name", "Nom article")}>
|
|
|
|
type="number"
|
|
|
|
<input name="article_name" defaultValue={selectedOrder.article_name} required />
|
|
|
|
min="1"
|
|
|
|
</FieldLabel>
|
|
|
|
step="0.1"
|
|
|
|
<FieldLabel label={t("Mold reference", "Reference moule")}>
|
|
|
|
defaultValue={selectedOrder.theoretical_cycle_time_sec}
|
|
|
|
<input name="mold_ref" defaultValue={selectedOrder.mold_ref ?? ""} />
|
|
|
|
placeholder="Theoretical cycle"
|
|
|
|
</FieldLabel>
|
|
|
|
required
|
|
|
|
<FieldLabel label={t("Material reference", "Reference matiere")}>
|
|
|
|
/>
|
|
|
|
<input name="material_ref" defaultValue={selectedOrder.material_ref ?? ""} />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
|
|
|
|
<FieldLabel label={t("Planned quantity", "Quantite prevue")}>
|
|
|
|
|
|
|
|
<input name="planned_qty" type="number" min="1" defaultValue={selectedOrder.planned_qty} required />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
|
|
|
|
<FieldLabel label={t("Cavities", "Empreintes")}>
|
|
|
|
|
|
|
|
<input name="cavities" type="number" min="1" defaultValue={selectedOrder.cavities} required />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
|
|
|
|
<FieldLabel label={t("Theoretical cycle time", "Temps de cycle theorique")}>
|
|
|
|
|
|
|
|
<input name="theoretical_cycle_time_sec" type="number" min="1" step="0.1" defaultValue={selectedOrder.theoretical_cycle_time_sec} required />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
<button type="submit" className="primary-button" disabled={Object.keys(formErrors.order).length > 0}>
|
|
|
|
<button type="submit" className="primary-button" disabled={Object.keys(formErrors.order).length > 0}>
|
|
|
|
Update OF
|
|
|
|
{t("Update OF", "Modifier OF")}
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
{actionError("order")}
|
|
|
|
{actionError("order")}
|
|
|
|
{toast("order")}
|
|
|
|
{toast("order")}
|
|
|
|
</form>
|
|
|
|
</form>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
) : (
|
|
|
|
) : (
|
|
|
|
<div className="empty-state">Select an OF to see and update details.</div>
|
|
|
|
<div className="empty-state">{t("Select an OF to see and update details.", "Selectionner un OF pour voir et modifier les details.")}</div>
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Panel>
|
|
|
|
</Panel>
|
|
|
|
@@ -818,11 +879,11 @@ export default function App() {
|
|
|
|
if (activeModule === "downtimes") {
|
|
|
|
if (activeModule === "downtimes") {
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<Panel
|
|
|
|
<Panel
|
|
|
|
title="Qualification Arret"
|
|
|
|
title={t("Stop qualification", "Qualification arret")}
|
|
|
|
subtitle={
|
|
|
|
subtitle={
|
|
|
|
selectedMachine
|
|
|
|
selectedMachine
|
|
|
|
? `${selectedMachineUnqualifiedDowntimes.length} unqualified for ${selectedMachine.code} / ${unqualifiedDowntimes.length} total`
|
|
|
|
? `${selectedMachineUnqualifiedDowntimes.length} ${t("unqualified for", "non qualifies pour")} ${selectedMachine.code} / ${unqualifiedDowntimes.length} ${t("total", "total")}`
|
|
|
|
: `${unqualifiedDowntimes.length} unqualified downtime(s)`
|
|
|
|
: `${unqualifiedDowntimes.length} ${t("unqualified downtime(s)", "arret(s) non qualifies")}`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<div className="module-grid module-grid--form-table">
|
|
|
|
<div className="module-grid module-grid--form-table">
|
|
|
|
@@ -835,33 +896,38 @@ export default function App() {
|
|
|
|
onChangeCapture={(event) => validateForm(event.currentTarget, "downtime")}
|
|
|
|
onChangeCapture={(event) => validateForm(event.currentTarget, "downtime")}
|
|
|
|
noValidate
|
|
|
|
noValidate
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<select name="downtime_id" required defaultValue={selectedMachineUnqualifiedDowntimes[0]?.id ?? ""}>
|
|
|
|
<FieldLabel label={t("Downtime to qualify", "Arret a qualifier")} error={fieldError("downtime", "downtime_id")}>
|
|
|
|
<option value="" disabled>
|
|
|
|
<select name="downtime_id" required defaultValue={selectedMachineUnqualifiedDowntimes[0]?.id ?? ""}>
|
|
|
|
Downtime to qualify
|
|
|
|
<option value="" disabled>
|
|
|
|
</option>
|
|
|
|
{t("Select downtime", "Selectionner arret")}
|
|
|
|
{downtimeOptions.map((downtime) => (
|
|
|
|
|
|
|
|
<option key={downtime.id} value={downtime.id}>
|
|
|
|
|
|
|
|
{downtimeOptionLabel(downtime)}
|
|
|
|
|
|
|
|
</option>
|
|
|
|
</option>
|
|
|
|
))}
|
|
|
|
{downtimeOptions.map((downtime) => (
|
|
|
|
</select>
|
|
|
|
<option key={downtime.id} value={downtime.id}>
|
|
|
|
{fieldError("downtime", "downtime_id")}
|
|
|
|
{downtimeOptionLabel(downtime)}
|
|
|
|
<select name="reason_code" required defaultValue="">
|
|
|
|
</option>
|
|
|
|
<option value="" disabled>
|
|
|
|
))}
|
|
|
|
Reason
|
|
|
|
</select>
|
|
|
|
</option>
|
|
|
|
</FieldLabel>
|
|
|
|
{references?.downtime_reasons.map((reason) => (
|
|
|
|
<FieldLabel label={t("Reason", "Cause")} error={fieldError("downtime", "reason_code")}>
|
|
|
|
<option key={reason.code} value={reason.code}>
|
|
|
|
<select name="reason_code" required defaultValue="">
|
|
|
|
{reason.label}
|
|
|
|
<option value="" disabled>
|
|
|
|
|
|
|
|
{t("Select reason", "Selectionner cause")}
|
|
|
|
</option>
|
|
|
|
</option>
|
|
|
|
))}
|
|
|
|
{references?.downtime_reasons.map((reason) => (
|
|
|
|
</select>
|
|
|
|
<option key={reason.code} value={reason.code}>
|
|
|
|
{fieldError("downtime", "reason_code")}
|
|
|
|
{reason.label}
|
|
|
|
<input name="qualified_by" placeholder="Operator" required />
|
|
|
|
</option>
|
|
|
|
{fieldError("downtime", "qualified_by")}
|
|
|
|
))}
|
|
|
|
<textarea name="comment" placeholder="Comment" rows={3} />
|
|
|
|
</select>
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
|
|
|
|
<FieldLabel label={t("Qualified by", "Qualifie par")} error={fieldError("downtime", "qualified_by")}>
|
|
|
|
|
|
|
|
<input name="qualified_by" placeholder={t("Operator name", "Nom operateur")} required />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
|
|
|
|
<FieldLabel label={t("Comment", "Commentaire")}>
|
|
|
|
|
|
|
|
<textarea name="comment" placeholder={t("Optional comment", "Commentaire optionnel")} rows={3} />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
<button type="submit" className="primary-button" disabled={Object.keys(formErrors.downtime).length > 0}>
|
|
|
|
<button type="submit" className="primary-button" disabled={Object.keys(formErrors.downtime).length > 0}>
|
|
|
|
Qualify
|
|
|
|
{t("Qualify", "Qualifier")}
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
{actionError("downtime")}
|
|
|
|
{actionError("downtime")}
|
|
|
|
{toast("downtime")}
|
|
|
|
{toast("downtime")}
|
|
|
|
@@ -870,7 +936,7 @@ export default function App() {
|
|
|
|
<div className="downtime-board">
|
|
|
|
<div className="downtime-board">
|
|
|
|
{selectedMachine ? (
|
|
|
|
{selectedMachine ? (
|
|
|
|
<section className="downtime-focus">
|
|
|
|
<section className="downtime-focus">
|
|
|
|
<span className="meta-label">Selected machine</span>
|
|
|
|
<span className="meta-label">{t("Selected machine", "Machine selectionnee")}</span>
|
|
|
|
<strong>{selectedMachine.code}</strong>
|
|
|
|
<strong>{selectedMachine.code}</strong>
|
|
|
|
{selectedMachineUnqualifiedDowntimes.length > 0 ? (
|
|
|
|
{selectedMachineUnqualifiedDowntimes.length > 0 ? (
|
|
|
|
<ul className="timeline-list timeline-list--compact">
|
|
|
|
<ul className="timeline-list timeline-list--compact">
|
|
|
|
@@ -885,13 +951,13 @@ export default function App() {
|
|
|
|
))}
|
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
</ul>
|
|
|
|
) : (
|
|
|
|
) : (
|
|
|
|
<div className="empty-state">No unqualified downtime for {selectedMachine.code}.</div>
|
|
|
|
<div className="empty-state">{t("No unqualified downtime for", "Aucun arret non qualifie pour")} {selectedMachine.code}.</div>
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
) : null}
|
|
|
|
) : null}
|
|
|
|
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<section>
|
|
|
|
<span className="meta-label">All stops waiting qualification</span>
|
|
|
|
<span className="meta-label">{t("All stops waiting qualification", "Tous les arrets en attente de qualification")}</span>
|
|
|
|
<ul className="timeline-list">
|
|
|
|
<ul className="timeline-list">
|
|
|
|
{downtimeOptions.map((item) => (
|
|
|
|
{downtimeOptions.map((item) => (
|
|
|
|
<li key={item.id} className={item.machine_id === selectedMachineId ? "is-current-machine" : ""}>
|
|
|
|
<li key={item.id} className={item.machine_id === selectedMachineId ? "is-current-machine" : ""}>
|
|
|
|
@@ -909,7 +975,7 @@ export default function App() {
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section>
|
|
|
|
<section>
|
|
|
|
<span className="meta-label">Qualified stops history</span>
|
|
|
|
<span className="meta-label">{t("Qualified stops history", "Historique arrets qualifies")}</span>
|
|
|
|
<ul className="timeline-list timeline-list--qualified">
|
|
|
|
<ul className="timeline-list timeline-list--qualified">
|
|
|
|
{qualifiedDowntimes.map((item) => (
|
|
|
|
{qualifiedDowntimes.map((item) => (
|
|
|
|
<li key={item.id} className={item.machine_id === selectedMachineId ? "is-current-machine" : ""}>
|
|
|
|
<li key={item.id} className={item.machine_id === selectedMachineId ? "is-current-machine" : ""}>
|
|
|
|
@@ -919,7 +985,7 @@ export default function App() {
|
|
|
|
</strong>
|
|
|
|
</strong>
|
|
|
|
<span>
|
|
|
|
<span>
|
|
|
|
{item.order_number ? `${item.order_number} / ` : ""}
|
|
|
|
{item.order_number ? `${item.order_number} / ` : ""}
|
|
|
|
{item.qualified_by ? `By ${item.qualified_by} / ` : ""}
|
|
|
|
{item.qualified_by ? `${t("By", "Par")} ${item.qualified_by} / ` : ""}
|
|
|
|
{formatDurationSeconds(item.duration_sec)}
|
|
|
|
{formatDurationSeconds(item.duration_sec)}
|
|
|
|
{item.comment ? ` / ${item.comment}` : ""}
|
|
|
|
{item.comment ? ` / ${item.comment}` : ""}
|
|
|
|
</span>
|
|
|
|
</span>
|
|
|
|
@@ -931,10 +997,10 @@ export default function App() {
|
|
|
|
</li>
|
|
|
|
</li>
|
|
|
|
))}
|
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
</ul>
|
|
|
|
{qualifiedDowntimes.length === 0 ? <div className="empty-state">No qualified downtime history yet.</div> : null}
|
|
|
|
{qualifiedDowntimes.length === 0 ? <div className="empty-state">{t("No qualified downtime history yet.", "Aucun historique d'arret qualifie.")}</div> : null}
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{unqualifiedDowntimes.length === 0 ? <div className="empty-state">No downtime currently needs qualification.</div> : null}
|
|
|
|
{unqualifiedDowntimes.length === 0 ? <div className="empty-state">{t("No downtime currently needs qualification.", "Aucun arret a qualifier actuellement.")}</div> : null}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Panel>
|
|
|
|
</Panel>
|
|
|
|
);
|
|
|
|
);
|
|
|
|
@@ -942,7 +1008,7 @@ export default function App() {
|
|
|
|
|
|
|
|
|
|
|
|
if (activeModule === "scrap") {
|
|
|
|
if (activeModule === "scrap") {
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<Panel title="Saisie Rebut" subtitle={selectedMachine?.code ?? "Select machine"}>
|
|
|
|
<Panel title={t("Scrap declaration", "Saisie rebut")} subtitle={selectedMachine?.code ?? t("Select machine", "Selectionner machine")}>
|
|
|
|
<div className="module-grid module-grid--form-table">
|
|
|
|
<div className="module-grid module-grid--form-table">
|
|
|
|
<form
|
|
|
|
<form
|
|
|
|
className="form-grid module-form"
|
|
|
|
className="form-grid module-form"
|
|
|
|
@@ -952,24 +1018,29 @@ export default function App() {
|
|
|
|
onChangeCapture={(event) => validateForm(event.currentTarget, "scrap")}
|
|
|
|
onChangeCapture={(event) => validateForm(event.currentTarget, "scrap")}
|
|
|
|
noValidate
|
|
|
|
noValidate
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<input name="operator_name" placeholder="Operator" required />
|
|
|
|
<FieldLabel label={t("Operator", "Operateur")} error={fieldError("scrap", "operator_name")}>
|
|
|
|
{fieldError("scrap", "operator_name")}
|
|
|
|
<input name="operator_name" placeholder={t("Operator name", "Nom operateur")} required />
|
|
|
|
<input name="quantity" type="number" min="1" placeholder="Quantity" required />
|
|
|
|
</FieldLabel>
|
|
|
|
{fieldError("scrap", "quantity")}
|
|
|
|
<FieldLabel label={t("Quantity", "Quantite")} error={fieldError("scrap", "quantity")}>
|
|
|
|
<select name="reason_code" required defaultValue="">
|
|
|
|
<input name="quantity" type="number" min="1" placeholder="1" required />
|
|
|
|
<option value="" disabled>
|
|
|
|
</FieldLabel>
|
|
|
|
Scrap reason
|
|
|
|
<FieldLabel label={t("Scrap reason", "Cause rebut")} error={fieldError("scrap", "reason_code")}>
|
|
|
|
</option>
|
|
|
|
<select name="reason_code" required defaultValue="">
|
|
|
|
{references?.scrap_reasons.map((reason) => (
|
|
|
|
<option value="" disabled>
|
|
|
|
<option key={reason.code} value={reason.code}>
|
|
|
|
{t("Select scrap reason", "Selectionner cause rebut")}
|
|
|
|
{reason.label}
|
|
|
|
|
|
|
|
</option>
|
|
|
|
</option>
|
|
|
|
))}
|
|
|
|
{references?.scrap_reasons.map((reason) => (
|
|
|
|
</select>
|
|
|
|
<option key={reason.code} value={reason.code}>
|
|
|
|
{fieldError("scrap", "reason_code")}
|
|
|
|
{reason.label}
|
|
|
|
<textarea name="comment" placeholder="Comment" rows={3} />
|
|
|
|
</option>
|
|
|
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
|
|
|
|
<FieldLabel label={t("Comment", "Commentaire")}>
|
|
|
|
|
|
|
|
<textarea name="comment" placeholder={t("Optional comment", "Commentaire optionnel")} rows={3} />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
<button type="submit" className="primary-button" disabled={!selectedMachine || Object.keys(formErrors.scrap).length > 0}>
|
|
|
|
<button type="submit" className="primary-button" disabled={!selectedMachine || Object.keys(formErrors.scrap).length > 0}>
|
|
|
|
Record
|
|
|
|
{t("Record", "Enregistrer")}
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
{actionError("scrap")}
|
|
|
|
{actionError("scrap")}
|
|
|
|
{toast("scrap")}
|
|
|
|
{toast("scrap")}
|
|
|
|
@@ -985,14 +1056,14 @@ export default function App() {
|
|
|
|
</li>
|
|
|
|
</li>
|
|
|
|
))}
|
|
|
|
))}
|
|
|
|
</ul>
|
|
|
|
</ul>
|
|
|
|
{scraps.length === 0 ? <div className="empty-state">No scrap has been declared yet.</div> : null}
|
|
|
|
{scraps.length === 0 ? <div className="empty-state">{t("No scrap has been declared yet.", "Aucun rebut declare pour le moment.")}</div> : null}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Panel>
|
|
|
|
</Panel>
|
|
|
|
);
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
<Panel title="Config Machine" subtitle={selectedMachine?.code ?? "No machine selected"}>
|
|
|
|
<Panel title={t("Machine config", "Config machine")} subtitle={selectedMachine?.code ?? t("No machine selected", "Aucune machine selectionnee")}>
|
|
|
|
{selectedMachine ? (
|
|
|
|
{selectedMachine ? (
|
|
|
|
<form
|
|
|
|
<form
|
|
|
|
className="form-grid config-form"
|
|
|
|
className="form-grid config-form"
|
|
|
|
@@ -1003,55 +1074,45 @@ export default function App() {
|
|
|
|
key={selectedMachine.id}
|
|
|
|
key={selectedMachine.id}
|
|
|
|
noValidate
|
|
|
|
noValidate
|
|
|
|
>
|
|
|
|
>
|
|
|
|
<input name="name" placeholder="Display name" defaultValue={selectedMachine.config.name} required />
|
|
|
|
<FieldLabel label={t("Display name", "Nom affiche")} error={fieldError("config", "name")}>
|
|
|
|
{fieldError("config", "name")}
|
|
|
|
<input name="name" defaultValue={selectedMachine.config.name} required />
|
|
|
|
<input name="brand" placeholder="Brand" defaultValue={selectedMachine.config.brand ?? ""} />
|
|
|
|
</FieldLabel>
|
|
|
|
<input name="model" placeholder="Model" defaultValue={selectedMachine.config.model ?? ""} />
|
|
|
|
<FieldLabel label={t("Brand", "Marque")}>
|
|
|
|
|
|
|
|
<input name="brand" defaultValue={selectedMachine.config.brand ?? ""} />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
|
|
|
|
<FieldLabel label={t("Model", "Modele")}>
|
|
|
|
|
|
|
|
<input name="model" defaultValue={selectedMachine.config.model ?? ""} />
|
|
|
|
|
|
|
|
</FieldLabel>
|
|
|
|
<label className="checkbox-field">
|
|
|
|
<label className="checkbox-field">
|
|
|
|
<input name="is_active" type="checkbox" defaultChecked={selectedMachine.config.is_active} />
|
|
|
|
<input name="is_active" type="checkbox" defaultChecked={selectedMachine.config.is_active} />
|
|
|
|
<span>Active on dashboard</span>
|
|
|
|
<span>{t("Active on dashboard", "Active sur tableau de bord")}</span>
|
|
|
|
</label>
|
|
|
|
</label>
|
|
|
|
<select name="cycle_signal_edge" defaultValue={selectedMachine.config.cycle_signal_edge}>
|
|
|
|
<FieldLabel label={t("Cycle signal edge", "Front signal cycle")}>
|
|
|
|
<option value="rising">Rising edge</option>
|
|
|
|
<select name="cycle_signal_edge" defaultValue={selectedMachine.config.cycle_signal_edge}>
|
|
|
|
<option value="falling">Falling edge</option>
|
|
|
|
<option value="rising">{t("Rising edge", "Front montant")}</option>
|
|
|
|
</select>
|
|
|
|
<option value="falling">{t("Falling edge", "Front descendant")}</option>
|
|
|
|
<input name="debounce_ms" type="number" min="0" defaultValue={selectedMachine.config.debounce_ms} placeholder="Debounce ms" required />
|
|
|
|
</select>
|
|
|
|
{fieldError("config", "debounce_ms")}
|
|
|
|
</FieldLabel>
|
|
|
|
<input
|
|
|
|
<FieldLabel label={t("Debounce", "Anti-rebond")} error={fieldError("config", "debounce_ms")}>
|
|
|
|
name="min_cycle_time_sec"
|
|
|
|
<input name="debounce_ms" type="number" min="0" defaultValue={selectedMachine.config.debounce_ms} required />
|
|
|
|
type="number"
|
|
|
|
</FieldLabel>
|
|
|
|
min="1"
|
|
|
|
<FieldLabel label={t("Minimum cycle time", "Temps cycle minimum")} error={fieldError("config", "min_cycle_time_sec")}>
|
|
|
|
defaultValue={selectedMachine.config.min_cycle_time_sec}
|
|
|
|
<input name="min_cycle_time_sec" type="number" min="1" defaultValue={selectedMachine.config.min_cycle_time_sec} required />
|
|
|
|
placeholder="Min cycle sec"
|
|
|
|
</FieldLabel>
|
|
|
|
required
|
|
|
|
<FieldLabel label={t("Maximum cycle time", "Temps cycle maximum")} error={fieldError("config", "max_cycle_time_sec")}>
|
|
|
|
/>
|
|
|
|
<input name="max_cycle_time_sec" type="number" min="1" defaultValue={selectedMachine.config.max_cycle_time_sec} required />
|
|
|
|
{fieldError("config", "min_cycle_time_sec")}
|
|
|
|
</FieldLabel>
|
|
|
|
<input
|
|
|
|
<FieldLabel label={t("Stop detection delay", "Delai detection arret")} error={fieldError("config", "stop_detection_delay_sec")}>
|
|
|
|
name="max_cycle_time_sec"
|
|
|
|
<input name="stop_detection_delay_sec" type="number" min="1" defaultValue={selectedMachine.config.stop_detection_delay_sec} required />
|
|
|
|
type="number"
|
|
|
|
</FieldLabel>
|
|
|
|
min="1"
|
|
|
|
|
|
|
|
defaultValue={selectedMachine.config.max_cycle_time_sec}
|
|
|
|
|
|
|
|
placeholder="Max cycle sec"
|
|
|
|
|
|
|
|
required
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
{fieldError("config", "max_cycle_time_sec")}
|
|
|
|
|
|
|
|
<input
|
|
|
|
|
|
|
|
name="stop_detection_delay_sec"
|
|
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
|
|
min="1"
|
|
|
|
|
|
|
|
defaultValue={selectedMachine.config.stop_detection_delay_sec}
|
|
|
|
|
|
|
|
placeholder="Stop delay sec"
|
|
|
|
|
|
|
|
required
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
{fieldError("config", "stop_detection_delay_sec")}
|
|
|
|
|
|
|
|
<button type="submit" className="primary-button" disabled={Object.keys(formErrors.config).length > 0}>
|
|
|
|
<button type="submit" className="primary-button" disabled={Object.keys(formErrors.config).length > 0}>
|
|
|
|
Save
|
|
|
|
{t("Save", "Enregistrer")}
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
{actionError("config")}
|
|
|
|
{actionError("config")}
|
|
|
|
{toast("config")}
|
|
|
|
{toast("config")}
|
|
|
|
</form>
|
|
|
|
</form>
|
|
|
|
) : (
|
|
|
|
) : (
|
|
|
|
<p>No machine selected.</p>
|
|
|
|
<p>{t("No machine selected.", "Aucune machine selectionnee.")}</p>
|
|
|
|
)}
|
|
|
|
)}
|
|
|
|
</Panel>
|
|
|
|
</Panel>
|
|
|
|
);
|
|
|
|
);
|
|
|
|
@@ -1063,25 +1124,34 @@ export default function App() {
|
|
|
|
<header className="hero">
|
|
|
|
<header className="hero">
|
|
|
|
<div>
|
|
|
|
<div>
|
|
|
|
<p className="eyebrow">Plast Track MVP</p>
|
|
|
|
<p className="eyebrow">Plast Track MVP</p>
|
|
|
|
<h1>Workshop supervision</h1>
|
|
|
|
<h1>{t("Workshop supervision", "Supervision atelier")}</h1>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="hero__status">
|
|
|
|
<div className="hero__controls">
|
|
|
|
<span className="hero__dot" aria-hidden="true" />
|
|
|
|
<label className="language-select">
|
|
|
|
<strong>{connectionStatus}</strong>
|
|
|
|
<span>{t("Language", "Langue")}</span>
|
|
|
|
{errorMessage ? <p>{errorMessage}</p> : null}
|
|
|
|
<select value={language} onChange={(event) => setLanguage(event.target.value as Language)}>
|
|
|
|
|
|
|
|
<option value="en">English</option>
|
|
|
|
|
|
|
|
<option value="fr">Francais</option>
|
|
|
|
|
|
|
|
</select>
|
|
|
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
<div className="hero__status">
|
|
|
|
|
|
|
|
<span className="hero__dot" aria-hidden="true" />
|
|
|
|
|
|
|
|
<strong>{connectionLabel(connectionStatus)}</strong>
|
|
|
|
|
|
|
|
{errorMessage ? <p>{errorMessage}</p> : null}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</header>
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
{connectionStatus === "Reconnecting" ? <div className="connection-banner">Reconnecting...</div> : null}
|
|
|
|
{connectionStatus === "Reconnecting" ? <div className="connection-banner">{t("Reconnecting...", "Reconnexion...")}</div> : null}
|
|
|
|
|
|
|
|
|
|
|
|
<section className="summary-ribbon" aria-label="Workshop summary">
|
|
|
|
<section className="summary-ribbon" aria-label={t("Workshop summary", "Resume atelier")}>
|
|
|
|
<KpiBlock label="Global OEE" value={`${summaryMetrics.oee}%`} subLabel="Daily aggregate" tone="dark" />
|
|
|
|
<KpiBlock label={t("Global OEE", "TRS global")} value={`${summaryMetrics.oee}%`} subLabel={t("Daily aggregate", "Agregat journalier")} tone="dark" />
|
|
|
|
<KpiBlock label="Production" value={summaryMetrics.production} subLabel={`${machines.length} machines monitored`} />
|
|
|
|
<KpiBlock label={t("Production", "Production")} value={summaryMetrics.production} subLabel={`${machines.length} ${t("machines monitored", "machines supervisees")}`} />
|
|
|
|
<KpiBlock label="Open stops" value={summaryMetrics.openDowntimes} subLabel={`${summaryMetrics.unqualifiedDowntimes} unqualified`} />
|
|
|
|
<KpiBlock label={t("Open stops", "Arrets ouverts")} value={summaryMetrics.openDowntimes} subLabel={`${summaryMetrics.unqualifiedDowntimes} ${t("unqualified", "non qualifies")}`} />
|
|
|
|
<KpiBlock label="Output / scrap" value={`${summaryMetrics.producedQty} / ${summaryMetrics.scrapQty}`} subLabel="Pieces today" />
|
|
|
|
<KpiBlock label={t("Output / scrap", "Production / rebut")} value={`${summaryMetrics.producedQty} / ${summaryMetrics.scrapQty}`} subLabel={t("Pieces today", "Pieces du jour")} />
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<nav className="module-tabs" aria-label="Modules">
|
|
|
|
<nav className="module-tabs" aria-label={t("Modules", "Modules")}>
|
|
|
|
{moduleTabs.map((tab) => (
|
|
|
|
{moduleTabs.map((tab) => (
|
|
|
|
<button
|
|
|
|
<button
|
|
|
|
key={tab.key}
|
|
|
|
key={tab.key}
|
|
|
|
|