@propriety/court-calendar 0.0.13 → 0.0.15
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/.claude/settings.local.json +9 -8
- package/.github/workflows/publish.yml +28 -0
- package/README.md +237 -0
- package/dist/_components/Modal/Modal.d.ts +2 -1
- package/dist/helpers/courtDates.d.ts +2 -2
- package/dist/helpers/formatter.d.ts +1 -0
- package/dist/index.mjs +1641 -1612
- package/package.json +1 -1
- package/src/_components/CCalendar.tsx +4 -3
- package/src/_components/Modal/CreateEdit/DateSelector.tsx +42 -42
- package/src/_components/Modal/Modal.css +15 -15
- package/src/_components/Modal/Modal.tsx +4 -0
- package/src/_components/Modal/View/InfoBoxBtn.css +39 -39
- package/src/_components/Modal/View/InfoBoxBtn.tsx +29 -29
- package/src/_components/Shared/FormRow.tsx +37 -37
- package/src/_components/Shared/SearchBar.tsx +87 -87
- package/src/helpers/cases.ts +10 -2
- package/src/helpers/courtDates.ts +13 -7
- package/src/helpers/formatter.ts +28 -0
- package/src/index.ts +2 -2
- package/tsconfig.app.json +32 -32
- package/tsconfig.json +4 -4
- package/tsconfig.node.json +30 -30
package/package.json
CHANGED
|
@@ -59,7 +59,7 @@ export default function CCalendar({ apiKey, activeUser }: { apiKey: string; acti
|
|
|
59
59
|
searchTerm: '',
|
|
60
60
|
municode: null,
|
|
61
61
|
user: null,
|
|
62
|
-
showOnlyUnsettled:
|
|
62
|
+
showOnlyUnsettled: true,
|
|
63
63
|
showOnlyWithoutEvidence: false,
|
|
64
64
|
showOnlyUnreviewed: false,
|
|
65
65
|
});
|
|
@@ -81,7 +81,7 @@ export default function CCalendar({ apiKey, activeUser }: { apiKey: string; acti
|
|
|
81
81
|
async function getAllCourtDates(apiKey: string) {
|
|
82
82
|
// Try to load from IndexedDB cache
|
|
83
83
|
const cachedDates = await getCourtDatesCache();
|
|
84
|
-
if (cachedDates) {
|
|
84
|
+
if (cachedDates && cachedDates.length > 0) {
|
|
85
85
|
setCourtDates(cachedDates);
|
|
86
86
|
} else {
|
|
87
87
|
getAllDates(apiKey)
|
|
@@ -132,7 +132,7 @@ export default function CCalendar({ apiKey, activeUser }: { apiKey: string; acti
|
|
|
132
132
|
updateCourtDateInMemory(updatedCourtDate);
|
|
133
133
|
|
|
134
134
|
// Then persist to API in background
|
|
135
|
-
updateCourtDate(courtDateId, updatedData, apiKey).then((success) => {
|
|
135
|
+
updateCourtDate(courtDateId, updatedData, apiKey, undefined, activeUser).then((success) => {
|
|
136
136
|
if (!success) {
|
|
137
137
|
// Revert on failure
|
|
138
138
|
updateCourtDateInMemory(courtDate);
|
|
@@ -718,6 +718,7 @@ export default function CCalendar({ apiKey, activeUser }: { apiKey: string; acti
|
|
|
718
718
|
deleteCases={deleteCaseMemoryAndCache}
|
|
719
719
|
isFetchingCases={isFetchingCases}
|
|
720
720
|
apiKey={apiKey}
|
|
721
|
+
activeUser={activeUser}
|
|
721
722
|
/>
|
|
722
723
|
<Tooltip id='event-tooltip' className='cc-tooltip' />
|
|
723
724
|
</div>
|
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
import { DatePicker } from '@mui/x-date-pickers';
|
|
2
|
-
import dayjs from 'dayjs';
|
|
3
|
-
import type { CourtDate } from '@/types';
|
|
4
|
-
|
|
5
|
-
export default function DateSelector({
|
|
6
|
-
editing,
|
|
7
|
-
property,
|
|
8
|
-
label,
|
|
9
|
-
setEdited,
|
|
10
|
-
disabled,
|
|
11
|
-
}: {
|
|
12
|
-
editing: CourtDate | undefined;
|
|
13
|
-
property: keyof CourtDate;
|
|
14
|
-
label: string;
|
|
15
|
-
setEdited: (data: CourtDate) => void;
|
|
16
|
-
disabled: boolean;
|
|
17
|
-
}) {
|
|
18
|
-
return (
|
|
19
|
-
<DatePicker
|
|
20
|
-
label={label}
|
|
21
|
-
disabled={disabled}
|
|
22
|
-
value={
|
|
23
|
-
editing && editing[property] && typeof editing[property] !== 'boolean'
|
|
24
|
-
? dayjs(editing[property] as string | number | Date)
|
|
25
|
-
: null
|
|
26
|
-
}
|
|
27
|
-
onChange={(newValue) => {
|
|
28
|
-
setEdited({
|
|
29
|
-
...editing!,
|
|
30
|
-
[property]: newValue!.toDate(),
|
|
31
|
-
});
|
|
32
|
-
}}
|
|
33
|
-
reduceAnimations
|
|
34
|
-
sx={{ width: '100%' }}
|
|
35
|
-
slotProps={{
|
|
36
|
-
popper: {
|
|
37
|
-
className: 'themed',
|
|
38
|
-
},
|
|
39
|
-
}}
|
|
40
|
-
/>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
1
|
+
import { DatePicker } from '@mui/x-date-pickers';
|
|
2
|
+
import dayjs from 'dayjs';
|
|
3
|
+
import type { CourtDate } from '@/types';
|
|
4
|
+
|
|
5
|
+
export default function DateSelector({
|
|
6
|
+
editing,
|
|
7
|
+
property,
|
|
8
|
+
label,
|
|
9
|
+
setEdited,
|
|
10
|
+
disabled,
|
|
11
|
+
}: {
|
|
12
|
+
editing: CourtDate | undefined;
|
|
13
|
+
property: keyof CourtDate;
|
|
14
|
+
label: string;
|
|
15
|
+
setEdited: (data: CourtDate) => void;
|
|
16
|
+
disabled: boolean;
|
|
17
|
+
}) {
|
|
18
|
+
return (
|
|
19
|
+
<DatePicker
|
|
20
|
+
label={label}
|
|
21
|
+
disabled={disabled}
|
|
22
|
+
value={
|
|
23
|
+
editing && editing[property] && typeof editing[property] !== 'boolean'
|
|
24
|
+
? dayjs(editing[property] as string | number | Date)
|
|
25
|
+
: null
|
|
26
|
+
}
|
|
27
|
+
onChange={(newValue) => {
|
|
28
|
+
setEdited({
|
|
29
|
+
...editing!,
|
|
30
|
+
[property]: newValue!.toDate(),
|
|
31
|
+
});
|
|
32
|
+
}}
|
|
33
|
+
reduceAnimations
|
|
34
|
+
sx={{ width: '100%' }}
|
|
35
|
+
slotProps={{
|
|
36
|
+
popper: {
|
|
37
|
+
className: 'themed',
|
|
38
|
+
},
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -1,15 +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
|
-
}
|
|
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
|
+
}
|
|
@@ -29,6 +29,7 @@ export default function CCModal({
|
|
|
29
29
|
deleteCases,
|
|
30
30
|
isFetchingCases,
|
|
31
31
|
apiKey,
|
|
32
|
+
activeUser,
|
|
32
33
|
}: {
|
|
33
34
|
modalIsOpen: boolean;
|
|
34
35
|
modalMode: ModalMode;
|
|
@@ -43,6 +44,7 @@ export default function CCModal({
|
|
|
43
44
|
deleteCases: (courtDateId: string) => void;
|
|
44
45
|
isFetchingCases: boolean;
|
|
45
46
|
apiKey: string;
|
|
47
|
+
activeUser: number;
|
|
46
48
|
}) {
|
|
47
49
|
const [editedData, setEditedData] = useState<CourtDate | undefined>(undefined);
|
|
48
50
|
const [editedCases, setEditedCases] = useState<Case[]>([]);
|
|
@@ -149,6 +151,7 @@ export default function CCModal({
|
|
|
149
151
|
editedData,
|
|
150
152
|
apiKey,
|
|
151
153
|
editedCases.map((c) => c.SCARIndexNumber),
|
|
154
|
+
activeUser,
|
|
152
155
|
);
|
|
153
156
|
// Update cache after save
|
|
154
157
|
const courtDateId = editedData.CourtDateID;
|
|
@@ -171,6 +174,7 @@ export default function CCModal({
|
|
|
171
174
|
editedData.MuniCode,
|
|
172
175
|
apiKey,
|
|
173
176
|
editedCases.map((c) => c.SCARIndexNumber),
|
|
177
|
+
editedData.HearingTime || undefined,
|
|
174
178
|
);
|
|
175
179
|
if (id) {
|
|
176
180
|
const updatedData = { ...editedData, CourtDateID: id };
|
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
.info-box-btn {
|
|
2
|
-
cursor: pointer;
|
|
3
|
-
width: 100%;
|
|
4
|
-
display: inherit;
|
|
5
|
-
position: relative;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
.info-box-btn > .info-box-btn-cover {
|
|
9
|
-
position: absolute;
|
|
10
|
-
top: 0%;
|
|
11
|
-
width: 5%;
|
|
12
|
-
right: 0;
|
|
13
|
-
height: 100%;
|
|
14
|
-
background-color: var(--text) !important;
|
|
15
|
-
color: var(--text);
|
|
16
|
-
z-index: 99999;
|
|
17
|
-
border-top-right-radius: 8px;
|
|
18
|
-
border-bottom-right-radius: 8px;
|
|
19
|
-
overflow: hidden;
|
|
20
|
-
display: flex;
|
|
21
|
-
justify-content: center;
|
|
22
|
-
align-items: center;
|
|
23
|
-
opacity: 0.7;
|
|
24
|
-
transition:
|
|
25
|
-
background-color 0.3s,
|
|
26
|
-
height 0.3s,
|
|
27
|
-
width 0.3s,
|
|
28
|
-
top 0.3s,
|
|
29
|
-
border-radius 0.3s,
|
|
30
|
-
opacity 0.3s;
|
|
31
|
-
}
|
|
32
|
-
.info-box-btn:hover > .info-box-btn-cover {
|
|
33
|
-
height: 100%;
|
|
34
|
-
width: 100%;
|
|
35
|
-
background-color: var(--fc-today-bg-color) !important;
|
|
36
|
-
top: 0;
|
|
37
|
-
border-radius: 8px;
|
|
38
|
-
opacity: 1;
|
|
39
|
-
}
|
|
1
|
+
.info-box-btn {
|
|
2
|
+
cursor: pointer;
|
|
3
|
+
width: 100%;
|
|
4
|
+
display: inherit;
|
|
5
|
+
position: relative;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.info-box-btn > .info-box-btn-cover {
|
|
9
|
+
position: absolute;
|
|
10
|
+
top: 0%;
|
|
11
|
+
width: 5%;
|
|
12
|
+
right: 0;
|
|
13
|
+
height: 100%;
|
|
14
|
+
background-color: var(--text) !important;
|
|
15
|
+
color: var(--text);
|
|
16
|
+
z-index: 99999;
|
|
17
|
+
border-top-right-radius: 8px;
|
|
18
|
+
border-bottom-right-radius: 8px;
|
|
19
|
+
overflow: hidden;
|
|
20
|
+
display: flex;
|
|
21
|
+
justify-content: center;
|
|
22
|
+
align-items: center;
|
|
23
|
+
opacity: 0.7;
|
|
24
|
+
transition:
|
|
25
|
+
background-color 0.3s,
|
|
26
|
+
height 0.3s,
|
|
27
|
+
width 0.3s,
|
|
28
|
+
top 0.3s,
|
|
29
|
+
border-radius 0.3s,
|
|
30
|
+
opacity 0.3s;
|
|
31
|
+
}
|
|
32
|
+
.info-box-btn:hover > .info-box-btn-cover {
|
|
33
|
+
height: 100%;
|
|
34
|
+
width: 100%;
|
|
35
|
+
background-color: var(--fc-today-bg-color) !important;
|
|
36
|
+
top: 0;
|
|
37
|
+
border-radius: 8px;
|
|
38
|
+
opacity: 1;
|
|
39
|
+
}
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import Typography from '@mui/material/Typography';
|
|
2
|
-
import InfoBox from './InfoBox';
|
|
3
|
-
import './InfoBoxBtn.css';
|
|
4
|
-
import Stack from '@mui/material/Stack';
|
|
5
|
-
import Box from '@mui/material/Box';
|
|
6
|
-
|
|
7
|
-
export default function InfoBoxBtn({
|
|
8
|
-
label,
|
|
9
|
-
info,
|
|
10
|
-
btnLabel,
|
|
11
|
-
btnIcon,
|
|
12
|
-
onBtnClick,
|
|
13
|
-
}: {
|
|
14
|
-
label: string;
|
|
15
|
-
info: string;
|
|
16
|
-
btnLabel?: string;
|
|
17
|
-
btnIcon?: React.ReactNode;
|
|
18
|
-
onBtnClick?: () => void;
|
|
19
|
-
}) {
|
|
20
|
-
return (
|
|
21
|
-
<Box className='info-box-btn' onClick={onBtnClick}>
|
|
22
|
-
<Stack className='info-box-btn-cover' direction={'column'}>
|
|
23
|
-
<Typography variant={'h6'}>{btnLabel}</Typography>
|
|
24
|
-
{btnIcon}
|
|
25
|
-
</Stack>
|
|
26
|
-
<InfoBox label={label} info={info} />
|
|
27
|
-
</Box>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
1
|
+
import Typography from '@mui/material/Typography';
|
|
2
|
+
import InfoBox from './InfoBox';
|
|
3
|
+
import './InfoBoxBtn.css';
|
|
4
|
+
import Stack from '@mui/material/Stack';
|
|
5
|
+
import Box from '@mui/material/Box';
|
|
6
|
+
|
|
7
|
+
export default function InfoBoxBtn({
|
|
8
|
+
label,
|
|
9
|
+
info,
|
|
10
|
+
btnLabel,
|
|
11
|
+
btnIcon,
|
|
12
|
+
onBtnClick,
|
|
13
|
+
}: {
|
|
14
|
+
label: string;
|
|
15
|
+
info: string;
|
|
16
|
+
btnLabel?: string;
|
|
17
|
+
btnIcon?: React.ReactNode;
|
|
18
|
+
onBtnClick?: () => void;
|
|
19
|
+
}) {
|
|
20
|
+
return (
|
|
21
|
+
<Box className='info-box-btn' onClick={onBtnClick}>
|
|
22
|
+
<Stack className='info-box-btn-cover' direction={'column'}>
|
|
23
|
+
<Typography variant={'h6'}>{btnLabel}</Typography>
|
|
24
|
+
{btnIcon}
|
|
25
|
+
</Stack>
|
|
26
|
+
<InfoBox label={label} info={info} />
|
|
27
|
+
</Box>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
import Box from '@mui/material/Box';
|
|
2
|
-
import Stack from '@mui/material/Stack';
|
|
3
|
-
import Typography from '@mui/material/Typography';
|
|
4
|
-
|
|
5
|
-
export default function FormRow({
|
|
6
|
-
children,
|
|
7
|
-
gap = 7,
|
|
8
|
-
header,
|
|
9
|
-
subheader,
|
|
10
|
-
}: {
|
|
11
|
-
children: React.ReactNode;
|
|
12
|
-
gap?: number;
|
|
13
|
-
header?: string;
|
|
14
|
-
subheader?: string;
|
|
15
|
-
}) {
|
|
16
|
-
return (
|
|
17
|
-
<>
|
|
18
|
-
<Stack direction='column' spacing={0.5} mb={header !== undefined ? 3 : 0}>
|
|
19
|
-
<Typography variant='h6'>{header}</Typography>
|
|
20
|
-
<Typography variant='body2' sx={{ opacity: 0.85 }}>
|
|
21
|
-
{subheader}
|
|
22
|
-
</Typography>
|
|
23
|
-
</Stack>
|
|
24
|
-
<Box
|
|
25
|
-
marginTop={1}
|
|
26
|
-
marginBottom={1}
|
|
27
|
-
display='flex'
|
|
28
|
-
flexDirection='row'
|
|
29
|
-
alignItems={'center'}
|
|
30
|
-
justifyContent={'space-between'}
|
|
31
|
-
gap={gap}
|
|
32
|
-
>
|
|
33
|
-
{children}
|
|
34
|
-
</Box>
|
|
35
|
-
</>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
1
|
+
import Box from '@mui/material/Box';
|
|
2
|
+
import Stack from '@mui/material/Stack';
|
|
3
|
+
import Typography from '@mui/material/Typography';
|
|
4
|
+
|
|
5
|
+
export default function FormRow({
|
|
6
|
+
children,
|
|
7
|
+
gap = 7,
|
|
8
|
+
header,
|
|
9
|
+
subheader,
|
|
10
|
+
}: {
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
gap?: number;
|
|
13
|
+
header?: string;
|
|
14
|
+
subheader?: string;
|
|
15
|
+
}) {
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
<Stack direction='column' spacing={0.5} mb={header !== undefined ? 3 : 0}>
|
|
19
|
+
<Typography variant='h6'>{header}</Typography>
|
|
20
|
+
<Typography variant='body2' sx={{ opacity: 0.85 }}>
|
|
21
|
+
{subheader}
|
|
22
|
+
</Typography>
|
|
23
|
+
</Stack>
|
|
24
|
+
<Box
|
|
25
|
+
marginTop={1}
|
|
26
|
+
marginBottom={1}
|
|
27
|
+
display='flex'
|
|
28
|
+
flexDirection='row'
|
|
29
|
+
alignItems={'center'}
|
|
30
|
+
justifyContent={'space-between'}
|
|
31
|
+
gap={gap}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
</Box>
|
|
35
|
+
</>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
@@ -1,87 +1,87 @@
|
|
|
1
|
-
import InputAdornment from '@mui/material/InputAdornment';
|
|
2
|
-
import TextField from '@mui/material/TextField';
|
|
3
|
-
import CancelIcon from '@mui/icons-material/Cancel';
|
|
4
|
-
import SearchIcon from '@mui/icons-material/Search';
|
|
5
|
-
import IconButton from '@mui/material/IconButton';
|
|
6
|
-
import { useState, useEffect, useRef } from 'react';
|
|
7
|
-
|
|
8
|
-
export default function SearchBar({
|
|
9
|
-
searchTerm,
|
|
10
|
-
setSearchTerm,
|
|
11
|
-
width,
|
|
12
|
-
}: {
|
|
13
|
-
searchTerm: string;
|
|
14
|
-
setSearchTerm: (term: string) => void;
|
|
15
|
-
width?: string | number;
|
|
16
|
-
}) {
|
|
17
|
-
const [inputValue, setInputValue] = useState(searchTerm);
|
|
18
|
-
const inputRef = useRef<HTMLInputElement>(null);
|
|
19
|
-
|
|
20
|
-
// Keep local inputValue in sync with searchTerm prop
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
setInputValue(searchTerm);
|
|
23
|
-
}, [searchTerm]);
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
const handler = setTimeout(() => {
|
|
27
|
-
if (inputValue !== searchTerm) {
|
|
28
|
-
setSearchTerm(inputValue);
|
|
29
|
-
}
|
|
30
|
-
}, 300);
|
|
31
|
-
return () => clearTimeout(handler);
|
|
32
|
-
}, [inputValue, searchTerm, setSearchTerm]);
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<TextField
|
|
36
|
-
aria-label='Search'
|
|
37
|
-
placeholder='Search...'
|
|
38
|
-
size='small'
|
|
39
|
-
variant='outlined'
|
|
40
|
-
value={inputValue}
|
|
41
|
-
onChange={(e) => setInputValue(e.target.value)}
|
|
42
|
-
fullWidth={width === undefined}
|
|
43
|
-
style={{ width: width ?? '100%' }}
|
|
44
|
-
sx={{
|
|
45
|
-
'& .MuiOutlinedInput-root:hover': {
|
|
46
|
-
backgroundColor: 'var(--fc-today-bg-color) !important',
|
|
47
|
-
'& .MuiInputAdornment-root': {
|
|
48
|
-
backgroundColor: 'inherit !important',
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
}}
|
|
52
|
-
slotProps={{
|
|
53
|
-
input: {
|
|
54
|
-
ref: inputRef,
|
|
55
|
-
sx: { paddingY: '2px' },
|
|
56
|
-
startAdornment: (
|
|
57
|
-
<InputAdornment
|
|
58
|
-
position='start'
|
|
59
|
-
onClick={() => inputRef.current?.focus()}
|
|
60
|
-
sx={{ cursor: 'pointer' }}
|
|
61
|
-
>
|
|
62
|
-
<SearchIcon
|
|
63
|
-
fontSize='small'
|
|
64
|
-
sx={{
|
|
65
|
-
userSelect: 'none',
|
|
66
|
-
pointerEvents: 'none',
|
|
67
|
-
}}
|
|
68
|
-
/>
|
|
69
|
-
</InputAdornment>
|
|
70
|
-
),
|
|
71
|
-
endAdornment: inputValue ? (
|
|
72
|
-
<InputAdornment position='end'>
|
|
73
|
-
<IconButton
|
|
74
|
-
size='small'
|
|
75
|
-
aria-label='Clear search'
|
|
76
|
-
sx={{ marginRight: -0.75, paddingY: '2px' }}
|
|
77
|
-
onClick={() => setInputValue('')}
|
|
78
|
-
>
|
|
79
|
-
<CancelIcon fontSize='small' />
|
|
80
|
-
</IconButton>
|
|
81
|
-
</InputAdornment>
|
|
82
|
-
) : null,
|
|
83
|
-
},
|
|
84
|
-
}}
|
|
85
|
-
/>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
1
|
+
import InputAdornment from '@mui/material/InputAdornment';
|
|
2
|
+
import TextField from '@mui/material/TextField';
|
|
3
|
+
import CancelIcon from '@mui/icons-material/Cancel';
|
|
4
|
+
import SearchIcon from '@mui/icons-material/Search';
|
|
5
|
+
import IconButton from '@mui/material/IconButton';
|
|
6
|
+
import { useState, useEffect, useRef } from 'react';
|
|
7
|
+
|
|
8
|
+
export default function SearchBar({
|
|
9
|
+
searchTerm,
|
|
10
|
+
setSearchTerm,
|
|
11
|
+
width,
|
|
12
|
+
}: {
|
|
13
|
+
searchTerm: string;
|
|
14
|
+
setSearchTerm: (term: string) => void;
|
|
15
|
+
width?: string | number;
|
|
16
|
+
}) {
|
|
17
|
+
const [inputValue, setInputValue] = useState(searchTerm);
|
|
18
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
19
|
+
|
|
20
|
+
// Keep local inputValue in sync with searchTerm prop
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
setInputValue(searchTerm);
|
|
23
|
+
}, [searchTerm]);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const handler = setTimeout(() => {
|
|
27
|
+
if (inputValue !== searchTerm) {
|
|
28
|
+
setSearchTerm(inputValue);
|
|
29
|
+
}
|
|
30
|
+
}, 300);
|
|
31
|
+
return () => clearTimeout(handler);
|
|
32
|
+
}, [inputValue, searchTerm, setSearchTerm]);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<TextField
|
|
36
|
+
aria-label='Search'
|
|
37
|
+
placeholder='Search...'
|
|
38
|
+
size='small'
|
|
39
|
+
variant='outlined'
|
|
40
|
+
value={inputValue}
|
|
41
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
42
|
+
fullWidth={width === undefined}
|
|
43
|
+
style={{ width: width ?? '100%' }}
|
|
44
|
+
sx={{
|
|
45
|
+
'& .MuiOutlinedInput-root:hover': {
|
|
46
|
+
backgroundColor: 'var(--fc-today-bg-color) !important',
|
|
47
|
+
'& .MuiInputAdornment-root': {
|
|
48
|
+
backgroundColor: 'inherit !important',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
}}
|
|
52
|
+
slotProps={{
|
|
53
|
+
input: {
|
|
54
|
+
ref: inputRef,
|
|
55
|
+
sx: { paddingY: '2px' },
|
|
56
|
+
startAdornment: (
|
|
57
|
+
<InputAdornment
|
|
58
|
+
position='start'
|
|
59
|
+
onClick={() => inputRef.current?.focus()}
|
|
60
|
+
sx={{ cursor: 'pointer' }}
|
|
61
|
+
>
|
|
62
|
+
<SearchIcon
|
|
63
|
+
fontSize='small'
|
|
64
|
+
sx={{
|
|
65
|
+
userSelect: 'none',
|
|
66
|
+
pointerEvents: 'none',
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
</InputAdornment>
|
|
70
|
+
),
|
|
71
|
+
endAdornment: inputValue ? (
|
|
72
|
+
<InputAdornment position='end'>
|
|
73
|
+
<IconButton
|
|
74
|
+
size='small'
|
|
75
|
+
aria-label='Clear search'
|
|
76
|
+
sx={{ marginRight: -0.75, paddingY: '2px' }}
|
|
77
|
+
onClick={() => setInputValue('')}
|
|
78
|
+
>
|
|
79
|
+
<CancelIcon fontSize='small' />
|
|
80
|
+
</IconButton>
|
|
81
|
+
</InputAdornment>
|
|
82
|
+
) : null,
|
|
83
|
+
},
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
package/src/helpers/cases.ts
CHANGED
|
@@ -62,11 +62,19 @@ export function isCaseSettled(c: Case, isVillage: boolean): boolean {
|
|
|
62
62
|
if (!isVillage) {
|
|
63
63
|
// town
|
|
64
64
|
if (c.SCARDeterminationAction === null) return false;
|
|
65
|
-
if (
|
|
65
|
+
if (
|
|
66
|
+
c.SCARDeterminationAction.toLowerCase().includes('s') ||
|
|
67
|
+
c.SCARDeterminationAction.toLowerCase().includes('w')
|
|
68
|
+
)
|
|
69
|
+
return true;
|
|
66
70
|
} else {
|
|
67
71
|
// village
|
|
68
72
|
if (c.VillageSCARDeterminationAction === null) return false;
|
|
69
|
-
if (
|
|
73
|
+
if (
|
|
74
|
+
c.VillageSCARDeterminationAction.toLowerCase().includes('s') ||
|
|
75
|
+
c.VillageSCARDeterminationAction.toLowerCase().includes('w')
|
|
76
|
+
)
|
|
77
|
+
return true;
|
|
70
78
|
}
|
|
71
79
|
return false;
|
|
72
80
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HearingType, Lifecycle, SourceType, type CourtDate } from '../types';
|
|
2
|
-
import {
|
|
2
|
+
import { formatDateTimeForAPI } from './formatter';
|
|
3
3
|
|
|
4
4
|
export async function getAllDates(apiKey: string): Promise<Array<CourtDate>> {
|
|
5
5
|
if (!apiKey) return [];
|
|
@@ -30,8 +30,10 @@ export async function updateCourtDate(
|
|
|
30
30
|
updatedData: Partial<CourtDate>,
|
|
31
31
|
apiKey: string,
|
|
32
32
|
courtCases?: string[],
|
|
33
|
+
user?: number,
|
|
33
34
|
): Promise<boolean> {
|
|
34
35
|
if (!apiKey) return false;
|
|
36
|
+
console.log(`Updating court date ${courtDateId} by user: ${user} with data:`, updatedData);
|
|
35
37
|
const res = await fetch(`https://utils.aventine.ai/court-dates/${courtDateId}/update`, {
|
|
36
38
|
method: 'PUT',
|
|
37
39
|
mode: 'cors',
|
|
@@ -41,20 +43,23 @@ export async function updateCourtDate(
|
|
|
41
43
|
},
|
|
42
44
|
body: JSON.stringify({
|
|
43
45
|
adjournment_date: updatedData.AdjournmentDate
|
|
44
|
-
?
|
|
46
|
+
? formatDateTimeForAPI(new Date(updatedData.AdjournmentDate), updatedData.HearingTime || undefined)
|
|
47
|
+
: undefined,
|
|
48
|
+
court_date: updatedData.CourtDate
|
|
49
|
+
? formatDateTimeForAPI(new Date(updatedData.CourtDate), updatedData.HearingTime || undefined)
|
|
45
50
|
: undefined,
|
|
46
|
-
court_date: updatedData.CourtDate ? formatDateForAPI(new Date(updatedData.CourtDate)) : undefined,
|
|
47
51
|
hearing_link: updatedData.HearingLink !== '' ? updatedData.HearingLink : undefined,
|
|
48
|
-
hearing_officer: updatedData.HearingOfficer !==
|
|
52
|
+
hearing_officer: updatedData.HearingOfficer !== undefined ? updatedData.HearingOfficer : undefined,
|
|
49
53
|
hearing_time: updatedData.HearingTime !== '' ? updatedData.HearingTime : undefined,
|
|
50
54
|
hearing_type: updatedData.Type !== HearingType.UNKNOWN ? updatedData.Type : undefined,
|
|
51
55
|
muni: updatedData.MuniCode,
|
|
52
|
-
|
|
53
|
-
|
|
56
|
+
first_chair: updatedData.FirstChair !== undefined ? updatedData.FirstChair : undefined,
|
|
57
|
+
second_chair: updatedData.SecondChair !== undefined ? updatedData.SecondChair : undefined,
|
|
54
58
|
lifecycle: updatedData.Lifecycle !== Lifecycle.SCHEDULED ? updatedData.Lifecycle : undefined,
|
|
55
59
|
court_cases: courtCases || undefined,
|
|
56
60
|
is_adjourned: updatedData.IsAdjourned !== undefined ? updatedData.IsAdjourned : undefined,
|
|
57
61
|
notes: updatedData.Notes !== null ? updatedData.Notes : undefined,
|
|
62
|
+
edit_user: user !== undefined ? user : undefined,
|
|
58
63
|
}),
|
|
59
64
|
});
|
|
60
65
|
if (res.ok) {
|
|
@@ -90,6 +95,7 @@ export async function createCourtDate(
|
|
|
90
95
|
muniCode: string,
|
|
91
96
|
apiKey: string,
|
|
92
97
|
courtCases?: string[],
|
|
98
|
+
hearingTime?: string,
|
|
93
99
|
): Promise<number | null> {
|
|
94
100
|
if (!apiKey) return null;
|
|
95
101
|
const res = await fetch(`https://utils.aventine.ai/court-dates/create`, {
|
|
@@ -100,7 +106,7 @@ export async function createCourtDate(
|
|
|
100
106
|
'x-api-key': apiKey,
|
|
101
107
|
},
|
|
102
108
|
body: JSON.stringify({
|
|
103
|
-
court_date:
|
|
109
|
+
court_date: formatDateTimeForAPI(new Date(courtDate), hearingTime || undefined),
|
|
104
110
|
muni: muniCode,
|
|
105
111
|
source: SourceType.MANUAL,
|
|
106
112
|
court_cases: courtCases || [],
|