@propriety/court-calendar 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +26 -0
- package/README.md +0 -0
- package/biome.json +302 -0
- package/dev/App.tsx +51 -0
- package/dev/main.tsx +10 -0
- package/index.html +12 -0
- package/package.json +54 -0
- package/public/vite.svg +1 -0
- package/src/_components/CCalendar.css +463 -0
- package/src/_components/CCalendar.tsx +726 -0
- package/src/_components/List/CalendarList.tsx +288 -0
- package/src/_components/Modal/CaseDetails/CaseDetails.tsx +414 -0
- package/src/_components/Modal/CaseDetails/EvidenceRow.tsx +83 -0
- package/src/_components/Modal/CaseDetails/EvidenceSection.tsx +94 -0
- package/src/_components/Modal/CreateEdit/CreateEditCase.tsx +241 -0
- package/src/_components/Modal/CreateEdit/DateSelector.tsx +42 -0
- package/src/_components/Modal/CreateEdit/EditUserFieldDropdown.tsx +54 -0
- package/src/_components/Modal/CreateEdit/EnumDropdown.tsx +54 -0
- package/src/_components/Modal/CreateEdit/HearingOfficerDropdown.tsx +48 -0
- package/src/_components/Modal/CreateEdit/TextFieldList.tsx +186 -0
- package/src/_components/Modal/CreateEdit/ToggleableTextField.tsx +91 -0
- package/src/_components/Modal/Modal.css +15 -0
- package/src/_components/Modal/Modal.tsx +325 -0
- package/src/_components/Modal/ModalActions.tsx +99 -0
- package/src/_components/Modal/View/CaseToolbar.tsx +81 -0
- package/src/_components/Modal/View/CaseViewer.tsx +237 -0
- package/src/_components/Modal/View/DateDetails.tsx +138 -0
- package/src/_components/Modal/View/InfoBox.tsx +22 -0
- package/src/_components/Modal/View/InfoBoxBtn.css +39 -0
- package/src/_components/Modal/View/InfoBoxBtn.tsx +29 -0
- package/src/_components/Modal/View/NoticeFileLink.tsx +44 -0
- package/src/_components/Shared/FirstSecondChairIcons.tsx +247 -0
- package/src/_components/Shared/FormRow.tsx +37 -0
- package/src/_components/Shared/MuniDropdown.tsx +94 -0
- package/src/_components/Shared/SearchBar.tsx +87 -0
- package/src/_components/Toolbar/CaseFilter.tsx +77 -0
- package/src/_components/Toolbar/DateTypeFilter.tsx +63 -0
- package/src/_components/Toolbar/HearingTypeFilter.tsx +63 -0
- package/src/_components/Toolbar/Toolbar.tsx +159 -0
- package/src/_components/Toolbar/UserFilter.tsx +105 -0
- package/src/_components/Toolbar/ViewFilter.tsx +48 -0
- package/src/helpers/cache.ts +89 -0
- package/src/helpers/cases.ts +79 -0
- package/src/helpers/courtDates.ts +139 -0
- package/src/helpers/formatter.ts +16 -0
- package/src/helpers/munis.ts +44 -0
- package/src/helpers/people.ts +46 -0
- package/src/index.ts +2 -0
- package/src/types.ts +129 -0
- package/tsconfig.app.json +32 -0
- package/tsconfig.json +4 -0
- package/tsconfig.node.json +30 -0
- package/vite.config.ts +27 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { useState, memo } from 'react';
|
|
2
|
+
import Box from '@mui/material/Box';
|
|
3
|
+
import Stack from '@mui/material/Stack';
|
|
4
|
+
import Grid from '@mui/material/Grid';
|
|
5
|
+
import Typography from '@mui/material/Typography';
|
|
6
|
+
import Paper from '@mui/material/Paper';
|
|
7
|
+
import Chip from '@mui/material/Chip';
|
|
8
|
+
import Card from '@mui/material/Card';
|
|
9
|
+
import CardContent from '@mui/material/CardContent';
|
|
10
|
+
import Tooltip from '@mui/material/Tooltip';
|
|
11
|
+
import InfoIcon from '@mui/icons-material/Info';
|
|
12
|
+
import DescriptionIcon from '@mui/icons-material/Description';
|
|
13
|
+
import HomeIcon from '@mui/icons-material/Home';
|
|
14
|
+
import EvidenceSection from './EvidenceSection';
|
|
15
|
+
import type { Case } from '@/types';
|
|
16
|
+
import { allUsers } from '@/helpers/people';
|
|
17
|
+
|
|
18
|
+
const CaseDetails = memo(function CaseDetails({
|
|
19
|
+
selectedCase,
|
|
20
|
+
isVillage,
|
|
21
|
+
}: {
|
|
22
|
+
selectedCase: Case | null;
|
|
23
|
+
isVillage: boolean;
|
|
24
|
+
}) {
|
|
25
|
+
const [tooltipText, setTooltipText] = useState('Copy');
|
|
26
|
+
|
|
27
|
+
if (!selectedCase) {
|
|
28
|
+
return (
|
|
29
|
+
<Box className='themed' p={4} textAlign='center'>
|
|
30
|
+
<Typography variant='h6'>No case selected</Typography>
|
|
31
|
+
</Box>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
ParcelID,
|
|
37
|
+
Year,
|
|
38
|
+
Municipality,
|
|
39
|
+
Negotiator,
|
|
40
|
+
SCARIndexNumber,
|
|
41
|
+
VillageSCARIndexNumber,
|
|
42
|
+
SCARDeterminationAction,
|
|
43
|
+
VillageSCARDeterminationAction,
|
|
44
|
+
SCARSettleDate,
|
|
45
|
+
SCARFileDate,
|
|
46
|
+
VillageSCARSettleDate,
|
|
47
|
+
VillageSCARFileDate,
|
|
48
|
+
property_data,
|
|
49
|
+
evidence,
|
|
50
|
+
} = selectedCase;
|
|
51
|
+
|
|
52
|
+
const handleCopyParcel = async () => {
|
|
53
|
+
try {
|
|
54
|
+
await navigator.clipboard.writeText(ParcelID);
|
|
55
|
+
setTooltipText('Copied!');
|
|
56
|
+
setTimeout(() => setTooltipText('Copy'), 2000);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error('Failed to copy:', err);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const isReviewed = () => {
|
|
63
|
+
return selectedCase.DateCompleted !== null;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<Stack spacing={4} mt={1}>
|
|
68
|
+
{/* Property Header Section */}
|
|
69
|
+
<Box
|
|
70
|
+
sx={{
|
|
71
|
+
position: 'relative',
|
|
72
|
+
border: '1px solid',
|
|
73
|
+
borderColor: 'divider',
|
|
74
|
+
borderRadius: 1,
|
|
75
|
+
mt: 2,
|
|
76
|
+
p: 2,
|
|
77
|
+
}}
|
|
78
|
+
>
|
|
79
|
+
<Stack
|
|
80
|
+
direction='row'
|
|
81
|
+
alignItems='center'
|
|
82
|
+
spacing={1}
|
|
83
|
+
sx={{
|
|
84
|
+
position: 'absolute',
|
|
85
|
+
top: 0,
|
|
86
|
+
left: 12,
|
|
87
|
+
transform: 'translateY(-50%)',
|
|
88
|
+
px: 1,
|
|
89
|
+
background: 'var(--bg)',
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<HomeIcon color='primary' />
|
|
93
|
+
<Typography variant='body1' fontWeight={600}>
|
|
94
|
+
Property Information
|
|
95
|
+
</Typography>
|
|
96
|
+
</Stack>
|
|
97
|
+
<Stack direction='row' spacing={2} alignItems='center'>
|
|
98
|
+
<Box>
|
|
99
|
+
<img
|
|
100
|
+
src={`https://aventine-photos.s3.us-east-1.amazonaws.com/${ParcelID}.jpg`}
|
|
101
|
+
alt={`property ${ParcelID} photo`}
|
|
102
|
+
height={120}
|
|
103
|
+
width={140}
|
|
104
|
+
style={{
|
|
105
|
+
minWidth: 140,
|
|
106
|
+
maxWidth: 140,
|
|
107
|
+
minHeight: 120,
|
|
108
|
+
maxHeight: 120,
|
|
109
|
+
objectFit: 'cover',
|
|
110
|
+
borderRadius: 8,
|
|
111
|
+
border: '2px solid rgba(255,255,255,0.8)',
|
|
112
|
+
background: '#f8f8f8',
|
|
113
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
|
|
114
|
+
}}
|
|
115
|
+
loading='lazy'
|
|
116
|
+
/>
|
|
117
|
+
</Box>
|
|
118
|
+
<Stack spacing={1} flex={1}>
|
|
119
|
+
<Typography variant='h6' fontWeight={600}>
|
|
120
|
+
{property_data?.Address || 'Unknown Address'}
|
|
121
|
+
</Typography>
|
|
122
|
+
<Typography variant='subtitle1' fontWeight={500}>
|
|
123
|
+
Owner: {property_data?.PropertyOwnerFull || 'Unknown'}
|
|
124
|
+
</Typography>
|
|
125
|
+
<Stack direction='row' spacing={1.5}>
|
|
126
|
+
<Tooltip title={tooltipText} arrow placement='top'>
|
|
127
|
+
<Chip
|
|
128
|
+
label={`${ParcelID}`}
|
|
129
|
+
size='medium'
|
|
130
|
+
variant='outlined'
|
|
131
|
+
onClick={handleCopyParcel}
|
|
132
|
+
sx={{
|
|
133
|
+
cursor: 'pointer',
|
|
134
|
+
'&:hover': {
|
|
135
|
+
backgroundColor: 'var(--fc-today-bg-color) !important',
|
|
136
|
+
},
|
|
137
|
+
transition: 'background-color 0.2s ease-in-out',
|
|
138
|
+
}}
|
|
139
|
+
/>
|
|
140
|
+
</Tooltip>
|
|
141
|
+
<Chip label={`${Year}`} size='medium' variant='outlined' />
|
|
142
|
+
<Chip
|
|
143
|
+
label={`${isReviewed() ? 'Reviewed' : 'Unreviewed'}`}
|
|
144
|
+
size='medium'
|
|
145
|
+
variant='outlined'
|
|
146
|
+
sx={{
|
|
147
|
+
'&.MuiChip-outlined': {
|
|
148
|
+
borderColor: isReviewed() ? 'green !important' : 'red !important',
|
|
149
|
+
},
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
</Stack>
|
|
153
|
+
</Stack>
|
|
154
|
+
</Stack>
|
|
155
|
+
</Box>
|
|
156
|
+
|
|
157
|
+
{/* Case Information Section */}
|
|
158
|
+
<Box
|
|
159
|
+
sx={{
|
|
160
|
+
position: 'relative',
|
|
161
|
+
border: '1px solid',
|
|
162
|
+
borderColor: 'divider',
|
|
163
|
+
borderRadius: 1,
|
|
164
|
+
mt: 1.5,
|
|
165
|
+
p: 1.5,
|
|
166
|
+
}}
|
|
167
|
+
>
|
|
168
|
+
<Stack
|
|
169
|
+
direction='row'
|
|
170
|
+
alignItems='center'
|
|
171
|
+
spacing={1}
|
|
172
|
+
sx={{
|
|
173
|
+
position: 'absolute',
|
|
174
|
+
top: 0,
|
|
175
|
+
left: 12,
|
|
176
|
+
transform: 'translateY(-50%)',
|
|
177
|
+
px: 1,
|
|
178
|
+
}}
|
|
179
|
+
>
|
|
180
|
+
<InfoIcon color='primary' />
|
|
181
|
+
<Typography variant='body1' fontWeight={600}>
|
|
182
|
+
Case Information
|
|
183
|
+
</Typography>
|
|
184
|
+
</Stack>
|
|
185
|
+
<Grid container spacing={1.5}>
|
|
186
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
187
|
+
<Card elevation={0} sx={{ height: '100%' }}>
|
|
188
|
+
<CardContent sx={{ py: 1, px: 1.5, '&:last-child': { pb: 1 } }}>
|
|
189
|
+
<Typography
|
|
190
|
+
variant='caption'
|
|
191
|
+
fontWeight={600}
|
|
192
|
+
textTransform='uppercase'
|
|
193
|
+
letterSpacing={0.5}
|
|
194
|
+
>
|
|
195
|
+
Municipality
|
|
196
|
+
</Typography>
|
|
197
|
+
<Typography variant='body1' fontWeight={500} mt={0.25}>
|
|
198
|
+
{Municipality || '—'}
|
|
199
|
+
</Typography>
|
|
200
|
+
</CardContent>
|
|
201
|
+
</Card>
|
|
202
|
+
</Grid>
|
|
203
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
204
|
+
<Card elevation={0} sx={{ height: '100%' }}>
|
|
205
|
+
<CardContent sx={{ py: 1, px: 1.5, '&:last-child': { pb: 1 } }}>
|
|
206
|
+
<Typography
|
|
207
|
+
variant='caption'
|
|
208
|
+
fontWeight={600}
|
|
209
|
+
textTransform='uppercase'
|
|
210
|
+
letterSpacing={0.5}
|
|
211
|
+
>
|
|
212
|
+
Negotiator
|
|
213
|
+
</Typography>
|
|
214
|
+
<Typography variant='body1' fontWeight={500} mt={0.25}>
|
|
215
|
+
{Negotiator && !isNaN(Negotiator)
|
|
216
|
+
? `${allUsers[Negotiator].UserFirstName} ${allUsers[Negotiator].UserLastName}`
|
|
217
|
+
: '—'}
|
|
218
|
+
</Typography>
|
|
219
|
+
</CardContent>
|
|
220
|
+
</Card>
|
|
221
|
+
</Grid>
|
|
222
|
+
{!isVillage && (
|
|
223
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
224
|
+
<Card elevation={0} sx={{ height: '100%' }}>
|
|
225
|
+
<CardContent sx={{ py: 1, px: 1.5, '&:last-child': { pb: 1 } }}>
|
|
226
|
+
<Typography
|
|
227
|
+
variant='caption'
|
|
228
|
+
fontWeight={600}
|
|
229
|
+
textTransform='uppercase'
|
|
230
|
+
letterSpacing={0.5}
|
|
231
|
+
>
|
|
232
|
+
SCAR Index #
|
|
233
|
+
</Typography>
|
|
234
|
+
<Typography variant='body1' fontWeight={500} mt={0.25}>
|
|
235
|
+
{SCARIndexNumber || '—'}
|
|
236
|
+
</Typography>
|
|
237
|
+
</CardContent>
|
|
238
|
+
</Card>
|
|
239
|
+
</Grid>
|
|
240
|
+
)}
|
|
241
|
+
{isVillage && (
|
|
242
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
243
|
+
<Card elevation={0} sx={{ height: '100%' }}>
|
|
244
|
+
<CardContent sx={{ py: 1, px: 1.5, '&:last-child': { pb: 1 } }}>
|
|
245
|
+
<Typography
|
|
246
|
+
variant='caption'
|
|
247
|
+
fontWeight={600}
|
|
248
|
+
textTransform='uppercase'
|
|
249
|
+
letterSpacing={0.5}
|
|
250
|
+
>
|
|
251
|
+
Village SCAR Index #
|
|
252
|
+
</Typography>
|
|
253
|
+
<Typography variant='body1' fontWeight={500} mt={0.25}>
|
|
254
|
+
{VillageSCARIndexNumber || '—'}
|
|
255
|
+
</Typography>
|
|
256
|
+
</CardContent>
|
|
257
|
+
</Card>
|
|
258
|
+
</Grid>
|
|
259
|
+
)}
|
|
260
|
+
{!isVillage && (
|
|
261
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
262
|
+
<Card elevation={0} sx={{ height: '100%' }}>
|
|
263
|
+
<CardContent sx={{ py: 1, px: 1.5, '&:last-child': { pb: 1 } }}>
|
|
264
|
+
<Typography
|
|
265
|
+
variant='caption'
|
|
266
|
+
fontWeight={600}
|
|
267
|
+
textTransform='uppercase'
|
|
268
|
+
letterSpacing={0.5}
|
|
269
|
+
>
|
|
270
|
+
SCAR Determination
|
|
271
|
+
</Typography>
|
|
272
|
+
<Typography variant='body1' fontWeight={500} mt={0.25}>
|
|
273
|
+
{SCARDeterminationAction || '—'}
|
|
274
|
+
</Typography>
|
|
275
|
+
</CardContent>
|
|
276
|
+
</Card>
|
|
277
|
+
</Grid>
|
|
278
|
+
)}
|
|
279
|
+
{isVillage && (
|
|
280
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
281
|
+
<Card elevation={0} sx={{ height: '100%' }}>
|
|
282
|
+
<CardContent sx={{ py: 1, px: 1.5, '&:last-child': { pb: 1 } }}>
|
|
283
|
+
<Typography
|
|
284
|
+
variant='caption'
|
|
285
|
+
fontWeight={600}
|
|
286
|
+
textTransform='uppercase'
|
|
287
|
+
letterSpacing={0.5}
|
|
288
|
+
>
|
|
289
|
+
Village SCAR Determination
|
|
290
|
+
</Typography>
|
|
291
|
+
<Typography variant='body1' fontWeight={500} mt={0.25}>
|
|
292
|
+
{VillageSCARDeterminationAction || '—'}
|
|
293
|
+
</Typography>
|
|
294
|
+
</CardContent>
|
|
295
|
+
</Card>
|
|
296
|
+
</Grid>
|
|
297
|
+
)}
|
|
298
|
+
{!isVillage && (
|
|
299
|
+
<>
|
|
300
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
301
|
+
<Card elevation={0} sx={{ height: '100%' }}>
|
|
302
|
+
<CardContent sx={{ py: 1, px: 1.5, '&:last-child': { pb: 1 } }}>
|
|
303
|
+
<Typography
|
|
304
|
+
variant='caption'
|
|
305
|
+
fontWeight={600}
|
|
306
|
+
textTransform='uppercase'
|
|
307
|
+
letterSpacing={0.5}
|
|
308
|
+
>
|
|
309
|
+
SCAR File Date
|
|
310
|
+
</Typography>
|
|
311
|
+
<Typography variant='body1' fontWeight={500} mt={0.25}>
|
|
312
|
+
{SCARFileDate ? new Date(SCARFileDate).toLocaleDateString() : '—'}
|
|
313
|
+
</Typography>
|
|
314
|
+
</CardContent>
|
|
315
|
+
</Card>
|
|
316
|
+
</Grid>
|
|
317
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
318
|
+
<Card elevation={0} sx={{ height: '100%' }}>
|
|
319
|
+
<CardContent sx={{ py: 1, px: 1.5, '&:last-child': { pb: 1 } }}>
|
|
320
|
+
<Typography
|
|
321
|
+
variant='caption'
|
|
322
|
+
fontWeight={600}
|
|
323
|
+
textTransform='uppercase'
|
|
324
|
+
letterSpacing={0.5}
|
|
325
|
+
>
|
|
326
|
+
SCAR Settle Date
|
|
327
|
+
</Typography>
|
|
328
|
+
<Typography variant='body1' fontWeight={500} mt={0.25}>
|
|
329
|
+
{SCARSettleDate ? new Date(SCARSettleDate).toLocaleDateString() : '—'}
|
|
330
|
+
</Typography>
|
|
331
|
+
</CardContent>
|
|
332
|
+
</Card>
|
|
333
|
+
</Grid>
|
|
334
|
+
</>
|
|
335
|
+
)}
|
|
336
|
+
{isVillage && (
|
|
337
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
338
|
+
<Card elevation={0} sx={{ height: '100%' }}>
|
|
339
|
+
<CardContent sx={{ py: 1, px: 1.5, '&:last-child': { pb: 1 } }}>
|
|
340
|
+
<Typography
|
|
341
|
+
variant='caption'
|
|
342
|
+
fontWeight={600}
|
|
343
|
+
textTransform='uppercase'
|
|
344
|
+
letterSpacing={0.5}
|
|
345
|
+
>
|
|
346
|
+
Village SCAR File Date
|
|
347
|
+
</Typography>
|
|
348
|
+
<Typography variant='body1' fontWeight={500} mt={0.25}>
|
|
349
|
+
{VillageSCARFileDate ? new Date(VillageSCARFileDate).toLocaleDateString() : '—'}
|
|
350
|
+
</Typography>
|
|
351
|
+
</CardContent>
|
|
352
|
+
</Card>
|
|
353
|
+
</Grid>
|
|
354
|
+
)}
|
|
355
|
+
{isVillage && (
|
|
356
|
+
<Grid size={{ xs: 12, sm: 6 }}>
|
|
357
|
+
<Card elevation={0} sx={{ height: '100%' }}>
|
|
358
|
+
<CardContent sx={{ py: 1, px: 1.5, '&:last-child': { pb: 1 } }}>
|
|
359
|
+
<Typography
|
|
360
|
+
variant='caption'
|
|
361
|
+
fontWeight={600}
|
|
362
|
+
textTransform='uppercase'
|
|
363
|
+
letterSpacing={0.5}
|
|
364
|
+
>
|
|
365
|
+
Village SCAR Settle Date
|
|
366
|
+
</Typography>
|
|
367
|
+
<Typography variant='body1' fontWeight={500} mt={0.25}>
|
|
368
|
+
{VillageSCARSettleDate
|
|
369
|
+
? new Date(VillageSCARSettleDate).toLocaleDateString()
|
|
370
|
+
: '—'}
|
|
371
|
+
</Typography>
|
|
372
|
+
</CardContent>
|
|
373
|
+
</Card>
|
|
374
|
+
</Grid>
|
|
375
|
+
)}
|
|
376
|
+
</Grid>
|
|
377
|
+
</Box>
|
|
378
|
+
|
|
379
|
+
{/* Evidence Section */}
|
|
380
|
+
<Box
|
|
381
|
+
sx={{
|
|
382
|
+
position: 'relative',
|
|
383
|
+
border: '1px solid',
|
|
384
|
+
borderColor: 'divider',
|
|
385
|
+
borderRadius: 1,
|
|
386
|
+
mt: 2,
|
|
387
|
+
p: 2,
|
|
388
|
+
}}
|
|
389
|
+
>
|
|
390
|
+
<Stack
|
|
391
|
+
direction='row'
|
|
392
|
+
alignItems='center'
|
|
393
|
+
spacing={1}
|
|
394
|
+
sx={{
|
|
395
|
+
position: 'absolute',
|
|
396
|
+
top: 0,
|
|
397
|
+
left: 12,
|
|
398
|
+
transform: 'translateY(-50%)',
|
|
399
|
+
px: 1,
|
|
400
|
+
background: 'var(--bg)',
|
|
401
|
+
}}
|
|
402
|
+
>
|
|
403
|
+
<DescriptionIcon color='primary' />
|
|
404
|
+
<Typography variant='body1' fontWeight={600}>
|
|
405
|
+
Evidence
|
|
406
|
+
</Typography>
|
|
407
|
+
</Stack>
|
|
408
|
+
<EvidenceSection evidence={evidence} />
|
|
409
|
+
</Box>
|
|
410
|
+
</Stack>
|
|
411
|
+
);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
export default CaseDetails;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import Grid from '@mui/material/Grid';
|
|
2
|
+
import Typography from '@mui/material/Typography';
|
|
3
|
+
import Chip from '@mui/material/Chip';
|
|
4
|
+
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|
5
|
+
import Stack from '@mui/material/Stack';
|
|
6
|
+
import IconButton from '@mui/material/IconButton';
|
|
7
|
+
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
|
8
|
+
|
|
9
|
+
export default function EvidenceRow({
|
|
10
|
+
label,
|
|
11
|
+
url,
|
|
12
|
+
user,
|
|
13
|
+
uploadDate,
|
|
14
|
+
createdDate,
|
|
15
|
+
isUploaded = false,
|
|
16
|
+
}: {
|
|
17
|
+
label: string;
|
|
18
|
+
url: string;
|
|
19
|
+
user: string;
|
|
20
|
+
uploadDate: string;
|
|
21
|
+
createdDate: string;
|
|
22
|
+
isUploaded?: boolean;
|
|
23
|
+
}) {
|
|
24
|
+
function handleRowClick() {
|
|
25
|
+
if (url) {
|
|
26
|
+
window.open(url, '_blank', 'noopener,noreferrer');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Grid
|
|
32
|
+
container
|
|
33
|
+
spacing={2}
|
|
34
|
+
onClick={handleRowClick}
|
|
35
|
+
className='evidence-row'
|
|
36
|
+
sx={{
|
|
37
|
+
padding: '8px',
|
|
38
|
+
backgroundColor: isUploaded ? 'rgba(76, 175, 80, 0.08)' : 'transparent',
|
|
39
|
+
borderRadius: '4px',
|
|
40
|
+
cursor: url ? 'pointer' : 'default',
|
|
41
|
+
'&:hover, &:hover div': url
|
|
42
|
+
? {
|
|
43
|
+
backgroundColor: isUploaded
|
|
44
|
+
? 'rgba(76, 175, 80, 0.15)'
|
|
45
|
+
: 'var(--fc-today-bg-color) !important',
|
|
46
|
+
}
|
|
47
|
+
: {},
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
<Grid size={3}>
|
|
51
|
+
<Stack direction='row' spacing={1} alignItems='center'>
|
|
52
|
+
<Typography variant='body2' fontWeight={isUploaded ? 600 : 400}>
|
|
53
|
+
{label}
|
|
54
|
+
</Typography>
|
|
55
|
+
{isUploaded && (
|
|
56
|
+
<Chip
|
|
57
|
+
label='Uploaded'
|
|
58
|
+
size='small'
|
|
59
|
+
color='success'
|
|
60
|
+
icon={<CheckCircleIcon />}
|
|
61
|
+
sx={{ height: 20, fontSize: '0.7rem' }}
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
</Stack>
|
|
65
|
+
</Grid>
|
|
66
|
+
<Grid size={3.5}>
|
|
67
|
+
<Typography variant='body2'>{`Created on ${createdDate}`}</Typography>
|
|
68
|
+
</Grid>
|
|
69
|
+
<Grid size={3.5}>
|
|
70
|
+
<Typography variant='body2'>{isUploaded ? `Uploaded on ${uploadDate}` : 'Not uploaded'}</Typography>
|
|
71
|
+
</Grid>
|
|
72
|
+
<Grid size={1}>
|
|
73
|
+
<Typography variant='body2'>{user}</Typography>
|
|
74
|
+
</Grid>
|
|
75
|
+
|
|
76
|
+
<Grid size={1}>
|
|
77
|
+
<Stack direction='row' justifyContent='flex-end'>
|
|
78
|
+
{url && <OpenInNewIcon fontSize='small' sx={{ opacity: 0.6 }} />}
|
|
79
|
+
</Stack>
|
|
80
|
+
</Grid>
|
|
81
|
+
</Grid>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Evidence } from '@/types';
|
|
2
|
+
import Stack from '@mui/material/Stack';
|
|
3
|
+
import EvidenceRow from './EvidenceRow';
|
|
4
|
+
import { allUsers } from '@/helpers/people';
|
|
5
|
+
|
|
6
|
+
export default function EvidenceSection({ evidence }: { evidence: Evidence | null }) {
|
|
7
|
+
console.log('evidence in EvidenceSection:', evidence);
|
|
8
|
+
|
|
9
|
+
// Check if uploaded evidence exists
|
|
10
|
+
const hasUploadedEvidence = evidence?.Uploaded?.Evidence;
|
|
11
|
+
|
|
12
|
+
// Parse evidence types
|
|
13
|
+
const parseEvidenceTypes = (evidenceStr: string | undefined): string[] => {
|
|
14
|
+
if (!evidenceStr) return [];
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(evidenceStr);
|
|
17
|
+
return Array.isArray(parsed) ? parsed.map((e) => e.toLowerCase()) : [];
|
|
18
|
+
} catch {
|
|
19
|
+
return evidenceStr
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.split(',')
|
|
22
|
+
.map((e) => e.trim());
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Get evidence types to display
|
|
27
|
+
const evidenceTypes = hasUploadedEvidence
|
|
28
|
+
? parseEvidenceTypes(evidence.Uploaded.Evidence)
|
|
29
|
+
: parseEvidenceTypes(evidence?.Evidence);
|
|
30
|
+
|
|
31
|
+
const hasSales = evidenceTypes.some((type) => type.includes('sales') || type.includes('unequal'));
|
|
32
|
+
const hasEquity = evidenceTypes.some((type) => type.includes('excessive') || type.includes('equity'));
|
|
33
|
+
const hasAnyEvidence = hasSales || hasEquity;
|
|
34
|
+
|
|
35
|
+
// Format upload date if available
|
|
36
|
+
const uploadDate = evidence?.Uploaded?.UploadDate
|
|
37
|
+
? new Date(evidence.Uploaded.UploadDate).toLocaleDateString()
|
|
38
|
+
: '';
|
|
39
|
+
|
|
40
|
+
// format creation date if available
|
|
41
|
+
const creationDate = evidence?.CreatedDate ? new Date(evidence.CreatedDate).toLocaleDateString() : '';
|
|
42
|
+
|
|
43
|
+
// format user if available
|
|
44
|
+
const user = evidence?.User ? allUsers[evidence?.User] : null;
|
|
45
|
+
const userName = user ? `${user.UserFirstName} ${user.UserLastName}` : '';
|
|
46
|
+
|
|
47
|
+
// Generate S3 URL for evidence
|
|
48
|
+
const generateEvidenceUrl = (reportType: 'sales' | 'equity'): string => {
|
|
49
|
+
if (!evidence?.ParcelID || !evidence?.Year) return '';
|
|
50
|
+
|
|
51
|
+
// for later in case of changes, these can be weird with unequal/excessive
|
|
52
|
+
const reportTypeMap: Record<string, string> = {
|
|
53
|
+
sales: 'sales',
|
|
54
|
+
equity: 'equity',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const mappedType = reportTypeMap[reportType];
|
|
58
|
+
const baseUrl = 'https://aventine-court-docs.s3.amazonaws.com';
|
|
59
|
+
const path = `residential/evidence/${evidence.Year}/${evidence.ParcelID}/${mappedType}_comps_fnma.pdf`;
|
|
60
|
+
|
|
61
|
+
return `${baseUrl}/${path}`;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Stack spacing={1} direction='column'>
|
|
66
|
+
{/* sales */}
|
|
67
|
+
{hasSales && (
|
|
68
|
+
<EvidenceRow
|
|
69
|
+
label='Sales Records'
|
|
70
|
+
url={generateEvidenceUrl('sales')}
|
|
71
|
+
user={userName}
|
|
72
|
+
uploadDate={uploadDate}
|
|
73
|
+
createdDate={creationDate}
|
|
74
|
+
isUploaded={!!hasUploadedEvidence}
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
77
|
+
|
|
78
|
+
{/* equity */}
|
|
79
|
+
{hasEquity && (
|
|
80
|
+
<EvidenceRow
|
|
81
|
+
label='Equity Records'
|
|
82
|
+
url={generateEvidenceUrl('equity')}
|
|
83
|
+
user={userName}
|
|
84
|
+
uploadDate={uploadDate}
|
|
85
|
+
createdDate={creationDate}
|
|
86
|
+
isUploaded={!!hasUploadedEvidence}
|
|
87
|
+
/>
|
|
88
|
+
)}
|
|
89
|
+
|
|
90
|
+
{/* none */}
|
|
91
|
+
{!hasAnyEvidence && <EvidenceRow label='No Records' url={''} user={''} uploadDate={''} createdDate={''} />}
|
|
92
|
+
</Stack>
|
|
93
|
+
);
|
|
94
|
+
}
|