| @@ -112,6 +112,22 @@ export default function TestingPage() { | |||||
| const [bomByItemCodeResult, setBomByItemCodeResult] = useState<string | null>( | const [bomByItemCodeResult, setBomByItemCodeResult] = useState<string | null>( | ||||
| null, | null, | ||||
| ); | ); | ||||
| const whatsAppTestInFlightRef = useRef(false); | |||||
| const [whatsAppVar1, setWhatsAppVar1] = useState("12/1"); | |||||
| const [whatsAppVar2, setWhatsAppVar2] = useState("3pm"); | |||||
| const [whatsAppTestLoading, setWhatsAppTestLoading] = useState(false); | |||||
| const [whatsAppTestResult, setWhatsAppTestResult] = useState<string | null>( | |||||
| null, | |||||
| ); | |||||
| const emailTestInFlightRef = useRef(false); | |||||
| const [emailTestSubject, setEmailTestSubject] = useState( | |||||
| "FPSMS M18 sync alert [TEST]", | |||||
| ); | |||||
| const [emailTestMessage, setEmailTestMessage] = useState( | |||||
| "FPSMS sync alert test message from /testing page.", | |||||
| ); | |||||
| const [emailTestLoading, setEmailTestLoading] = useState(false); | |||||
| const [emailTestResult, setEmailTestResult] = useState<string | null>(null); | |||||
| const onpackPayload = useMemo( | const onpackPayload = useMemo( | ||||
| () => buildOnPackJobOrdersPayload(onpackJobOrders), | () => buildOnPackJobOrdersPayload(onpackJobOrders), | ||||
| @@ -353,6 +369,70 @@ export default function TestingPage() { | |||||
| } | } | ||||
| }; | }; | ||||
| const handleWhatsAppSyncAlertTest = async () => { | |||||
| if (whatsAppTestInFlightRef.current) return; | |||||
| whatsAppTestInFlightRef.current = true; | |||||
| setWhatsAppTestLoading(true); | |||||
| setWhatsAppTestResult(null); | |||||
| try { | |||||
| const params = new URLSearchParams(); | |||||
| const v1 = whatsAppVar1.trim(); | |||||
| const v2 = whatsAppVar2.trim(); | |||||
| if (v1) params.set("var1", v1); | |||||
| if (v2) params.set("var2", v2); | |||||
| const qs = params.toString(); | |||||
| const response = await clientAuthFetch( | |||||
| `${NEXT_PUBLIC_API_URL}/scheduler/trigger/sync-alert-test-whatsapp${qs ? `?${qs}` : ""}`, | |||||
| { method: "GET" }, | |||||
| ); | |||||
| if (response.status === 401 || response.status === 403) return; | |||||
| const text = await response.text(); | |||||
| if (!response.ok) { | |||||
| setWhatsAppTestResult(`HTTP ${response.status}\n\n${text}`); | |||||
| return; | |||||
| } | |||||
| setWhatsAppTestResult(text); | |||||
| } catch (e) { | |||||
| const msg = e instanceof Error ? e.message : String(e); | |||||
| setWhatsAppTestResult(`Error: ${msg}`); | |||||
| } finally { | |||||
| setWhatsAppTestLoading(false); | |||||
| whatsAppTestInFlightRef.current = false; | |||||
| } | |||||
| }; | |||||
| const handleSyncAlertTestEmail = async () => { | |||||
| if (emailTestInFlightRef.current) return; | |||||
| emailTestInFlightRef.current = true; | |||||
| setEmailTestLoading(true); | |||||
| setEmailTestResult(null); | |||||
| try { | |||||
| const params = new URLSearchParams(); | |||||
| const subj = emailTestSubject.trim(); | |||||
| const msg = emailTestMessage.trim(); | |||||
| if (subj) params.set("subject", subj); | |||||
| if (msg) params.set("message", msg); | |||||
| const qs = params.toString(); | |||||
| const response = await clientAuthFetch( | |||||
| `${NEXT_PUBLIC_API_URL}/scheduler/trigger/sync-alert-test-email${qs ? `?${qs}` : ""}`, | |||||
| { method: "GET" }, | |||||
| ); | |||||
| if (response.status === 401 || response.status === 403) return; | |||||
| const text = await response.text(); | |||||
| if (!response.ok) { | |||||
| setEmailTestResult(`HTTP ${response.status}\n\n${text}`); | |||||
| return; | |||||
| } | |||||
| setEmailTestResult(text); | |||||
| } catch (e) { | |||||
| const msg = e instanceof Error ? e.message : String(e); | |||||
| setEmailTestResult(`Error: ${msg}`); | |||||
| } finally { | |||||
| setEmailTestLoading(false); | |||||
| emailTestInFlightRef.current = false; | |||||
| } | |||||
| }; | |||||
| const handleBomLookupByItemCode = async () => { | const handleBomLookupByItemCode = async () => { | ||||
| if (bomByItemCodeInFlightRef.current) return; | if (bomByItemCodeInFlightRef.current) return; | ||||
| const code = bomByItemCodeInput.trim(); | const code = bomByItemCodeInput.trim(); | ||||
| @@ -440,6 +520,7 @@ export default function TestingPage() { | |||||
| <Tab label="3. Laser Bag2 自動送" /> | <Tab label="3. Laser Bag2 自動送" /> | ||||
| <Tab label="4. 批號標籤列印" /> | <Tab label="4. 批號標籤列印" /> | ||||
| <Tab label="5. M18 BOM shop" /> | <Tab label="5. M18 BOM shop" /> | ||||
| <Tab label="6. Sync alert email" /> | |||||
| </Tabs> | </Tabs> | ||||
| <TabPanel value={tabValue} index={0}> | <TabPanel value={tabValue} index={0}> | ||||
| @@ -888,6 +969,65 @@ export default function TestingPage() { | |||||
| ) : null} | ) : null} | ||||
| </Section> | </Section> | ||||
| </TabPanel> | </TabPanel> | ||||
| <TabPanel value={tabValue} index={5}> | |||||
| <Section title="6. M18 sync alert email"> | |||||
| <Alert severity="info" sx={{ mb: 2 }}> | |||||
| Production sync errors email{" "} | |||||
| <strong>[email protected]</strong> and{" "} | |||||
| <strong>[email protected]</strong> (see{" "} | |||||
| <code>scheduler.sync-alert.email.to-addresses</code>). WhatsApp/Twilio | |||||
| is disabled. SMTP from DB <code>MAIL.smtp.*</code> (e.g. Gmail{" "} | |||||
| <code>[email protected]</code> + app password). | |||||
| </Alert> | |||||
| <Stack spacing={2} sx={{ mb: 2, maxWidth: 720 }}> | |||||
| <TextField | |||||
| size="small" | |||||
| label="Email subject" | |||||
| value={emailTestSubject} | |||||
| onChange={(e) => setEmailTestSubject(e.target.value)} | |||||
| fullWidth | |||||
| /> | |||||
| <TextField | |||||
| label="Test message" | |||||
| value={emailTestMessage} | |||||
| onChange={(e) => setEmailTestMessage(e.target.value)} | |||||
| multiline | |||||
| minRows={3} | |||||
| fullWidth | |||||
| /> | |||||
| <Stack direction="row" spacing={2} sx={{ flexWrap: "wrap" }}> | |||||
| <Button | |||||
| variant="contained" | |||||
| color="primary" | |||||
| onClick={() => void handleSyncAlertTestEmail()} | |||||
| disabled={emailTestLoading} | |||||
| > | |||||
| {emailTestLoading ? "Sending…" : "Send test email message"} | |||||
| </Button> | |||||
| </Stack> | |||||
| </Stack> | |||||
| {emailTestResult ? ( | |||||
| <TextField | |||||
| fullWidth | |||||
| multiline | |||||
| minRows={2} | |||||
| label="Email test response" | |||||
| value={emailTestResult} | |||||
| InputProps={{ readOnly: true }} | |||||
| sx={{ fontFamily: "monospace", mb: 3 }} | |||||
| /> | |||||
| ) : null} | |||||
| <Typography variant="body2" color="textSecondary" sx={{ mt: 1 }}> | |||||
| <code> | |||||
| GET /scheduler/trigger/sync-alert-test-email?subject=…&message=… | |||||
| </code> | |||||
| <br /> | |||||
| <code>GET /scheduler/trigger/sync-alert-check</code> — run alert rules | |||||
| now (empty = OK) | |||||
| </Typography> | |||||
| </Section> | |||||
| </TabPanel> | |||||
| </Box> | </Box> | ||||
| ); | ); | ||||
| } | } | ||||