diff --git a/app/src/components/ComplaintCard.tsx b/app/src/components/ComplaintCard.tsx index e308065..f1110a4 100644 --- a/app/src/components/ComplaintCard.tsx +++ b/app/src/components/ComplaintCard.tsx @@ -1,11 +1,12 @@ import { useState } from 'react'; +import { createPortal } from 'react-dom'; import { Zap, Hammer, Trash2, Pencil, X, Check, Calendar, MapPin, BedDouble, - MessageSquare, ChevronDown, Wrench, GitBranch, + MessageSquare, Wrench, GitBranch, ChevronRight, UserCircle, Clock, } from 'lucide-react'; import { POST_PLACES } from '../constants/models'; -// ── Types ──────────────────────────────────────────────────────────────────────── +// ── Types ───────────────────────────────────────────────────────────────────── export type Role = 'faculty' | 'warden' | 'centrehead'; @@ -58,29 +59,23 @@ interface ComplaintCardProps { onDelete: (postId: number) => void; } -// ── Status / stage styling ───────────────────────────────────────────────────────── +// ── Status config ────────────────────────────────────────────────────────────── -interface StatusStyle { - label: string; - badge: string; // pill background/text/border - dot: string; // status dot -} +interface StatusStyle { label: string; pill: string; dot: string } const STATUS_CONFIG: Record = { - Pending_XEN: { label: 'Pending · XEN', badge: 'bg-amber-50 text-amber-700 border-amber-200', dot: 'bg-amber-400' }, - Pending_AE: { label: 'Pending · AE', badge: 'bg-blue-50 text-blue-700 border-blue-200', dot: 'bg-blue-400' }, - Pending_JE: { label: 'Pending · JE', badge: 'bg-indigo-50 text-indigo-700 border-indigo-200', dot: 'bg-indigo-400' }, - Resolved_JE: { label: 'Resolved by JE', badge: 'bg-teal-50 text-teal-700 border-teal-200', dot: 'bg-teal-400' }, - Resolved: { label: 'Resolved', badge: 'bg-emerald-50 text-emerald-700 border-emerald-200', dot: 'bg-emerald-400' }, - Closed: { label: 'Closed', badge: 'bg-red-50 text-red-600 border-red-200', dot: 'bg-red-400' }, + Pending_XEN: { label: 'Pending · XEN', pill: 'bg-amber-100 text-amber-800 border-amber-300', dot: 'bg-amber-400' }, + Pending_AE: { label: 'Pending · AE', pill: 'bg-sky-100 text-sky-800 border-sky-300', dot: 'bg-sky-400' }, + Pending_JE: { label: 'Pending · JE', pill: 'bg-violet-100 text-violet-800 border-violet-300', dot: 'bg-violet-400' }, + Resolved_JE: { label: 'Resolved by JE', pill: 'bg-teal-100 text-teal-800 border-teal-300', dot: 'bg-teal-400' }, + Resolved: { label: 'Resolved', pill: 'bg-emerald-100 text-emerald-800 border-emerald-300', dot: 'bg-emerald-500' }, + Closed: { label: 'Closed', pill: 'bg-rose-100 text-rose-800 border-rose-300', dot: 'bg-rose-400' }, }; -const FALLBACK_STATUS: StatusStyle = { - label: 'Unknown', badge: 'bg-gray-100 text-gray-600 border-gray-200', dot: 'bg-gray-400', -}; +const FALLBACK: StatusStyle = { label: 'Unknown', pill: 'bg-gray-100 text-gray-600 border-gray-300', dot: 'bg-gray-400' }; -function statusStyle(status: string): StatusStyle { - return STATUS_CONFIG[status] ?? { ...FALLBACK_STATUS, label: status.replace(/_/g, ' ') }; +function statusStyle(s: string): StatusStyle { + return STATUS_CONFIG[s] ?? { ...FALLBACK, label: s.replace(/_/g, ' ') }; } const STAGES = ['XEN', 'AE', 'JE']; @@ -91,226 +86,399 @@ function formatDate(iso: string) { function formatDateTime(iso: string) { return new Date(iso).toLocaleString('en-IN', { - day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit', + day: '2-digit', month: 'short', year: 'numeric', hour: '2-digit', minute: '2-digit', }); } -// ── Component ────────────────────────────────────────────────────────────────────── +function roleLabel(position: string) { + return position.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); +} -export function ComplaintCard({ - post, role, isEditing, isBusy, editForm, - onEditFormChange, onStartEdit, onCancelEdit, onSaveEdit, onDelete, -}: ComplaintCardProps) { - const [commentsOpen, setCommentsOpen] = useState(false); +// ── Type theme ───────────────────────────────────────────────────────────────── - const isFaculty = role === 'faculty'; - const isWarden = role === 'warden'; - const status = statusStyle(post.status); +function typeTheme(isElectrical: boolean) { + return isElectrical + ? { cardBg: 'bg-amber-50', accentBar: 'bg-amber-400', headerBg: 'bg-amber-100/70', iconColor: 'text-amber-600', badge: 'bg-amber-200 text-amber-900 border-amber-400', stageDone: 'bg-amber-500 border-amber-500' } + : { cardBg: 'bg-sky-50', accentBar: 'bg-sky-500', headerBg: 'bg-sky-100/70', iconColor: 'text-sky-600', badge: 'bg-sky-200 text-sky-900 border-sky-400', stageDone: 'bg-sky-600 border-sky-600' }; +} + +// ── Stage tracker ────────────────────────────────────────────────────────────── + +function StageTracker({ stage, theme }: { stage: string; theme: ReturnType }) { + const current = STAGES.indexOf(stage); + return ( +
+ +
+ {STAGES.map((s, idx) => { + const done = current >= 0 && idx <= current; + const isLast = idx === STAGES.length - 1; + return ( +
+ + {s} + + {!isLast && ( +
idx ? theme.stageDone.replace('bg-', 'bg-') : 'bg-gray-300'}`} /> + )} +
+ ); + })} +
+
+ ); +} + +// ── Edit form ────────────────────────────────────────────────────────────────── + +function EditFormFields({ + editForm, isFaculty, isWarden, onEditFormChange, +}: { + editForm: EditForm; + isFaculty: boolean; + isWarden: boolean; + onEditFormChange: (patch: Partial) => void; +}) { + const inputCls = 'w-full text-sm text-gray-800 bg-white border border-gray-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-gray-400/40 focus:border-gray-600'; + const labelCls = 'block text-[10px] font-bold uppercase tracking-wider text-gray-500 mb-1'; + return ( +
+
+ + onEditFormChange({ title: e.target.value })} className={inputCls} /> +
+ {isFaculty && ( +
+ + +
+ )} + {isWarden && ( +
+ + onEditFormChange({ room_number: e.target.value })} className={inputCls} /> +
+ )} +
+ +