Skip to content

Commit

Permalink
Merge branch 'v1.5.00'
Browse files Browse the repository at this point in the history
  • Loading branch information
mark-prins committed Nov 16, 2023
2 parents 84da189 + 4edd6ca commit b1bb2ad
Show file tree
Hide file tree
Showing 332 changed files with 13,673 additions and 907 deletions.
13 changes: 13 additions & 0 deletions client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ The functional areas are in `packages` as noted below. Mostly the packages are s
- host: the wrapper for the application which then hosts individual packages. Components in here include the framework - navigation, footer, drawer, app bar - and pages such as login
- system: packages which are re-used by other packages, such as `item` and `name` but are application-specific, which is why they are in here and not in `common` which is trying to be more application agnostic.

Packages should have the following hierarchy:

```mermaid
graph RL;
common --> config
system --> common
others["invoices, inventory, dashboard, etc"] --> system
host-->others;
```

Packages shouldn't have circular dependencies to other packages, i.e. packages can only import packages on their left (as shown in the diagram).
For example, the `common` packages can be imported by the `system` package but the `common` package can't import the `system` or the `invoices` package.

Code is separated into functional areas, so that we can isolate bundles to these areas. These are not the same as areas in the site; there is a separation between code organisation and UI organisation - for example the site has `Distribution > Outbound Shipments` and `Replenishment > Inbound Shipments` and both of these are found in the `invoices` package in the code, as they are functionally very similar while being logically different.

Within each area you'll see a similar pattern of this for tabular data, which is pretty much everything:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Enumeration;

import javax.net.ssl.SSLHandshakeException;

Expand Down Expand Up @@ -344,15 +347,34 @@ public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
});
}

// Attempt to get a non-loopback address for the local server
// and fallback to loopback if there is an error
private String getLocalAddress(NsdServiceInfo serviceInfo){
try {
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
NetworkInterface intf = en.nextElement();
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress() && inetAddress.isSiteLocalAddress()) {
return inetAddress.getHostAddress();
}
}
}
} catch (SocketException ex) {
Log.e(OM_SUPPLY, ex.toString());
}
return serviceInfo.getHost().getHostAddress();
}
private JSObject serviceInfoToObject(NsdServiceInfo serviceInfo) {
String serverHardwareId = parseAttribute(serviceInfo, discoveryConstants.HARDWARE_ID_KEY);
Boolean isLocal = serverHardwareId.equals(discoveryConstants.hardwareId);
return new JSObject()
.put("protocol", parseAttribute(serviceInfo, discoveryConstants.PROTOCOL_KEY))
.put("clientVersion", parseAttribute(serviceInfo, discoveryConstants.CLIENT_VERSION_KEY))
.put("port", serviceInfo.getPort())
.put("ip", serviceInfo.getHost().getHostAddress())
.put("ip", isLocal ? getLocalAddress(serviceInfo) : serviceInfo.getHost().getHostAddress())
.put("hardwareId", serverHardwareId)
.put("isLocal", serverHardwareId.equals(discoveryConstants.hardwareId));
.put("isLocal", isLocal);

}

