Skip to content

Commit f299355

Browse files
RRanathccbc-service-account
authored andcommitted
feat: add history table to GIS coverage
1 parent e254ea3 commit f299355

File tree

6 files changed

+450
-7
lines changed

6 files changed

+450
-7
lines changed
+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { DateTime } from 'luxon';
2+
import {
3+
DropdownOption,
4+
MaterialReactTable,
5+
MRT_ColumnDef,
6+
useMaterialReactTable,
7+
} from 'material-react-table';
8+
import { useMemo } from 'react';
9+
import { TableCellProps } from '@mui/material';
10+
import styled from 'styled-components';
11+
import * as Sentry from '@sentry/nextjs';
12+
import { filterOutNullishs } from 'components/AnalystDashboard/AllDashboard';
13+
import ClearFilters from 'components/Table/ClearFilters';
14+
import DateFilter from '../../Table/Filters/DateFilter';
15+
16+
const StyledLink = styled.button`
17+
color: ${(props) => props.theme.color.links};
18+
text-decoration-line: underline;
19+
word-break: break-word;
20+
`;
21+
22+
const handleDownload = async (uuid, fileName) => {
23+
const url = `/api/s3/download/${uuid}/${fileName}`;
24+
await fetch(url)
25+
.then((response) => response.json())
26+
.then((response) => {
27+
window.open(response, '_blank');
28+
});
29+
};
30+
31+
const fileCell = ({ cell }) => {
32+
return (
33+
<StyledLink
34+
data-testid="history-file-link"
35+
onClick={(e) => {
36+
e.preventDefault();
37+
handleDownload(cell.row.original?.uuid, cell.getValue()).catch(
38+
(err) => {
39+
Sentry.captureException(err);
40+
}
41+
);
42+
}}
43+
>
44+
{cell.getValue()}
45+
</StyledLink>
46+
);
47+
};
48+
49+
const dateUploadedCell = ({ cell }) => {
50+
return DateTime.fromISO(cell.row.original.createdAt)
51+
.setZone('America/Los_Angeles')
52+
.toLocaleString(DateTime.DATETIME_FULL);
53+
};
54+
55+
const GisHistory = ({ historyTableList }) => {
56+
const columns = useMemo<MRT_ColumnDef<any>[]>(() => {
57+
const uniqueUsers = [
58+
...new Set(historyTableList.map((historyItem) => historyItem.name)),
59+
].filter(filterOutNullishs);
60+
61+
return [
62+
{
63+
accessorKey: 'name',
64+
header: 'Uploaded by',
65+
size: 300,
66+
filterVariant: 'select',
67+
filterSelectOptions: (uniqueUsers as DropdownOption[]) || [],
68+
},
69+
{
70+
accessorKey: 'file',
71+
header: 'File',
72+
size: 450,
73+
Cell: fileCell,
74+
},
75+
{
76+
accessorKey: 'createdAt',
77+
header: 'Date uploaded',
78+
Cell: dateUploadedCell,
79+
size: 300,
80+
filterVariant: 'date',
81+
Filter: DateFilter,
82+
sortingFn: 'datetime',
83+
filterFn: (row, _columnIds, filterValue) => {
84+
if (!filterValue) return true;
85+
const rowDate = DateTime.fromISO(row.original.createdAt).toFormat(
86+
'yyyy-MM-dd'
87+
);
88+
return rowDate === filterValue;
89+
},
90+
},
91+
];
92+
}, [historyTableList]);
93+
94+
const muiTableBodyCellProps = (): TableCellProps => {
95+
return {
96+
sx: {
97+
padding: '8px !important',
98+
},
99+
};
100+
};
101+
102+
const muiTableHeadCellProps = {
103+
sx: {
104+
wordBreak: 'break-word',
105+
texOverflow: 'wrap',
106+
'.Mui-TableHeadCell-Content-Labels': {
107+
width: '100%',
108+
justifyContent: 'space-between',
109+
},
110+
'.Mui-TableHeadCell-Content-Wrapper ': {
111+
overflow: 'hidden',
112+
textOverflow: 'clip',
113+
padding: '0',
114+
},
115+
'&:last-child': {
116+
paddingRight: '16px',
117+
},
118+
'&:first-child': {
119+
paddingLeft: '16px',
120+
},
121+
},
122+
};
123+
124+
const table = useMaterialReactTable({
125+
columns,
126+
data: historyTableList,
127+
enablePagination: false,
128+
enableBottomToolbar: false,
129+
enableGlobalFilter: false,
130+
muiTableContainerProps: {
131+
sx: {
132+
padding: '0 8px 8px 8px',
133+
maxHeight: '100%',
134+
},
135+
},
136+
muiTableBodyRowProps: {
137+
sx: {
138+
boxShadow: '0 3px 3px -2px #c4c4c4',
139+
},
140+
},
141+
muiTableBodyCellProps,
142+
muiTableHeadCellProps,
143+
renderTopToolbarCustomActions: () => (
144+
<ClearFilters table={table} filters={table.getState().columnFilters} />
145+
),
146+
});
147+
return (
148+
<>
149+
<h2>Upload History</h2>
150+
<MaterialReactTable table={table} />
151+
</>
152+
);
153+
};
154+
155+
export default GisHistory;

app/components/AnalystDashboard/AllDashboard.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ const ApplicantStatusCell = ({ cell }) => {
154154
return <StatusPill status={analystStatus} styles={statusStyles} />;
155155
};
156156

157-
const filterOutNullishs = (val) => val !== undefined && val !== null;
157+
export const filterOutNullishs = (val) => val !== undefined && val !== null;
158158

