Add 4 files, update 15 files

This commit is contained in:
[dyad] committed 2026-08-02 15:17:18 +02:00
1 parent a97794acf8
commit 2db87a1227
19 files changed
+414 -33

No files matched your search

+34 -4
View File
@@ -54,6 +54,10 @@ interface CommunicationRoom {
studentId: string;
teacherId: string;
roomType: string;
audienceType: string;
escalationEligibleAt: string | null;
escalatedAt: string | null;
resolutionStatus: string;
status: string;
requestedBy: string;
acceptedAt: string | null;
@@ -532,6 +536,17 @@ function ChatArea({
}
};
const handleEscalate = async () => {
try {
await apiPut(`/api/communication-rooms/${roomId}`, { action: 'escalate' });
toast.success('Conversation escalated to administration');
fetchRoom();
onRefresh();
} catch {
toast.error('This conversation cannot be escalated yet');
}
};
// Voice recording with Web Audio API
const startRecording = async () => {
try {
@@ -704,6 +719,11 @@ function ChatArea({
const isStudent = room?.studentId === currentUserId;
const otherPerson = isStudent ? room?.teacher : room?.student;
const isAdmin = role === 'SCHOOL_ADMIN' || role === 'VICE_PRINCIPAL' || role === 'SUPER_ADMIN';
const canEscalate = (role === 'STUDENT' || role === 'PARENT')
&& room?.audienceType === 'direct'
&& room?.resolutionStatus !== 'resolved'
&& !room?.escalatedAt
&& Boolean(room?.escalationEligibleAt && new Date(room.escalationEligibleAt) <= new Date());
const formatTime = (dateStr: string) => {
const date = new Date(dateStr);
@@ -766,12 +786,17 @@ function ChatArea({
room?.status === 'active' ? t('communication.active') :
t('communication.closed')}
</Badge>
{isAdmin && (
{isAdmin && (
<Badge variant="outline" className="text-xs">
<Eye className="h-3 w-3 mr-1" />
{t('communication.read_only')}
</Badge>
)}
)}
{room?.escalatedAt && (
<Badge variant="outline" className="text-xs border-blue-300 text-blue-700">
<Shield className="h-3 w-3 mr-1" />Escalated
</Badge>
)}
</div>
</div>
</div>
@@ -788,7 +813,7 @@ function ChatArea({
</Button>
</>
)}
{role === 'TEACHER' && room?.status === 'active' && (
{role === 'TEACHER' && room?.status === 'active' && (
<>
<Button variant="outline" size="sm" className="h-8" onClick={() => setShowTeacherNotes(!showTeacherNotes)}>
<StickyNote className="h-3.5 w-3.5 mr-1" />
@@ -799,7 +824,12 @@ function ChatArea({
{t('communication.close')}
</Button>
</>
)}
)}
{canEscalate && (
<Button variant="outline" size="sm" className="h-8 border-amber-300 text-amber-700" onClick={handleEscalate}>
<AlertTriangle className="h-3.5 w-3.5 mr-1" />Escalate
</Button>
)}
</div>
</div>
+42 -1
View File
@@ -55,6 +55,11 @@ interface IllnessReport {
parentApprovalStatus: string;
parentApprovedBy: string | null;
parentApprovedAt: string | null;
leaveType: string;
teacherNotifiedAt: string | null;
adminApprovalStatus: string;
adminApprovedAt: string | null;
calendarEventId: string | null;
isVisibleToTeacher: boolean;
isVisibleToAdmin: boolean;
status: string;
@@ -334,6 +339,15 @@ function ReportCard({
const [isApproving, setIsApproving] = useState(false);
const getStatusBadge = () => {
if (report.adminApprovalStatus === 'approved') {
return <Badge variant="outline" className="bg-emerald-50 text-emerald-700 border-emerald-300 dark:bg-emerald-950/30 dark:text-emerald-300 dark:border-emerald-800"><CheckCircle2 className="h-3 w-3 mr-1" />Approved and on calendar</Badge>;
}
if (report.adminApprovalStatus === 'rejected') {
return <Badge variant="outline" className="bg-rose-50 text-rose-700 border-rose-300 dark:bg-rose-950/30 dark:text-rose-300 dark:border-rose-800"><XCircle className="h-3 w-3 mr-1" />Rejected by administration</Badge>;
}
if (report.parentApprovalStatus === 'approved') {
return <Badge variant="outline" className="bg-blue-50 text-blue-700 border-blue-300 dark:bg-blue-950/30 dark:text-blue-300 dark:border-blue-800"><Clock className="h-3 w-3 mr-1" />Awaiting administration</Badge>;
}
switch (report.parentApprovalStatus) {
case 'pending':
return <Badge variant="outline" className="bg-amber-50 text-amber-700 border-amber-300 dark:bg-amber-950/30 dark:text-amber-300 dark:border-amber-800"><Clock className="h-3 w-3 mr-1" />{t('illness.pending_approval')}</Badge>;
@@ -381,6 +395,10 @@ function ReportCard({
}
};
const canAdminApprove = (role === 'SCHOOL_ADMIN' || role === 'SUPER_ADMIN')
&& report.parentApprovalStatus === 'approved'
&& report.adminApprovalStatus === 'pending';
const handleDelete = async () => {
try {
await apiDelete(`/api/illness-reports/${report.id}`);
@@ -440,7 +458,8 @@ function ReportCard({
<motion.div
key={i}
className={`h-1.5 flex-1 rounded-full ${
report.parentApprovalStatus === 'approved' ? 'bg-emerald-400' :
report.adminApprovalStatus === 'approved' ? 'bg-emerald-400' :
report.parentApprovalStatus === 'approved' ? 'bg-blue-400' :
report.parentApprovalStatus === 'pending' ? 'bg-amber-400' : 'bg-rose-400'
}`}
initial={{ scaleX: 0 }}
@@ -481,6 +500,28 @@ function ReportCard({
</Button>
</>
)}
{canAdminApprove && (
<>
<Button
size="sm"
className="bg-emerald-500 hover:bg-emerald-600 text-white h-8"
onClick={handleApprove}
disabled={isApproving}
>
<CheckCircle2 className="h-3.5 w-3.5 mr-1" />
Final approve
</Button>
<Button
size="sm"
variant="outline"
className="h-8 text-rose-600 border-rose-300 hover:bg-rose-50"
onClick={handleReject}
disabled={isApproving}
>
Reject
</Button>
</>
)}
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={onToggle}>
{expanded ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</Button>