@propriety/court-calendar 1.0.21 → 1.0.23

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@propriety/court-calendar",
3
3
  "private": false,
4
- "version": "1.0.21",
4
+ "version": "1.0.23",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -467,3 +467,71 @@
467
467
  transform: scale(1.1);
468
468
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
469
469
  }
470
+
471
+ /* Print styles */
472
+ @page {
473
+ size: landscape;
474
+ margin: 0.5in;
475
+ }
476
+
477
+ @media print {
478
+ /* Hide toolbar, tooltips, modal */
479
+ #ccalendar-container > .MuiStack-root:first-child,
480
+ #event-tooltip,
481
+ .ReactModal__Overlay,
482
+ .MuiDataGrid-footerContainer {
483
+ display: none !important;
484
+ }
485
+
486
+ /* Force calendar to fit page width */
487
+ #ccalendar-container {
488
+ width: 100% !important;
489
+ overflow: visible !important;
490
+ }
491
+
492
+ .fc.fc-media-screen,
493
+ .fc .fc-scrollgrid,
494
+ .fc .fc-scrollgrid-section > td,
495
+ .fc .fc-daygrid-body,
496
+ .fc table {
497
+ width: 100% !important;
498
+ max-width: 100% !important;
499
+ }
500
+
501
+ .fc .fc-scrollgrid {
502
+ overflow: visible !important;
503
+ }
504
+
505
+ /* Let columns size naturally instead of fixed pixel widths */
506
+ .fc colgroup col {
507
+ width: auto !important;
508
+ }
509
+
510
+ /* Remove scroll constraints on calendar day cells */
511
+ .fc-daygrid-day-events {
512
+ max-height: none !important;
513
+ min-height: auto !important;
514
+ overflow: visible !important;
515
+ }
516
+
517
+ .month-day-cell {
518
+ height: auto !important;
519
+ }
520
+
521
+ /* Remove fixed height on table container */
522
+ #ccalendar-container .MuiDataGrid-root {
523
+ height: auto !important;
524
+ max-height: none !important;
525
+ }
526
+
527
+ #ccalendar-container .MuiDataGrid-virtualScroller {
528
+ overflow: visible !important;
529
+ height: auto !important;
530
+ }
531
+
532
+ /* Ensure colors print */
533
+ * {
534
+ -webkit-print-color-adjust: exact !important;
535
+ print-color-adjust: exact !important;
536
+ }
537
+ }
@@ -81,6 +81,7 @@ function CCalendarInner({ apiKey, activeUser }: { apiKey: string; activeUser: nu
81
81
  const [selectedCases, setSelectedCases] = useState<Case[]>([]);
82
82
  const [isFetchingCases, setIsFetchingCases] = useState<boolean>(false);
83
83
  const [searchedDateIDs, setSearchedDateIDs] = useState<Array<number>>([]);
84
+ const [isPrinting, setIsPrinting] = useState(false);
84
85
  // Track previously rendered event IDs to avoid reanimating unchanged events
85
86
  const prevEventIdsRef = useRef<Set<string>>(new Set());
86
87
  // Track previous view to detect actual view changes vs calendarApi remounts
@@ -600,6 +601,21 @@ function CCalendarInner({ apiKey, activeUser }: { apiKey: string; activeUser: nu
600
601
  }, [currentView, calendarApi]);
601
602
  // #endregion
602
603
 
604
+ // #region printing
605
+ function handlePrint() {
606
+ setIsPrinting(true);
607
+ }
608
+
609
+ useEffect(() => {
610
+ if (!isPrinting) return;
611
+ const timeout = setTimeout(() => {
612
+ window.print();
613
+ setIsPrinting(false);
614
+ }, 100);
615
+ return () => clearTimeout(timeout);
616
+ }, [isPrinting]);
617
+ // #endregion
618
+
603
619
  // #region Event Mounting (Tooltips & Animations)
604
620
  function handleEventMount(info: EventMountArg) {
605
621
  // biome-ignore lint/suspicious/noExplicitAny: i dont know the type
@@ -684,6 +700,7 @@ function CCalendarInner({ apiKey, activeUser }: { apiKey: string; activeUser: nu
684
700
  filterCtx={filterCtx}
685
701
  setFilterCtx={setFilterCtx}
686
702
  handleCreateClick={() => createCourtDate(calendarApi?.getDate() || currentDate || new Date())}
703
+ onPrint={handlePrint}
687
704
  currentView={currentView}
688
705
  setCurrentView={setCurrentView}
689
706
  currentDate={currentDate}
@@ -732,6 +749,7 @@ function CCalendarInner({ apiKey, activeUser }: { apiKey: string; activeUser: nu
732
749
  currentDate={currentDate}
733
750
  onUpdateChair={handleUpdateChair}
734
751
  allCases={allCases}
752
+ isPrinting={isPrinting}
735
753
  />
736
754
  )}
737
755
  <Modal
@@ -11,12 +11,14 @@ export default function CalendarList({
11
11
  currentDate,
12
12
  onUpdateChair,
13
13
  allCases,
14
+ isPrinting,
14
15
  }: {
15
16
  filteredDates: CourtDate[];
16
17
  setSelectedDate: (date: CourtDate) => void;
17
18
  currentDate: Date;
18
19
  onUpdateChair?: (courtDateId: number, position: 'first' | 'second', userId: number | null) => void;
19
20
  allCases: Record<string, Case[]>;
21
+ isPrinting?: boolean;
20
22
  }) {
21
23
  const { getTownshipName, getCountyName } = useReferenceData();
22
24
  const countyColors: Record<string, string> = {
@@ -264,11 +266,13 @@ export default function CalendarList({
264
266
  }));
265
267
 
266
268
  return (
267
- <div style={{ height: 600, width: '100%' }}>
269
+ <div style={{ height: isPrinting ? 'auto' : 600, width: '100%' }}>
268
270
  <DataGrid
269
271
  rows={rows}
270
272
  columns={columns}
271
273
  className='themed'
274
+ autoHeight={isPrinting}
275
+ hideFooter={isPrinting}
272
276
  initialState={{
273
277
  sorting: {
274
278
  sortModel: [{ field: 'courtDate', sort: 'asc' }],
@@ -237,7 +237,6 @@ const CreateEditCase = memo(function CreateEditCase({
237
237
  alternateFieldLabelPrefix='Parcel ID'
238
238
  toggleStates={isParcelIdMode}
239
239
  setToggleStates={setIsParcelIdMode}
240
- modalMode={modalMode}
241
240
  />
242
241
  </Box>
243
242
  </Stack>
@@ -1,11 +1,10 @@
1
1
  import Stack from '@mui/material/Stack';
2
- import { useRef, useState, useCallback, useEffect } from 'react';
2
+ import React, { useRef, useState, useCallback, useEffect } from 'react';
3
3
  import TrashIcon from '@mui/icons-material/Delete';
4
4
  import AddIcon from '@mui/icons-material/Add';
5
5
  import TextField from '@mui/material/TextField';
6
6
  import Fab from '@mui/material/Fab';
7
7
  import Typography from '@mui/material/Typography';
8
- import { ModalMode } from '@/types';
9
8
  import ToggleableTextField from './ToggleableTextField';
10
9
  import Box from '@mui/material/Box';
11
10
 
@@ -14,7 +13,6 @@ export default function TextFieldList({
14
13
  setTextFields,
15
14
  label,
16
15
  fieldLabelPrefix,
17
- modalMode,
18
16
  toggleStates,
19
17
  setToggleStates,
20
18
  alternateFieldLabelPrefix,
@@ -23,7 +21,6 @@ export default function TextFieldList({
23
21
  setTextFields: (fields: string[], toggles?: boolean[]) => void;
24
22
  label: string;
25
23
  fieldLabelPrefix: string;
26
- modalMode: ModalMode;
27
24
  toggleStates?: boolean[];
28
25
  setToggleStates?: (toggles: boolean[]) => void;
29
26
  alternateFieldLabelPrefix?: string;
@@ -102,6 +99,29 @@ export default function TextFieldList({
102
99
  [setToggleStates, toggleStates],
103
100
  );
104
101
 
102
+ const handlePaste = useCallback(
103
+ (index: number, e: React.ClipboardEvent<HTMLDivElement>) => {
104
+ const lines = e.clipboardData.getData('text').split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
105
+ if (lines.length <= 1) return;
106
+ e.preventDefault();
107
+
108
+ const newFields = [...localFields.slice(0, index), ...lines, ...localFields.slice(index + 1)];
109
+ const newToggles = toggleStates
110
+ ? [...toggleStates.slice(0, index), ...lines.map(() => false), ...toggleStates.slice(index + 1)]
111
+ : undefined;
112
+
113
+ setLocalFields(newFields);
114
+ setNumFields(newFields.length);
115
+ setTextFields(newFields, newToggles ?? []);
116
+ if (setToggleStates && newToggles) setToggleStates(newToggles);
117
+
118
+ setTimeout(() => {
119
+ if (boxRef.current) boxRef.current.scrollTop = boxRef.current.scrollHeight;
120
+ }, 0);
121
+ },
122
+ [localFields, toggleStates, setTextFields, setToggleStates],
123
+ );
124
+
105
125
  return (
106
126
  <Stack spacing={2}>
107
127
  <Stack direction='row' spacing={2} alignItems='center'>
@@ -109,7 +129,7 @@ export default function TextFieldList({
109
129
  color='primary'
110
130
  size='small'
111
131
  onClick={addField}
112
- disabled={numFields > textFields.length || modalMode === ModalMode.EDIT}
132
+ disabled={numFields > Math.max(textFields.length, 1)}
113
133
  style={{
114
134
  boxShadow: '1px 1px 3px rgba(0,0,0,0.2)',
115
135
  minWidth: 40,
@@ -145,7 +165,7 @@ export default function TextFieldList({
145
165
  <Fab
146
166
  color='error'
147
167
  size='small'
148
- disabled={modalMode === ModalMode.EDIT || numFields <= 1}
168
+ disabled={numFields <= 1}
149
169
  onClick={() => removeField(index)}
150
170
  style={{
151
171
  minWidth: 40,
@@ -161,6 +181,7 @@ export default function TextFieldList({
161
181
  value={localFields[index] || ''}
162
182
  onChange={(value) => updateField(index, value)}
163
183
  onBlur={syncToParent}
184
+ onPaste={(e) => handlePaste(index, e)}
164
185
  isToggled={isToggled || false}
165
186
  onToggleChange={(checked) => handleToggleChange(index, checked)}
166
187
  fieldLabelPrefix={fieldLabelPrefix}
@@ -173,6 +194,7 @@ export default function TextFieldList({
173
194
  value={localFields[index] || ''}
174
195
  onChange={(e) => updateField(index, e.target.value)}
175
196
  onBlur={syncToParent}
197
+ onPaste={(e) => handlePaste(index, e)}
176
198
  fullWidth
177
199
  />
178
200
  )}
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  import TextField from '@mui/material/TextField';
2
3
  import Box from '@mui/material/Box';
3
4
  import Typography from '@mui/material/Typography';
@@ -7,6 +8,7 @@ export default function ToggleableTextField({
7
8
  value,
8
9
  onChange,
9
10
  onBlur,
11
+ onPaste,
10
12
  isToggled,
11
13
  onToggleChange,
12
14
  fieldLabelPrefix,
@@ -16,6 +18,7 @@ export default function ToggleableTextField({
16
18
  value: string;
17
19
  onChange: (value: string) => void;
18
20
  onBlur?: () => void;
21
+ onPaste?: React.ClipboardEventHandler<HTMLDivElement>;
19
22
  isToggled: boolean;
20
23
  onToggleChange: (checked: boolean) => void;
21
24
  fieldLabelPrefix: string;
@@ -70,6 +73,7 @@ export default function ToggleableTextField({
70
73
  value={value}
71
74
  onChange={(e) => onChange(e.target.value)}
72
75
  onBlur={onBlur}
76
+ onPaste={onPaste}
73
77
  fullWidth
74
78
  slotProps={{
75
79
  inputLabel: {
@@ -3,6 +3,7 @@ import type { Calendar } from '@fullcalendar/core/index.js';
3
3
  import NavigateNext from '@mui/icons-material/NavigateNext';
4
4
  import NavigateBefore from '@mui/icons-material/NavigateBefore';
5
5
  import Add from '@mui/icons-material/Add';
6
+ import Print from '@mui/icons-material/Print';
6
7
  import Box from '@mui/material/Box';
7
8
  import Button from '@mui/material/Button';
8
9
  import Typography from '@mui/material/Typography';
@@ -22,6 +23,7 @@ export default function Toolbar({
22
23
  filterCtx,
23
24
  setFilterCtx,
24
25
  handleCreateClick,
26
+ onPrint,
25
27
  currentView,
26
28
  setCurrentView,
27
29
  currentDate,
@@ -34,6 +36,7 @@ export default function Toolbar({
34
36
  filterCtx: CalendarFilterCtx;
35
37
  setFilterCtx: (ctx: CalendarFilterCtx) => void;
36
38
  handleCreateClick: () => void;
39
+ onPrint: () => void;
37
40
  currentView: string;
38
41
  setCurrentView: (view: string) => void;
39
42
  currentDate: Date;
@@ -165,6 +168,9 @@ export default function Toolbar({
165
168
  <Button variant='outlined' size='medium' onClick={goToToday}>
166
169
  Today
167
170
  </Button>
171
+ <Button size='medium' onClick={onPrint}>
172
+ <Print fontSize='medium' />
173
+ </Button>
168
174
  <Button size='medium' onClick={handlePrev}>
169
175
  <NavigateBefore fontSize='medium' />
170
176
  </Button>
@@ -61,7 +61,7 @@ export async function* fetchAllCasesPaginated(apiKey: string, pageSize = 20) {
61
61
  export function isCaseSettled(c: Case, isVillage: boolean): boolean {
62
62
  if (!isVillage) {
63
63
  // town
64
- if (c.SCARDeterminationAction === null) return false;
64
+ if (!c.SCARDeterminationAction) return false;
65
65
  if (
66
66
  c.SCARDeterminationAction.toLowerCase().includes('s') ||
67
67
  c.SCARDeterminationAction.toLowerCase().includes('w') ||
@@ -70,7 +70,7 @@ export function isCaseSettled(c: Case, isVillage: boolean): boolean {
70
70
  return true;
71
71
  } else {
72
72
  // village
73
- if (c.VillageSCARDeterminationAction === null) return false;
73
+ if (!c.VillageSCARDeterminationAction) return false;
74
74
  if (
75
75
  c.VillageSCARDeterminationAction.toLowerCase().includes('s') ||
76
76
  c.VillageSCARDeterminationAction.toLowerCase().includes('w') ||