Skip to content

Commit 89fc639

Browse files
Merge pull request #109 from uwblueprint/kenzy/edit-notes
Edit Notes in AbsenceDetails
2 parents 27f3231 + 05947e1 commit 89fc639

File tree

2 files changed

+178
-31
lines changed

2 files changed

+178
-31
lines changed

src/components/AbsenceDetails.tsx

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ import { Buildings, Calendar } from 'iconsax-react';
2121
import { useState } from 'react';
2222
import { FiEdit2, FiMapPin, FiTrash2, FiUser } from 'react-icons/fi';
2323
import { IoEyeOutline } from 'react-icons/io5';
24+
import AbsenceClaimThanks from './AbsenceClaimThanks';
2425
import AbsenceStatusTag from './AbsenceStatusTag';
26+
import EditableNotes from './EditableNotes';
2527
import EditAbsenceForm from './EditAbsenceForm';
2628
import LessonPlanView from './LessonPlanView';
27-
import AbsenceClaimThanks from './AbsenceClaimThanks';
2829

2930
interface AbsenceDetailsProps {
3031
isOpen: boolean;
@@ -322,36 +323,11 @@ const AbsenceDetails: React.FC<AbsenceDetailsProps> = ({
322323
</Box>
323324
)}
324325
{isUserAbsentTeacher && !isAdminMode && (
325-
<Box position="relative">
326-
<Text textStyle="h4" mb="9px">
327-
Notes
328-
</Text>
329-
<Box
330-
fontSize="12px"
331-
sx={{
332-
padding: '15px 15px 33px 15px',
333-
borderRadius: '10px',
334-
}}
335-
background={theme.colors.neutralGray[50]}
336-
>
337-
{event.notes}
338-
</Box>
339-
340-
<IconButton
341-
aria-label="Edit Notes"
342-
icon={
343-
<FiEdit2
344-
size="15px"
345-
color={theme.colors.neutralGray[600]}
346-
/>
347-
}
348-
size="sm"
349-
variant="ghost"
350-
position="absolute"
351-
bottom="8px"
352-
right="16px"
353-
/>
354-
</Box>
326+
<EditableNotes
327+
notes={event.notes}
328+
absenceId={event.absenceId}
329+
fetchAbsences={fetchAbsences}
330+
/>
355331
)}
356332
{event.notes && (!isUserAbsentTeacher || isAdminMode) && (
357333
<Box position="relative">

src/components/EditableNotes.tsx

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import { useState, useRef, useEffect } from 'react';
2+
import {
3+
Box,
4+
Text,
5+
Textarea,
6+
IconButton,
7+
Button,
8+
HStack,
9+
useTheme,
10+
Spacer,
11+
useToast,
12+
} from '@chakra-ui/react';
13+
import { FiEdit2 } from 'react-icons/fi';
14+
15+
interface EditableNotesProps {
16+
notes: string;
17+
absenceId: number;
18+
fetchAbsences: () => Promise<void>;
19+
}
20+
21+
function EditableNotes({
22+
notes,
23+
absenceId,
24+
fetchAbsences,
25+
}: EditableNotesProps) {
26+
const theme = useTheme();
27+
const toast = useToast();
28+
const [isEditing, setIsEditing] = useState(false);
29+
const [savedNotes, setSavedNotes] = useState(notes);
30+
const [tempNotes, setTempNotes] = useState(notes);
31+
const [isSaving, setIsSaving] = useState(false);
32+
const noteBoxRef = useRef<HTMLDivElement | null>(null);
33+
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
34+
const [initialHeight, setInitialHeight] = useState<number | undefined>(
35+
undefined
36+
);
37+
38+
const emptyNotesSpace = 41;
39+
const spaceAfterNotes = 33;
40+
const minHeight = emptyNotesSpace + spaceAfterNotes;
41+
42+
const handleEditClick = () => {
43+
const height = noteBoxRef.current?.offsetHeight ?? minHeight;
44+
setInitialHeight(Math.max(height, minHeight));
45+
setTempNotes(savedNotes);
46+
setIsEditing(true);
47+
};
48+
49+
const handleCancel = () => {
50+
setIsEditing(false);
51+
setTempNotes(savedNotes);
52+
};
53+
54+
const handleSave = async () => {
55+
setIsSaving(true);
56+
57+
try {
58+
const response = await fetch('/api/editAbsence', {
59+
method: 'PUT',
60+
headers: { 'Content-Type': 'application/json' },
61+
body: JSON.stringify({
62+
id: absenceId,
63+
notes: tempNotes || null,
64+
}),
65+
});
66+
67+
if (!response.ok) {
68+
throw new Error('Failed to save notes');
69+
}
70+
71+
setSavedNotes(tempNotes);
72+
toast({
73+
title: 'Notes saved',
74+
description: 'Your notes were successfully updated.',
75+
status: 'success',
76+
duration: 4000,
77+
isClosable: true,
78+
});
79+
80+
setIsEditing(false);
81+
} catch (err) {
82+
toast({
83+
title: 'Error',
84+
description:
85+
err instanceof Error ? err.message : 'Failed to save notes',
86+
status: 'error',
87+
duration: 5000,
88+
isClosable: true,
89+
});
90+
} finally {
91+
setIsSaving(false);
92+
fetchAbsences();
93+
}
94+
};
95+
96+
useEffect(() => {
97+
if (isEditing && textAreaRef.current) {
98+
const textLength = textAreaRef.current.value.length;
99+
textAreaRef.current.setSelectionRange(textLength, textLength);
100+
}
101+
}, [isEditing]);
102+
103+
return (
104+
<Box position="relative">
105+
<Text textStyle="h4" mb="9px">
106+
Notes
107+
</Text>
108+
109+
{isEditing ? (
110+
<>
111+
<Textarea
112+
ref={textAreaRef}
113+
value={tempNotes}
114+
onChange={(e) => setTempNotes(e.target.value)}
115+
fontSize="12px"
116+
borderColor={theme.colors.primaryBlue[300]}
117+
borderRadius="10px"
118+
padding="15px"
119+
minH={`${initialHeight}px`}
120+
/>
121+
<HStack mt="20px" mb="9px">
122+
<Button
123+
width="143px"
124+
height="35.174px"
125+
fontSize="16px"
126+
fontWeight="500"
127+
variant="outline"
128+
onClick={handleCancel}
129+
>
130+
Cancel
131+
</Button>
132+
<Spacer />
133+
<Button
134+
width="143px"
135+
height="35.174px"
136+
fontSize="16px"
137+
fontWeight="500"
138+
onClick={handleSave}
139+
isLoading={isSaving}
140+
loadingText="Saving"
141+
>
142+
Save Changes
143+
</Button>
144+
</HStack>
145+
</>
146+
) : (
147+
<Box
148+
ref={noteBoxRef}
149+
fontSize="12px"
150+
padding="15px 15px 33px 15px"
151+
borderRadius="10px"
152+
background={theme.colors.neutralGray[50]}
153+
>
154+
{savedNotes || ''}
155+
<IconButton
156+
aria-label="Edit Notes"
157+
icon={<FiEdit2 size="15px" color={theme.colors.neutralGray[600]} />}
158+
size="sm"
159+
variant="ghost"
160+
position="absolute"
161+
bottom="8px"
162+
right="16px"
163+
onClick={handleEditClick}
164+
/>
165+
</Box>
166+
)}
167+
</Box>
168+
);
169+
}
170+
171+
export default EditableNotes;

0 commit comments

Comments
 (0)