159159
const toLabelValuePair = (value) =>
160160
value ? { label: value, value } : { label: 'Unassigned', value: ' ' };

app/components/Table/ClearFilters.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ const ClearFilters: React.FC<Props> = ({
3030
filters.filter((f) => f.id === 'program' && (f.value as any[]).length < 3)
3131
.length > 0;
3232

33-
const isGlobalFilterPresent = table.getState().globalFilter !== '';
33+
const isGlobalFilterPresent =
34+
table.getState().globalFilter && table.getState().globalFilter !== '';
3435

3536
return (
3637
<Button
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
2+
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
3+
import { DesktopDatePicker } from '@mui/x-date-pickers/DesktopDatePicker';
4+
import CloseIcon from '@mui/icons-material/Close';
5+
import { enUS } from '@mui/x-date-pickers/locales';
6+
import dayjs from 'dayjs';
7+
import styled from 'styled-components';
8+
9+
const StyledClearButton = styled.button`
10+
color: ${(props) => props.theme.color.borderGrey};
11+
`;
12+
13+
const DateFilter = ({ column }) => {
14+
const clearButton = () => (
15+
<StyledClearButton
16+
type="button"
17+
onClick={() => column.setFilterValue(null)}
18+
>
19+
<CloseIcon />
20+
</StyledClearButton>
21+
);
22+
23+
const rawFilterVal = column.getFilterValue();
24+
const filterVal = rawFilterVal ? dayjs(rawFilterVal) : null;
25+
26+
return (
27+
<LocalizationProvider
28+
localeText={
29+
enUS.components.MuiLocalizationProvider.defaultProps.localeText
30+
}
31+
dateAdapter={AdapterDayjs}
32+
>
33+
<DesktopDatePicker
34+
value={filterVal}
35+
onChange={(newValue) =>
36+
column.setFilterValue(newValue ? newValue.format('YYYY-MM-DD') : null)
37+
}
38+
slotProps={{
39+
textField: {
40+
variant: 'standard',
41+
placeholder: 'Filter by Date (YYYY-MM-DD)',
42+
fullWidth: true,
43+
},
44+
}}
45+
slots={{
46+
openPickerButton: column.getFilterValue() ? clearButton : undefined,
47+
}}
48+
format="YYYY-MM-DD"
49+
/>
50+
</LocalizationProvider>
51+
);
52+
};
53+
54+
export default DateFilter;

app/pages/analyst/gis/coverages.tsx

+32-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react';
1+
import { useMemo, useState } from 'react';
22
import { usePreloadedQuery } from 'react-relay/hooks';
33
import { withRelay, RelayProps } from 'relay-nextjs';
44
import { graphql } from 'react-relay';
@@ -14,10 +14,26 @@ import * as Sentry from '@sentry/nextjs';
1414
import Tabs from 'components/Analyst/GIS/Tabs';
1515
import checkFileType from 'utils/checkFileType';
1616
import { useUnsavedChanges } from 'components/UnsavedChangesProvider';
17+
import GisHistory from 'components/Analyst/GIS/GisHistory';
1718
import config from '../../../config';
1819

1920
const getCoveragesQuery = graphql`
2021
query coveragesQuery {
22+
allRecordVersions(
23+
orderBy: RECORD_ID_DESC
24+
condition: { tableName: "coverages_upload" }
25+
) {
26+
nodes {
27+
record
28+
tableName
29+
createdBy
30+
createdAt
31+
ccbcUserByCreatedBy {
32+
familyName
33+
givenName
34+
}
35+
}
36+
}
2137
session {
2238
sub
2339
...DashboardTabs_query
@@ -92,7 +108,7 @@ const validateFile = (file: globalThis.File) => {
92108
return { isValid: true, error: null };
93109
};
94110

95-
const CoveragesTab = () => {
111+
const CoveragesTab = ({ historyList }) => {
96112
const [selectedFile, setSelectedFile] = useState<File>();
97113
const [isUploading, setIsUploading] = useState(false);
98114
const [uploadSuccess, setUploadSuccess] = useState(false);
@@ -145,6 +161,17 @@ const CoveragesTab = () => {
145161
}
146162
};
147163

164+
const historyTableList = useMemo(
165+
() =>
166+
historyList.map((historyItem) => ({
167+
name: `${historyItem.ccbcUserByCreatedBy.givenName} ${historyItem.ccbcUserByCreatedBy.familyName}`,
168+
file: COVERAGES_FILE_NAME,
169+
createdAt: historyItem.createdAt,
170+
uuid: historyItem.record.uuid,
171+
})),
172+
[historyList]
173+
);
174+
148175
return (
149176
<div>
150177
<Tabs />
@@ -201,6 +228,7 @@ const CoveragesTab = () => {
201228
</StyledSuccess>
202229
)}
203230
</div>
231+
<GisHistory historyTableList={historyTableList} />
204232
</div>
205233
);
206234
};
@@ -209,12 +237,12 @@ const UploadCoverages = ({
209237
preloadedQuery,
210238
}: RelayProps<Record<string, unknown>, coveragesQuery>) => {
211239
const query = usePreloadedQuery(getCoveragesQuery, preloadedQuery);
212-
const { session } = query;
240+
const { session, allRecordVersions } = query;
213241
return (
214242
<Layout session={session} title="Connecting Communities BC">
215243
<StyledContainer>
216244
<DashboardTabs session={session} />
217-
<CoveragesTab />
245+
<CoveragesTab historyList={allRecordVersions?.nodes || []} />
218246
</StyledContainer>
219247
</Layout>
220248
);

0 commit comments

Comments
 (0)