Expand Down Expand Up @@ -472,6 +494,13 @@ public class FrontEndHost {
JSObject data;

public FrontEndHost(JSObject data) {
String ip = data.getString("ip");
// attempt to translate loopback addresses to an actual IP address
// so that we can display the local server IP for users to connect to the API
if (data.getBool("isLocal") && (ip.equals("127.0.0.1") || ip.equals("localhost"))) {
NsdServiceInfo serviceInfo = createLocalServiceInfo();
data.put("ip", getLocalAddress(serviceInfo));
}
this.data = data;
}

Expand Down
17 changes: 17 additions & 0 deletions client/packages/coldchain/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Cold chain

### Overview

Cold chain Domain

### Intentions

The intention is that this package is responsible for cold chain domain features:

- Temperature logs
- Monitoring
- CCE

### Tips & Things to keep in mind

### Future considerations
19 changes: 19 additions & 0 deletions client/packages/coldchain/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@openmsupply-client/coldchain",
"version": "0.0.0",
"sideEffects": false,
"private": true,
"main": "./src/index.ts",
"devDependencies": {},
"scripts": {
"tsc": "tsc",
"eslint": "eslint ./src"
},
"dependencies": {
"@openmsupply-client/common": "^0.0.1",
"@openmsupply-client/config": "^0.0.0",
"@openmsupply-client/system": "^0.0.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
167 changes: 167 additions & 0 deletions client/packages/coldchain/src/ColdchainNotification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import React, { PropsWithChildren } from 'react';
import {
BaseButton,
Box,
RouteBuilder,
TemperatureBreachSortFieldInput,
Typography,
useMatch,
useNavigate,
useUrlQuery,
} from '@openmsupply-client/common';
import { useFormatDateTime, useTranslation } from '@common/intl';
import { CircleAlertIcon } from '@common/icons';
import { alpha, useTheme } from '@common/styles';
import { AppRoute } from '@openmsupply-client/config';
import {
TemperatureBreachFragment,
useTemperatureBreach,
} from './Monitoring/api/TemperatureBreach';

const Text: React.FC<PropsWithChildren> = ({ children }) => (
<Typography
component="div"
sx={{
fontSize: '14px',
display: 'flex',
alignContent: 'center',
flexWrap: 'wrap',
}}
>
{children}
</Typography>
);

const Separator = () => (
<Text>
<Typography paddingX={0.5}>|</Typography>
</Text>
);

const DetailButton = ({ breach }: { breach: TemperatureBreachFragment }) => {
const t = useTranslation('coldchain');
const navigate = useNavigate();
const { urlQuery } = useUrlQuery();
const currentTab = (urlQuery['tab'] as string) ?? '';
const isColdchain = useMatch(
RouteBuilder.create(AppRoute.Coldchain).addWildCard().build()
);

if (isColdchain && currentTab === t('label.breaches')) return null;

return (
<BaseButton
variant="contained"
style={{ height: 32 }}
onClick={() =>
navigate(
RouteBuilder.create(AppRoute.Coldchain)
.addPart(AppRoute.Monitoring)
.addQuery({ tab: t('label.breaches') })
.addQuery({
sort: TemperatureBreachSortFieldInput.StartDatetime,
})
.addQuery({ acknowledged: false })
.addQuery({ 'sensor.name': breach.sensor?.name ?? '' })
.build()
)
}
>
{t('button.view-details')}
</BaseButton>
);
};
const Location = ({ breach }: { breach: TemperatureBreachFragment }) => {
const t = useTranslation('coldchain');

if (!breach?.location?.name) return null;
return (
<>
<Separator />
{!!breach?.location?.name && (
<Text>
{t('message.location')}
<b style={{ paddingLeft: 4 }}>{breach.location.name}</b>
</Text>
)}
</>
);
};

export const ColdchainNotification = () => {
const theme = useTheme();
const t = useTranslation('coldchain');
const { data: breaches } = useTemperatureBreach.document.notifications({
first: 1,
offset: 0,
sortBy: { key: 'startDatetime', direction: 'desc', isDesc: true },
filterBy: { acknowledged: false },
});
const { localisedDistanceToNow } = useFormatDateTime();
const breach = breaches?.nodes?.[0];

if (!breach) return null;

return (
<Box
sx={{
borderBottom: '1px solid',
borderBottomColor: 'primary.main',
backgroundColor: alpha(theme.palette.primary.main, 0.075),
flex: '0 0 54px',
display: 'flex',
paddingLeft: 2,
alignContent: 'center',
flexWrap: 'wrap',
}}
>
<Box
sx={{
display: 'flex',
alignContent: 'center',
flexWrap: 'wrap',
marginRight: 1,
}}
>
<CircleAlertIcon
fill={theme.palette.error.main}
sx={{
color: 'background.white',
}}
width={27}
height={27}
/>
</Box>
<Text>
<b>
{t('message.notification-breach-detected', {
time: localisedDistanceToNow(breach.startDatetime),
})}
</b>
</Text>
<Separator />
<Text>
{t('message.last-temperature', {
temperature: breach.maxOrMinTemperature,
})}
</Text>
<Separator />
<Text>
{t('message.device')}
<b style={{ paddingLeft: 4 }}>{breach.sensor?.name}</b>
</Text>
<Location breach={breach} />
<Box
sx={{
justifyContent: 'flex-end',
display: 'flex',
flex: 1,
marginRight: 2,
height: '32px',
}}
>
<DetailButton breach={breach} />
</Box>
</Box>
);
};
19 changes: 19 additions & 0 deletions client/packages/coldchain/src/ColdchainService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, { FC } from 'react';
import { RouteBuilder, Routes, Route } from '@openmsupply-client/common';
import { AppRoute } from '@openmsupply-client/config';
import { ListView } from './Sensor/ListView';
import { ListView as MonitoringListView } from './Monitoring/ListView';

export const ColdchainService: FC = () => {
const monitoringRoute = RouteBuilder.create(AppRoute.Monitoring).build();
const sensorRoute = RouteBuilder.create(AppRoute.Sensors).build();

return (
<Routes>
<Route path={monitoringRoute} element={<MonitoringListView />} />
<Route path={sensorRoute} element={<ListView />} />
</Routes>
);
};

export default ColdchainService;
43 changes: 43 additions & 0 deletions client/packages/coldchain/src/Monitoring/ListView/ListView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React, { FC } from 'react';
import { DetailTabs } from '@common/components';
import { TemperatureLogList } from './TemperatureLog';
import { useTranslation } from '@common/intl';
import {
TemperatureBreachSortFieldInput,
TemperatureLogSortFieldInput,
} from '@openmsupply-client/common';
import { TemperatureBreachList } from './TemperatureBreach';
import { TemperatureChart } from './TemperatureChart';

export const ListView: FC = () => {
const t = useTranslation('coldchain');

const tabs = [
{
Component: <TemperatureChart />,
value: t('label.chart'),
sort: {
key: TemperatureLogSortFieldInput.Datetime,
dir: 'desc' as 'desc' | 'asc',
},
},
{
Component: <TemperatureBreachList />,
value: t('label.breaches'),
sort: {
key: TemperatureBreachSortFieldInput.StartDatetime,
dir: 'desc' as 'desc' | 'asc',
},
},
{
Component: <TemperatureLogList />,
value: t('label.log'),
sort: {
key: TemperatureLogSortFieldInput.Datetime,
dir: 'desc' as 'desc' | 'asc',
},
},
];

return <DetailTabs tabs={tabs} />;
};
Loading

0 comments on commit b1bb2ad

Please sign in to comment.