@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,91 @@
|
|
|
1
|
+
import TextField from '@mui/material/TextField';
|
|
2
|
+
import Box from '@mui/material/Box';
|
|
3
|
+
import Typography from '@mui/material/Typography';
|
|
4
|
+
import Switch from '@mui/material/Switch';
|
|
5
|
+
|
|
6
|
+
export default function ToggleableTextField({
|
|
7
|
+
index,
|
|
8
|
+
value,
|
|
9
|
+
onChange,
|
|
10
|
+
onBlur,
|
|
11
|
+
isToggled,
|
|
12
|
+
onToggleChange,
|
|
13
|
+
fieldLabelPrefix,
|
|
14
|
+
alternateFieldLabelPrefix,
|
|
15
|
+
}: {
|
|
16
|
+
index: number;
|
|
17
|
+
value: string;
|
|
18
|
+
onChange: (value: string) => void;
|
|
19
|
+
onBlur?: () => void;
|
|
20
|
+
isToggled: boolean;
|
|
21
|
+
onToggleChange: (checked: boolean) => void;
|
|
22
|
+
fieldLabelPrefix: string;
|
|
23
|
+
alternateFieldLabelPrefix: string;
|
|
24
|
+
}) {
|
|
25
|
+
return (
|
|
26
|
+
<TextField
|
|
27
|
+
label={
|
|
28
|
+
<Box
|
|
29
|
+
sx={{
|
|
30
|
+
display: 'flex',
|
|
31
|
+
alignItems: 'center',
|
|
32
|
+
gap: 1,
|
|
33
|
+
borderRadius: '12px',
|
|
34
|
+
'&:hover': { cursor: 'pointer', backgroundColor: 'var(--fc-today-bg-color) !important' },
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
<Typography
|
|
38
|
+
variant='caption'
|
|
39
|
+
sx={{
|
|
40
|
+
fontSize: '1rem',
|
|
41
|
+
fontWeight: !isToggled ? 600 : 400,
|
|
42
|
+
color: !isToggled ? 'primary.main' : 'var(--text)',
|
|
43
|
+
opacity: !isToggled ? 1 : '0.5 !important',
|
|
44
|
+
transition: 'all 0.2s',
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
{fieldLabelPrefix}
|
|
48
|
+
</Typography>
|
|
49
|
+
<Switch
|
|
50
|
+
checked={isToggled}
|
|
51
|
+
onChange={(e) => {
|
|
52
|
+
e.stopPropagation();
|
|
53
|
+
onToggleChange(e.target.checked);
|
|
54
|
+
}}
|
|
55
|
+
size='medium'
|
|
56
|
+
/>
|
|
57
|
+
<Typography
|
|
58
|
+
variant='caption'
|
|
59
|
+
sx={{
|
|
60
|
+
fontSize: '1rem',
|
|
61
|
+
fontWeight: isToggled ? 600 : 400,
|
|
62
|
+
color: isToggled ? 'primary.main' : 'var(--text)',
|
|
63
|
+
transition: 'all 0.2s',
|
|
64
|
+
opacity: isToggled ? 1 : '0.5 !important',
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{alternateFieldLabelPrefix}
|
|
68
|
+
</Typography>
|
|
69
|
+
</Box>
|
|
70
|
+
}
|
|
71
|
+
value={value}
|
|
72
|
+
onChange={(e) => onChange(e.target.value)}
|
|
73
|
+
onBlur={onBlur}
|
|
74
|
+
fullWidth
|
|
75
|
+
slotProps={{
|
|
76
|
+
inputLabel: {
|
|
77
|
+
shrink: true,
|
|
78
|
+
sx: {
|
|
79
|
+
borderRadius: '12px',
|
|
80
|
+
top: '-3px',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
}}
|
|
84
|
+
sx={{
|
|
85
|
+
'& legend': {
|
|
86
|
+
width: '170px',
|
|
87
|
+
},
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* Modal appearing animation */
|
|
2
|
+
.modal-appear-animate {
|
|
3
|
+
animation: modalFadeIn 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
@keyframes modalFadeIn {
|
|
7
|
+
from {
|
|
8
|
+
opacity: 0;
|
|
9
|
+
transform: scale(0.96) translateY(24px);
|
|
10
|
+
}
|
|
11
|
+
to {
|
|
12
|
+
opacity: 1;
|
|
13
|
+
transform: scale(1) translateY(0);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import Dialog from '@mui/material/Dialog';
|
|
3
|
+
import DialogTitle from '@mui/material/DialogTitle';
|
|
4
|
+
import DialogContent from '@mui/material/DialogContent';
|
|
5
|
+
import { Lifecycle, ModalMode, SourceType, type CalendarFilterCtx, type Case, type CourtDate } from '@/types';
|
|
6
|
+
import CaseViewer from './View/CaseViewer';
|
|
7
|
+
import DateDetails from './View/DateDetails';
|
|
8
|
+
import ModalActions from './ModalActions';
|
|
9
|
+
import CreateEditCase from './CreateEdit/CreateEditCase';
|
|
10
|
+
import { LocalizationProvider } from '@mui/x-date-pickers';
|
|
11
|
+
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
|
12
|
+
import { createCourtDate, deleteCourtDate, isVillageDate, updateCourtDate } from '@/helpers/courtDates';
|
|
13
|
+
import IconButton from '@mui/material/IconButton';
|
|
14
|
+
import CloseIcon from '@mui/icons-material/Close';
|
|
15
|
+
import './Modal.css';
|
|
16
|
+
import CaseDetails from './CaseDetails/CaseDetails';
|
|
17
|
+
import Paper from '@mui/material/Paper';
|
|
18
|
+
|
|
19
|
+
export default function CCModal({
|
|
20
|
+
modalIsOpen,
|
|
21
|
+
modalMode,
|
|
22
|
+
setModalMode,
|
|
23
|
+
setIsOpen,
|
|
24
|
+
selectedCourtDate,
|
|
25
|
+
updateCourtDateInMemory,
|
|
26
|
+
filterCtx,
|
|
27
|
+
setFilterCtx,
|
|
28
|
+
selectedCases,
|
|
29
|
+
updateCases,
|
|
30
|
+
deleteCases,
|
|
31
|
+
isFetchingCases,
|
|
32
|
+
apiKey,
|
|
33
|
+
}: {
|
|
34
|
+
modalIsOpen: boolean;
|
|
35
|
+
modalMode: ModalMode;
|
|
36
|
+
setModalMode: (mode: ModalMode) => void;
|
|
37
|
+
setIsOpen: (isOpen: boolean) => void;
|
|
38
|
+
selectedCourtDate: CourtDate | null;
|
|
39
|
+
updateCourtDateInMemory: (updatedDate: CourtDate, del?: boolean) => void;
|
|
40
|
+
filterCtx: CalendarFilterCtx;
|
|
41
|
+
setFilterCtx: (ctx: CalendarFilterCtx) => void;
|
|
42
|
+
selectedCases: Case[];
|
|
43
|
+
updateCases: (cases: Record<string, Case[]>) => void;
|
|
44
|
+
deleteCases: (courtDateId: string) => void;
|
|
45
|
+
isFetchingCases: boolean;
|
|
46
|
+
apiKey: string;
|
|
47
|
+
}) {
|
|
48
|
+
const [editedData, setEditedData] = useState<CourtDate | undefined>(undefined);
|
|
49
|
+
const [editedCases, setEditedCases] = useState<Case[]>([]);
|
|
50
|
+
const [clickedCase, setClickedCase] = useState<Case | null>(null);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!modalIsOpen) {
|
|
54
|
+
setModalMode(ModalMode.DETAILS);
|
|
55
|
+
setClickedCase(null);
|
|
56
|
+
}
|
|
57
|
+
}, [modalIsOpen]);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (clickedCase !== null) {
|
|
61
|
+
setModalMode(ModalMode.CASE_DETAILS);
|
|
62
|
+
}
|
|
63
|
+
}, [clickedCase]);
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (modalMode === ModalMode.DETAILS) {
|
|
67
|
+
setClickedCase(null);
|
|
68
|
+
}
|
|
69
|
+
}, [modalMode]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (modalMode === ModalMode.EDIT && selectedCourtDate) {
|
|
73
|
+
setEditedData(selectedCourtDate);
|
|
74
|
+
setEditedCases(selectedCases);
|
|
75
|
+
}
|
|
76
|
+
}, [modalMode, selectedCourtDate, selectedCases]);
|
|
77
|
+
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (modalMode === ModalMode.CREATE) {
|
|
80
|
+
// Initialize with blank data for create mode, using selectedCourtDate.CourtDate if available
|
|
81
|
+
setEditedData({
|
|
82
|
+
CourtDateID: 0,
|
|
83
|
+
CourtDate: selectedCourtDate?.CourtDate ? new Date(selectedCourtDate.CourtDate) : new Date(),
|
|
84
|
+
MuniCode: '',
|
|
85
|
+
UploadDeadline: null,
|
|
86
|
+
CourtCases: 0,
|
|
87
|
+
Lifecycle: Lifecycle.SCHEDULED,
|
|
88
|
+
HearingTime: '',
|
|
89
|
+
HearingLink: null,
|
|
90
|
+
Source: SourceType.MANUAL,
|
|
91
|
+
Type: null,
|
|
92
|
+
FirstChair: null,
|
|
93
|
+
SecondChair: null,
|
|
94
|
+
HearingOfficer: null,
|
|
95
|
+
EnterDate: new Date(),
|
|
96
|
+
LastUpdateDate: null,
|
|
97
|
+
AdjournmentDate: null,
|
|
98
|
+
IsAdjourned: false,
|
|
99
|
+
Notes: null,
|
|
100
|
+
NoticeFile: null,
|
|
101
|
+
});
|
|
102
|
+
setEditedCases([
|
|
103
|
+
{
|
|
104
|
+
ParcelID: '',
|
|
105
|
+
Municipality: '',
|
|
106
|
+
Negotiator: 0,
|
|
107
|
+
Year: new Date().getFullYear(),
|
|
108
|
+
property_data: { Address: '', PropertyOwnerFull: '' },
|
|
109
|
+
evidence: null,
|
|
110
|
+
SCARIndexNumber: '',
|
|
111
|
+
SCARDeterminationAction: '',
|
|
112
|
+
SCARSettleDate: null,
|
|
113
|
+
SCARFileDate: null,
|
|
114
|
+
VillageSCARIndexNumber: '',
|
|
115
|
+
VillageSCARDeterminationAction: '',
|
|
116
|
+
VillageSCARSettleDate: null,
|
|
117
|
+
VillageSCARFileDate: null,
|
|
118
|
+
DateCompleted: null,
|
|
119
|
+
},
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
}, [modalMode, selectedCourtDate]);
|
|
123
|
+
|
|
124
|
+
// #region Handlers
|
|
125
|
+
function validateEditedData(): { valid: boolean; message?: string } {
|
|
126
|
+
if (!editedData) return { valid: false, message: 'No data to validate' };
|
|
127
|
+
|
|
128
|
+
// REQUIRED FIELDS
|
|
129
|
+
if (!editedData.CourtDate) return { valid: false, message: 'CourtDate is required' };
|
|
130
|
+
if (!editedData.MuniCode || editedData.MuniCode.trim() === '')
|
|
131
|
+
return { valid: false, message: 'MuniCode is required' };
|
|
132
|
+
|
|
133
|
+
// validation passed
|
|
134
|
+
return { valid: true };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async function handleSave() {
|
|
138
|
+
if (!editedData) return;
|
|
139
|
+
const validation = validateEditedData();
|
|
140
|
+
if (!validation.valid) {
|
|
141
|
+
alert(`Validation failed: ${validation.message}`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (modalMode === ModalMode.CREATE) {
|
|
145
|
+
await handleCreate();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
await updateCourtDate(
|
|
149
|
+
editedData.CourtDateID,
|
|
150
|
+
editedData,
|
|
151
|
+
apiKey,
|
|
152
|
+
editedCases.map((c) => c.SCARIndexNumber),
|
|
153
|
+
);
|
|
154
|
+
// Update cache after save
|
|
155
|
+
const courtDateId = editedData.CourtDateID;
|
|
156
|
+
if (courtDateId) {
|
|
157
|
+
updateCases({ [courtDateId.toString()]: editedCases });
|
|
158
|
+
}
|
|
159
|
+
setIsOpen(false); // TODO notification
|
|
160
|
+
updateCourtDateInMemory(editedData);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function handleCreate() {
|
|
164
|
+
if (!editedData) return;
|
|
165
|
+
const validation = validateEditedData();
|
|
166
|
+
if (!validation.valid) {
|
|
167
|
+
alert(`Validation failed: ${validation.message}`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const id = await createCourtDate(
|
|
171
|
+
editedData.CourtDate,
|
|
172
|
+
editedData.MuniCode,
|
|
173
|
+
apiKey,
|
|
174
|
+
editedCases.map((c) => c.SCARIndexNumber),
|
|
175
|
+
);
|
|
176
|
+
if (id) {
|
|
177
|
+
const updatedData = { ...editedData, CourtDateID: id };
|
|
178
|
+
setEditedData(updatedData);
|
|
179
|
+
await updateCourtDate(id, updatedData, apiKey);
|
|
180
|
+
updateCourtDateInMemory(updatedData);
|
|
181
|
+
// Update cache after create
|
|
182
|
+
updateCases({ [id.toString()]: editedCases });
|
|
183
|
+
}
|
|
184
|
+
setIsOpen(false); // TODO notification
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function handleDelete() {
|
|
188
|
+
const res = window.confirm('Are you sure you want to delete this court date?');
|
|
189
|
+
if (res) {
|
|
190
|
+
await deleteCourtDate(selectedCourtDate!.CourtDateID, apiKey);
|
|
191
|
+
updateCourtDateInMemory(selectedCourtDate!, true);
|
|
192
|
+
// Remove from cache after delete
|
|
193
|
+
const courtDateId = selectedCourtDate!.CourtDateID;
|
|
194
|
+
if (courtDateId) {
|
|
195
|
+
deleteCases(courtDateId.toString());
|
|
196
|
+
}
|
|
197
|
+
setIsOpen(false); // TODO notification
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// #endregion
|
|
201
|
+
|
|
202
|
+
// #region render
|
|
203
|
+
return (
|
|
204
|
+
<Dialog
|
|
205
|
+
open={modalIsOpen}
|
|
206
|
+
onClose={() => setIsOpen(false)}
|
|
207
|
+
fullWidth
|
|
208
|
+
maxWidth='lg'
|
|
209
|
+
slotProps={{
|
|
210
|
+
paper: {
|
|
211
|
+
className: 'modal-appear-animate',
|
|
212
|
+
sx: {
|
|
213
|
+
background:
|
|
214
|
+
'linear-gradient(to bottom, rgba(255,255,255,0.05), rgba(255,255,255,0)) !important',
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
}}
|
|
218
|
+
>
|
|
219
|
+
<DialogTitle
|
|
220
|
+
className='themed'
|
|
221
|
+
style={{
|
|
222
|
+
display: 'flex',
|
|
223
|
+
justifyContent: 'space-between',
|
|
224
|
+
alignItems: 'center',
|
|
225
|
+
}}
|
|
226
|
+
>
|
|
227
|
+
{modalMode === ModalMode.DETAILS
|
|
228
|
+
? 'Court Date Details'
|
|
229
|
+
: modalMode === ModalMode.CREATE
|
|
230
|
+
? 'Create Court Date'
|
|
231
|
+
: modalMode === ModalMode.EDIT
|
|
232
|
+
? 'Edit Court Date'
|
|
233
|
+
: modalMode === ModalMode.CASE_DETAILS
|
|
234
|
+
? 'Case Details'
|
|
235
|
+
: ''}
|
|
236
|
+
<IconButton aria-label='close' onClick={() => setIsOpen(false)} edge='end' size='small'>
|
|
237
|
+
<CloseIcon />
|
|
238
|
+
</IconButton>
|
|
239
|
+
</DialogTitle>
|
|
240
|
+
<DialogContent dividers className='themed' sx={{ position: 'relative' }}>
|
|
241
|
+
<LocalizationProvider dateAdapter={AdapterDayjs}>
|
|
242
|
+
<div
|
|
243
|
+
style={{
|
|
244
|
+
opacity: modalMode === ModalMode.DETAILS ? 1 : 0,
|
|
245
|
+
pointerEvents: modalMode === ModalMode.DETAILS ? 'auto' : 'none',
|
|
246
|
+
position: modalMode === ModalMode.DETAILS ? 'relative' : 'absolute',
|
|
247
|
+
top: 0,
|
|
248
|
+
left: 0,
|
|
249
|
+
right: 0,
|
|
250
|
+
transition: modalMode === ModalMode.DETAILS ? 'opacity 0.15s ease-in-out' : 'none',
|
|
251
|
+
overflow: modalMode === ModalMode.DETAILS ? 'visible' : 'hidden',
|
|
252
|
+
maxHeight: modalMode === ModalMode.DETAILS ? 'none' : 0,
|
|
253
|
+
}}
|
|
254
|
+
>
|
|
255
|
+
<DateDetails selectedCourtDate={selectedCourtDate} apiKey={apiKey} />
|
|
256
|
+
<CaseViewer
|
|
257
|
+
cases={selectedCases}
|
|
258
|
+
isFetchingCases={isFetchingCases}
|
|
259
|
+
filterCtx={filterCtx}
|
|
260
|
+
setFilterCtx={setFilterCtx}
|
|
261
|
+
setClickedCase={setClickedCase}
|
|
262
|
+
isVillage={isVillageDate(selectedCourtDate ? selectedCourtDate.MuniCode : '')}
|
|
263
|
+
/>
|
|
264
|
+
</div>
|
|
265
|
+
{(modalMode === ModalMode.EDIT || modalMode === ModalMode.CREATE) && editedData && (
|
|
266
|
+
<div
|
|
267
|
+
style={{
|
|
268
|
+
opacity: modalMode === ModalMode.EDIT || modalMode === ModalMode.CREATE ? 1 : 0,
|
|
269
|
+
pointerEvents:
|
|
270
|
+
modalMode === ModalMode.EDIT || modalMode === ModalMode.CREATE ? 'auto' : 'none',
|
|
271
|
+
position:
|
|
272
|
+
modalMode === ModalMode.EDIT || modalMode === ModalMode.CREATE
|
|
273
|
+
? 'relative'
|
|
274
|
+
: 'absolute',
|
|
275
|
+
top: 0,
|
|
276
|
+
left: 0,
|
|
277
|
+
right: 0,
|
|
278
|
+
transition:
|
|
279
|
+
modalMode === ModalMode.EDIT || modalMode === ModalMode.CREATE
|
|
280
|
+
? 'opacity 0.15s ease-in-out'
|
|
281
|
+
: 'none',
|
|
282
|
+
}}
|
|
283
|
+
>
|
|
284
|
+
<CreateEditCase
|
|
285
|
+
edited={editedData}
|
|
286
|
+
setEdited={setEditedData}
|
|
287
|
+
editedCases={editedCases}
|
|
288
|
+
setEditedCases={setEditedCases}
|
|
289
|
+
modalMode={modalMode}
|
|
290
|
+
/>
|
|
291
|
+
</div>
|
|
292
|
+
)}
|
|
293
|
+
{clickedCase !== null && (
|
|
294
|
+
<div
|
|
295
|
+
style={{
|
|
296
|
+
opacity: modalMode === ModalMode.CASE_DETAILS ? 1 : 0,
|
|
297
|
+
pointerEvents: modalMode === ModalMode.CASE_DETAILS ? 'auto' : 'none',
|
|
298
|
+
position: modalMode === ModalMode.CASE_DETAILS ? 'relative' : 'absolute',
|
|
299
|
+
top: 0,
|
|
300
|
+
left: 0,
|
|
301
|
+
right: 0,
|
|
302
|
+
transition: modalMode === ModalMode.CASE_DETAILS ? 'opacity 0.15s ease-in-out' : 'none',
|
|
303
|
+
overflow: modalMode === ModalMode.CASE_DETAILS ? 'visible' : 'hidden',
|
|
304
|
+
maxHeight: modalMode === ModalMode.CASE_DETAILS ? 'none' : 0,
|
|
305
|
+
}}
|
|
306
|
+
>
|
|
307
|
+
<CaseDetails
|
|
308
|
+
selectedCase={clickedCase}
|
|
309
|
+
isVillage={isVillageDate(selectedCourtDate ? selectedCourtDate.MuniCode : '')}
|
|
310
|
+
/>
|
|
311
|
+
</div>
|
|
312
|
+
)}
|
|
313
|
+
</LocalizationProvider>
|
|
314
|
+
</DialogContent>
|
|
315
|
+
<ModalActions
|
|
316
|
+
modalMode={modalMode}
|
|
317
|
+
onDelete={handleDelete}
|
|
318
|
+
onSave={handleSave}
|
|
319
|
+
setModalMode={setModalMode}
|
|
320
|
+
setIsOpen={setIsOpen}
|
|
321
|
+
/>
|
|
322
|
+
</Dialog>
|
|
323
|
+
);
|
|
324
|
+
// #endregion
|
|
325
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import Button from '@mui/material/Button';
|
|
2
|
+
import DialogActions from '@mui/material/DialogActions';
|
|
3
|
+
import { ModalMode } from '@/types';
|
|
4
|
+
import { useRef, useState } from 'react';
|
|
5
|
+
|
|
6
|
+
export default function ModalActions({
|
|
7
|
+
modalMode,
|
|
8
|
+
onDelete,
|
|
9
|
+
onSave,
|
|
10
|
+
setModalMode,
|
|
11
|
+
setIsOpen,
|
|
12
|
+
}: {
|
|
13
|
+
modalMode: ModalMode;
|
|
14
|
+
onDelete: () => void;
|
|
15
|
+
onSave: () => void;
|
|
16
|
+
setModalMode: (mode: ModalMode) => void;
|
|
17
|
+
setIsOpen: (isOpen: boolean) => void;
|
|
18
|
+
}) {
|
|
19
|
+
const [showDelete, setShowDelete] = useState(false);
|
|
20
|
+
const clickTimes = useRef<number[]>([]);
|
|
21
|
+
|
|
22
|
+
const handleDivClick = () => {
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
clickTimes.current = clickTimes.current.filter((t) => now - t < 600);
|
|
25
|
+
clickTimes.current.push(now);
|
|
26
|
+
if (clickTimes.current.length >= 3) {
|
|
27
|
+
setShowDelete(true);
|
|
28
|
+
clickTimes.current = [];
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<>
|
|
34
|
+
<DialogActions
|
|
35
|
+
className='modal-footer'
|
|
36
|
+
style={{
|
|
37
|
+
display: 'flex',
|
|
38
|
+
justifyContent: 'space-between',
|
|
39
|
+
alignItems: 'center',
|
|
40
|
+
padding: '15px 15px',
|
|
41
|
+
borderTop: '1px solid var(--fc-border-color)',
|
|
42
|
+
backgroundColor: 'var(--bg) !important',
|
|
43
|
+
}}
|
|
44
|
+
>
|
|
45
|
+
<Button
|
|
46
|
+
className=''
|
|
47
|
+
onClick={
|
|
48
|
+
modalMode === ModalMode.DETAILS || modalMode === ModalMode.CREATE
|
|
49
|
+
? () => setIsOpen(false)
|
|
50
|
+
: () => setModalMode(ModalMode.DETAILS)
|
|
51
|
+
}
|
|
52
|
+
color='error'
|
|
53
|
+
variant='outlined'
|
|
54
|
+
style={{ margin: '0' }}
|
|
55
|
+
>
|
|
56
|
+
{modalMode === ModalMode.DETAILS || modalMode === ModalMode.CREATE
|
|
57
|
+
? 'Close'
|
|
58
|
+
: modalMode === ModalMode.EDIT
|
|
59
|
+
? 'Cancel'
|
|
60
|
+
: 'Back'}
|
|
61
|
+
</Button>
|
|
62
|
+
<div
|
|
63
|
+
style={{
|
|
64
|
+
width: 90,
|
|
65
|
+
height: 40,
|
|
66
|
+
display: 'flex',
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
justifyContent: 'center',
|
|
69
|
+
cursor: 'unset',
|
|
70
|
+
userSelect: 'none',
|
|
71
|
+
}}
|
|
72
|
+
onClick={handleDivClick}
|
|
73
|
+
>
|
|
74
|
+
{showDelete && (
|
|
75
|
+
<Button
|
|
76
|
+
className=''
|
|
77
|
+
onClick={onDelete}
|
|
78
|
+
color='error'
|
|
79
|
+
variant='outlined'
|
|
80
|
+
style={{ margin: '0' }}
|
|
81
|
+
>
|
|
82
|
+
Delete
|
|
83
|
+
</Button>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
<Button
|
|
87
|
+
className=''
|
|
88
|
+
onClick={modalMode === ModalMode.DETAILS ? () => setModalMode(ModalMode.EDIT) : onSave}
|
|
89
|
+
color='primary'
|
|
90
|
+
variant='contained'
|
|
91
|
+
style={{ margin: '0' }}
|
|
92
|
+
disabled={modalMode === ModalMode.CASE_DETAILS}
|
|
93
|
+
>
|
|
94
|
+
{modalMode === ModalMode.DETAILS ? 'Edit' : 'Save'}
|
|
95
|
+
</Button>
|
|
96
|
+
</DialogActions>
|
|
97
|
+
</>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import Box from '@mui/material/Box';
|
|
2
|
+
import type { CalendarFilterCtx } from '@/types';
|
|
3
|
+
import SearchBar from '../../Shared/SearchBar';
|
|
4
|
+
import ToggleButtonGroup from '@mui/material/ToggleButtonGroup';
|
|
5
|
+
import ToggleButton from '@mui/material/ToggleButton';
|
|
6
|
+
import Tooltip from '@mui/material/Tooltip';
|
|
7
|
+
import PendingActionsIcon from '@mui/icons-material/PendingActions';
|
|
8
|
+
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
|
9
|
+
import ManageSearchIcon from '@mui/icons-material/ManageSearch';
|
|
10
|
+
|
|
11
|
+
export default function CaseToolbar({
|
|
12
|
+
filterCtx,
|
|
13
|
+
setFilterCtx,
|
|
14
|
+
}: {
|
|
15
|
+
filterCtx: CalendarFilterCtx;
|
|
16
|
+
setFilterCtx: (ctx: CalendarFilterCtx) => void;
|
|
17
|
+
}) {
|
|
18
|
+
return (
|
|
19
|
+
<Box display='flex' alignItems='center' justifyContent='space-between' sx={{ p: 0, gap: 2 }} className='themed'>
|
|
20
|
+
{/* search bar */}
|
|
21
|
+
<SearchBar
|
|
22
|
+
searchTerm={filterCtx.searchTerm}
|
|
23
|
+
setSearchTerm={(term) => setFilterCtx({ ...filterCtx, searchTerm: term })}
|
|
24
|
+
/>
|
|
25
|
+
|
|
26
|
+
{/* show only unsettled */}
|
|
27
|
+
<ToggleButtonGroup>
|
|
28
|
+
<Tooltip title={`Show Only Unsettled`} arrow placement='top'>
|
|
29
|
+
<ToggleButton
|
|
30
|
+
value='unsettled'
|
|
31
|
+
selected={filterCtx.showOnlyUnsettled}
|
|
32
|
+
onClick={() =>
|
|
33
|
+
setFilterCtx({
|
|
34
|
+
...filterCtx,
|
|
35
|
+
showOnlyUnsettled: !filterCtx.showOnlyUnsettled,
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
>
|
|
39
|
+
<PendingActionsIcon fontSize='small' />
|
|
40
|
+
</ToggleButton>
|
|
41
|
+
</Tooltip>
|
|
42
|
+
</ToggleButtonGroup>
|
|
43
|
+
|
|
44
|
+
{/* show only without evidence */}
|
|
45
|
+
<ToggleButtonGroup>
|
|
46
|
+
<Tooltip title={`Show Only Without Evidence`} arrow placement='top'>
|
|
47
|
+
<ToggleButton
|
|
48
|
+
value='withoutEvidence'
|
|
49
|
+
selected={filterCtx.showOnlyWithoutEvidence}
|
|
50
|
+
onClick={() =>
|
|
51
|
+
setFilterCtx({
|
|
52
|
+
...filterCtx,
|
|
53
|
+
showOnlyWithoutEvidence: !filterCtx.showOnlyWithoutEvidence,
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
>
|
|
57
|
+
<WarningAmberIcon fontSize='small' />
|
|
58
|
+
</ToggleButton>
|
|
59
|
+
</Tooltip>
|
|
60
|
+
</ToggleButtonGroup>
|
|
61
|
+
|
|
62
|
+
{/* show only unreviewed */}
|
|
63
|
+
<ToggleButtonGroup>
|
|
64
|
+
<Tooltip title={`Show Only Unreviewed`} arrow placement='top'>
|
|
65
|
+
<ToggleButton
|
|
66
|
+
value='unreviewed'
|
|
67
|
+
selected={filterCtx.showOnlyUnreviewed}
|
|
68
|
+
onClick={() =>
|
|
69
|
+
setFilterCtx({
|
|
70
|
+
...filterCtx,
|
|
71
|
+
showOnlyUnreviewed: !filterCtx.showOnlyUnreviewed,
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
>
|
|
75
|
+
<ManageSearchIcon fontSize='small' />
|
|
76
|
+
</ToggleButton>
|
|
77
|
+
</Tooltip>
|
|
78
|
+
</ToggleButtonGroup>
|
|
79
|
+
</Box>
|
|
80
|
+
);
|
|
81
|
+
}
|