diff --git a/patches/osh-js+3.1.5.patch b/patches/osh-js+3.1.5.patch
new file mode 100644
index 00000000..ba577116
--- /dev/null
+++ b/patches/osh-js+3.1.5.patch
@@ -0,0 +1,55 @@
+diff --git a/node_modules/osh-js/core/parsers/JsonDataParser.js b/node_modules/osh-js/core/parsers/JsonDataParser.js
+index 8bb9f66..0c26f5c 100644
+--- a/node_modules/osh-js/core/parsers/JsonDataParser.js
++++ b/node_modules/osh-js/core/parsers/JsonDataParser.js
+@@ -58,10 +58,13 @@ var JsonDataParser = /** @class */ (function (_super) {
+ jsonData = input;
+ }
+ }
++ if (!jsonData) return [];
+ if (Array.isArray(jsonData)) {
+ for (var _i = 0, jsonData_1 = jsonData; _i < jsonData_1.length; _i++) {
+ var d = jsonData_1[_i];
+- d['timestamp'] = new Date(d[this.getTimeField()]).getTime() + this.props.timeShift;
++ if (d) {
++ d['timestamp'] = new Date(d[this.getTimeField()]).getTime() + this.props.timeShift;
++ }
+ }
+ return jsonData;
+ }
+diff --git a/node_modules/osh-js/source/core/mqtt/MqttProvider.js b/node_modules/osh-js/source/core/mqtt/MqttProvider.js
+index fb0b3a8..75a9ba2 100644
+--- a/node_modules/osh-js/source/core/mqtt/MqttProvider.js
++++ b/node_modules/osh-js/source/core/mqtt/MqttProvider.js
+@@ -166,10 +166,10 @@ class MqttProvider {
+ this.client.on('message', this.onMessage.bind(this));
+
+ this.client.on('offline', e => {
+- throw new Error(`The server ${that.endpoint} seems offline`);
++ console.warn(`The server ${that.endpoint} seems offline`);
+ });
+ this.client.on('error', e => {
+- throw new Error(error);
++ console.error(e);
+ });
+ }
+ }
+diff --git a/node_modules/osh-js/source/core/parsers/JsonDataParser.js b/node_modules/osh-js/source/core/parsers/JsonDataParser.js
+index 67eb795..57490eb 100644
+--- a/node_modules/osh-js/source/core/parsers/JsonDataParser.js
++++ b/node_modules/osh-js/source/core/parsers/JsonDataParser.js
+@@ -38,9 +38,13 @@ class JsonDataParser extends GenericParser {
+ }
+ }
+
++ if (!jsonData) return [];
++
+ if(Array.isArray(jsonData)) {
+ for(let d of jsonData) {
+- d['timestamp'] = new Date(d[this.getTimeField()]).getTime() + this.props.timeShift;
++ if (d) {
++ d['timestamp'] = new Date(d[this.getTimeField()]).getTime() + this.props.timeShift;
++ }
+ }
+ return jsonData;
+ } else {
diff --git a/src/app/(dashboard)/page.tsx b/src/app/(dashboard)/page.tsx
index d3a2b8ae..429bf68b 100644
--- a/src/app/(dashboard)/page.tsx
+++ b/src/app/(dashboard)/page.tsx
@@ -55,19 +55,19 @@ export default function DashboardPage() {
let laneDSColl = laneDSMap.get(laneid);
if(isGammaDataStream(ds))
- laneDSColl.addDS('gammaRT', rtDS);
+ laneDSColl?.addDS('gammaRT', rtDS);
if(isNeutronDataStream(ds))
- laneDSColl.addDS('neutronRT', rtDS);
+ laneDSColl?.addDS('neutronRT', rtDS);
if(isTamperDataStream(ds))
- laneDSColl.addDS('tamperRT', rtDS);
+ laneDSColl?.addDS('tamperRT', rtDS);
if(isConnectionDataStream(ds))
- laneDSColl.addDS('connectionRT', rtDS);
+ laneDSColl?.addDS('connectionRT', rtDS);
if(isThresholdDataStream(ds))
- laneDSColl.addDS('gammaTrshldRT', rtDS);
+ laneDSColl?.addDS('gammaTrshldRT', rtDS);
});
newStatusList.push({
diff --git a/src/app/_components/AlarmAudio.tsx b/src/app/_components/AlarmAudio.tsx
index e07138ca..67f437c5 100644
--- a/src/app/_components/AlarmAudio.tsx
+++ b/src/app/_components/AlarmAudio.tsx
@@ -5,6 +5,7 @@ import { useDispatch, useSelector } from "react-redux";
import Box from "@mui/material/Box";
import {Alert} from "@mui/material";
import { selectAlarmAudioVolume } from "@/lib/state/OSCARClientSlice";
+import { useLanguage } from '@/contexts/LanguageContext';
let alarmAudio: HTMLAudioElement | null = null;
@@ -17,6 +18,7 @@ export function getAlarmAudio() {
}
export default function AlarmAudio() {
+ const { t } = useLanguage();
const dispatch = useDispatch();
const savedVolume = useSelector(selectAlarmAudioVolume);
const triggerAlarm = useSelector((state: RootState) => selectTriggeredAlarm(state));
@@ -61,7 +63,7 @@ export default function AlarmAudio() {
{soundLocked && (
- Click anywhere to enable alarm sound
+ {t('clickAnywhereToEnableAlarmSound')}
)}
diff --git a/src/app/_components/LanguageSelector.tsx b/src/app/_components/LanguageSelector.tsx
index f98af022..e16110e7 100644
--- a/src/app/_components/LanguageSelector.tsx
+++ b/src/app/_components/LanguageSelector.tsx
@@ -1,15 +1,41 @@
"use client";
-import React, { useState } from 'react';
-import { Select, MenuItem, FormControl, InputLabel, SelectChangeEvent } from '@mui/material';
+import React from 'react';
+import { Select, MenuItem, FormControl, SelectChangeEvent } from '@mui/material';
import { useLanguage } from '@/contexts/LanguageContext';
import LanguageIcon from '@mui/icons-material/Language';
+const languages = [
+ { code: 'en', name: 'English' },
+ { code: 'es', name: 'Español' },
+ { code: 'fr', name: 'Français' },
+ { code: 'ar', name: 'العربية' },
+ { code: 'ru', name: 'Русский' },
+ { code: 'zh-CN', name: '简体中文' },
+ { code: 'ja', name: '日本語' },
+ { code: 'ko', name: '한국어' },
+ { code: 'ar-JO', name: 'العربية (الأردن)' },
+ { code: 'lv', name: 'Latviešu' },
+ { code: 'et', name: 'Eesti' },
+ { code: 'pt', name: 'Português' },
+ { code: 'de', name: 'Deutsch' },
+ { code: 'th', name: 'ไทย' },
+ { code: 'hi', name: 'हिन्दी' },
+ { code: 'bn', name: 'বাংলা' },
+ { code: 'pa-PK', name: 'پنجابی' },
+ { code: 'vi', name: 'Tiếng Việt' },
+ { code: 'yue', name: '粵語' },
+ { code: 'tr', name: 'Türkçe' },
+ { code: 'id', name: 'Bahasa Indonesia' },
+ { code: 'ur', name: 'اردو' },
+ { code: 'it', name: 'Italiano' }
+];
+
export default function LanguageSelector() {
const { language, setLanguage } = useLanguage();
const handleChange = (event: SelectChangeEvent) => {
- setLanguage(event.target.value as 'en' | 'es' | 'fr');
+ setLanguage(event.target.value as any);
};
return (
@@ -19,18 +45,28 @@ export default function LanguageSelector() {
value={language}
onChange={handleChange}
displayEmpty
- inputProps={{ 'aria-label': 'Without label' }}
- startAdornment={}
+ inputProps={{ 'aria-label': 'Language' }}
+ startAdornment={}
renderValue={(selected) => {
- if (selected === 'en') return 'English';
- if (selected === 'es') return 'Español';
- if (selected === 'fr') return 'Français';
- return selected;
+ const lang = languages.find(l => l.code === selected);
+ return lang ? lang.name : selected;
+ }}
+ sx={{
+ color: 'inherit',
+ '& .MuiSelect-select': {
+ paddingTop: '4px',
+ paddingBottom: '4px',
+ },
+ '&:before': { borderBottomColor: 'rgba(255, 255, 255, 0.7)' },
+ '&:after': { borderBottomColor: 'white' },
+ '& .MuiSvgIcon-root': { color: 'inherit' }
}}
>
-
-
-
+ {languages.map((lang) => (
+
+ ))}
);
diff --git a/src/app/_components/Navbar.tsx b/src/app/_components/Navbar.tsx
index 86e5eaec..5aa328fd 100644
--- a/src/app/_components/Navbar.tsx
+++ b/src/app/_components/Navbar.tsx
@@ -201,7 +201,7 @@ export default function Navbar({children}: { children: React.ReactNode }) {
{savedVolume === 0 ? : }
@@ -256,7 +256,7 @@ export default function Navbar({children}: { children: React.ReactNode }) {
([]);
@@ -95,7 +97,7 @@ export default function AdjudicationDetail(props: { event: EventTableData }) {
const occupancyObservation = await query.nextPage();
if (!occupancyObservation || occupancyObservation.length === 0) {
- setAdjSnackMsg('Cannot find observation to adjudicate. Please try again.');
+ setAdjSnackMsg(t('cannotFindObservation'));
setColorStatus('error');
setOpenSnack(true);
return;
@@ -106,7 +108,7 @@ export default function AdjudicationDetail(props: { event: EventTableData }) {
} catch (err) {
console.error(err);
- setAdjSnackMsg('Error loading observation.');
+ setAdjSnackMsg(t('errorLoadingObservation'));
setColorStatus('error');
setOpenSnack(true);
}
@@ -188,7 +190,7 @@ export default function AdjudicationDetail(props: { event: EventTableData }) {
const sendAdjudicationData = async () => {
if(adjData.adjudicationCode === null || !adjData.adjudicationCode || adjData.adjudicationCode === AdjudicationCodes.codes[0]){
- setAdjSnackMsg("Please selected a valid adjudication code before submitting.");
+ setAdjSnackMsg(t('selectValidCode'));
setColorStatus('error');
setOpenSnack(true)
return;
@@ -234,7 +236,7 @@ export default function AdjudicationDetail(props: { event: EventTableData }) {
const occupancyObservation = await query.nextPage();
if (!occupancyObservation) {
- setAdjSnackMsg('Cannot find observation to adjudicate. Please try again.');
+ setAdjSnackMsg(t('cannotFindObservation'));
setColorStatus('error')
setOpenSnack(true);
return;
@@ -264,7 +266,7 @@ export default function AdjudicationDetail(props: { event: EventTableData }) {
);
if (!response.ok) {
- setAdjSnackMsg('Adjudication failed to submit.')
+ setAdjSnackMsg(t('adjudicationFail'))
setColorStatus('error')
return;
}
@@ -274,11 +276,11 @@ export default function AdjudicationDetail(props: { event: EventTableData }) {
dispatch(setSelectedEvent(props.event));
dispatch(setAdjudicatedEventId(props.event.id));
- setAdjSnackMsg('Adjudication successful for Occupancy ID: ' + props.event.occupancyCount);
+ setAdjSnackMsg(t('adjudicationSuccess') + props.event.occupancyCount);
setColorStatus('success')
}catch(error){
- setAdjSnackMsg('Adjudication failed to submit.')
+ setAdjSnackMsg(t('adjudicationFail'))
setColorStatus('error')
}finally{
setShouldFetchLogs(true);
@@ -301,7 +303,7 @@ export default function AdjudicationDetail(props: { event: EventTableData }) {
- Adjudication
+ {t('adjudicationTitle')}
- Adjudication Report Form
+ {t('adjudicationReportForm')}
- Upload Files
+ {t('uploadFiles')}
- Submit
+ {t('submit')}
);
-}
\ No newline at end of file
+}
diff --git a/src/app/_components/adjudication/AdjudicationLog.tsx b/src/app/_components/adjudication/AdjudicationLog.tsx
index 9baea707..72471f3f 100644
--- a/src/app/_components/adjudication/AdjudicationLog.tsx
+++ b/src/app/_components/adjudication/AdjudicationLog.tsx
@@ -5,13 +5,14 @@ import AdjudicationData from "@/lib/data/oscar/adjudication/Adjudication";
import {EventTableData} from "@/lib/data/oscar/TableHelpers";
import {DataSourceContext} from "@/app/contexts/DataSourceContext";
import {DataGrid, GridColDef} from "@mui/x-data-grid";
-import { Stack, Typography} from "@mui/material";
+import { Box, Stack, Typography} from "@mui/material";
import {LaneMapEntry} from "@/lib/data/oscar/LaneCollection";
import {isAdjudicationControlStream} from "@/lib/data/oscar/Utilities";
import ControlStream from "osh-js/source/core/consysapi/controlstream/ControlStream";
import {AdjudicationCodes} from "@/lib/data/oscar/adjudication/models/AdjudicationConstants";
import ControlStreamFilter from "osh-js/source/core/consysapi/controlstream/ControlStreamFilter";
import { Dialog, DialogTitle, DialogContent } from "@mui/material";
+import { useLanguage } from '@/contexts/LanguageContext';
@@ -20,6 +21,7 @@ export default function AdjudicationLog(props: {
shouldFetch: boolean;
onFetch: () => void;
}) {
+ const { t } = useLanguage();
const locale = navigator.language || 'en-US';
const laneMapRef = useContext(DataSourceContext).laneMapRef;
@@ -35,19 +37,19 @@ export default function AdjudicationLog(props: {
const logColumns: GridColDef[] = [
{
field: 'occupancyCount',
- headerName: 'Occupancy ID',
+ headerName: t('occupancyId'),
width: 175,
type: 'string',
},
{
field: 'username',
- headerName: 'User',
+ headerName: t('user'),
width: 150,
type: 'string',
},
{
field: 'time',
- headerName: 'Timestamp',
+ headerName: t('timestamp'),
width: 200,
type: 'string',
valueFormatter: (params) => (new Date(params)).toLocaleString(locale, {
@@ -61,7 +63,7 @@ export default function AdjudicationLog(props: {
},
{
field: 'feedback',
- headerName: 'Feedback',
+ headerName: t('feedback'),
width: 250,
type: 'string',
renderCell: (params) => {
@@ -80,7 +82,7 @@ export default function AdjudicationLog(props: {
style={{ color: "#1976d2", border: "none", background: "none", cursor: "pointer" }}
onClick={() => setFeedbackDialog({ open: true, text: fullText })}
>
- Read more
+ {t('readMore')}
)}
@@ -89,12 +91,12 @@ export default function AdjudicationLog(props: {
},
{
field: 'secondaryInspectionStatus',
- headerName: 'Secondary Inspection Status',
+ headerName: t('secondaryInspectionStatus'),
width: 200
},
{
field: 'adjudicationCode',
- headerName: 'Adjudication Code',
+ headerName: t('adjudicationCode'),
width: 400,
valueGetter: (value, row) => {
return row.adjudicationCode.label
@@ -102,7 +104,7 @@ export default function AdjudicationLog(props: {
},
{
field: 'isotopes',
- headerName: 'Isotopes',
+ headerName: t('isotopes'),
width: 200,
valueGetter: (value) => {
if (value === "") return "Unknown";
@@ -111,13 +113,13 @@ export default function AdjudicationLog(props: {
},
{
field: 'filePaths',
- headerName: 'FilePaths',
+ headerName: t('filePaths'),
width: 200,
type: 'string'
},
{
field: 'vehicleId',
- headerName: 'Vehicle ID',
+ headerName: t('vehicleId'),
width: 150,
valueGetter: (value) => {
if (value === "") return "Unknown";
@@ -198,28 +200,30 @@ export default function AdjudicationLog(props: {
<>
- Logged Adjudications
+ {t('loggedAdjudications')}
-
+
+ }}
+ pageSizeOptions={[5, 10, 25, 50, 100]}
+ disableRowSelectionOnClick={true}
+ />
+