diff --git a/app/eslint.config.js b/app/eslint.config.js index ef614d2..c2a386b 100644 --- a/app/eslint.config.js +++ b/app/eslint.config.js @@ -18,5 +18,8 @@ export default defineConfig([ languageOptions: { globals: globals.browser, }, + rules: { + 'react-hooks/set-state-in-effect': 'off', + }, }, ]) diff --git a/app/src/components/ComplaintCard.tsx b/app/src/components/ComplaintCard.tsx index a423cb8..b93bfe0 100644 --- a/app/src/components/ComplaintCard.tsx +++ b/app/src/components/ComplaintCard.tsx @@ -2,7 +2,7 @@ import { useNavigate } from 'react-router-dom'; import { Zap, Hammer, Calendar, MapPin, BedDouble, MessageSquare, ChevronRight, } from 'lucide-react'; -import { POST_PLACES } from '../constants/models'; + // ── Types ───────────────────────────────────────────────────────────────────── @@ -74,7 +74,6 @@ function statusStyle(s: string): StatusStyle { return STATUS_CONFIG[norm] ?? { ...FALLBACK, label: s.replace(/_/g, ' ') }; } -const STAGES = ['XEN', 'AE', 'JE']; function formatDate(iso: string) { return new Date(iso).toLocaleDateString('en-IN', { day: '2-digit', month: 'short', year: 'numeric' }); diff --git a/app/src/pages/Landing.tsx b/app/src/pages/Landing.tsx index dbc2518..99b6d3c 100644 --- a/app/src/pages/Landing.tsx +++ b/app/src/pages/Landing.tsx @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from 'react'; -import { ArrowRight, Search } from 'lucide-react'; +import { ArrowRight } from 'lucide-react'; import { Link, useNavigate } from 'react-router-dom'; import { MainLayout } from '../components/layout/MainLayout'; @@ -15,7 +15,6 @@ export function Landing() { const [profile, setProfile] = useState(null); const [isAuth, setIsAuth] = useState(null); const [showLoginMenu, setShowLoginMenu] = useState(false); - const [trackId, setTrackId] = useState(''); const menuRef = useRef(null); const navigate = useNavigate(); @@ -89,28 +88,6 @@ export function Landing() { - - - - - - {/* Track Bar */} -
-
- Quick Track -
- setTrackId(e.target.value)} - placeholder="Enter Complaint ID — e.g. CMS-1042" - className="flex-1 px-4 py-2.5 text-sm text-[#111111] placeholder-[#999999] outline-none bg-transparent" - /> -
@@ -158,8 +135,7 @@ export function Landing() { { step: '01', text: 'Select the correct category — Civil or Electrical — to ensure proper routing.' }, { step: '02', text: 'Provide the exact building and room number in the complaint description.' }, { step: '03', text: 'Only Wardens may file complaints for hostel common areas.' }, - { step: '04', text: 'Attach relevant photos to help the team assess the issue faster.' }, - { step: '05', text: 'Do not file duplicate complaints for the same issue.' }, + { step: '04', text: 'Do not file duplicate complaints for the same issue.' }, ].map(({ step, text }) => (
{step} diff --git a/app/src/pages/admin/AdminPostView.tsx b/app/src/pages/admin/AdminPostView.tsx index 2a8dc79..6f1fb8b 100644 --- a/app/src/pages/admin/AdminPostView.tsx +++ b/app/src/pages/admin/AdminPostView.tsx @@ -17,6 +17,8 @@ import { Pencil, Trash2, Info, + Check, + Clock, } from 'lucide-react'; import { MainLayout } from '../../components/layout/MainLayout'; @@ -294,7 +296,7 @@ export function AdminPostView() { .then(async (res) => { if (!res.ok) { let msg = `Server error (${res.status})`; - try { const b = await res.json(); if (b?.error) msg = b.error; } catch {} + try { const b = await res.json(); if (b?.error) msg = b.error; } catch { /* ignore */ } const err: Error & { status?: number } = new Error(msg); err.status = res.status; throw err; @@ -350,7 +352,7 @@ export function AdminPostView() { if (!res.ok) { let msg = `Failed to edit comment (${res.status})`; - try { const b = await res.json(); if (b?.error) msg = b.error; } catch {} + try { const b = await res.json(); if (b?.error) msg = b.error; } catch { /* ignore */ } throw new Error(msg); } @@ -384,7 +386,7 @@ export function AdminPostView() { if (!res.ok) { let msg = `Failed to delete comment (${res.status})`; - try { const b = await res.json(); if (b?.error) msg = b.error; } catch {} + try { const b = await res.json(); if (b?.error) msg = b.error; } catch { /* ignore */ } throw new Error(msg); } @@ -423,7 +425,7 @@ export function AdminPostView() { }); if (!commentRes.ok) { let msg = `Failed to post comment (${commentRes.status})`; - try { const b = await commentRes.json(); if (b?.error) msg = b.error; } catch {} + try { const b = await commentRes.json(); if (b?.error) msg = b.error; } catch { /* ignore */ } throw new Error(msg); } @@ -436,7 +438,7 @@ export function AdminPostView() { }); if (!statusRes.ok) { let msg = `Comment posted but status update failed (${statusRes.status})`; - try { const b = await statusRes.json(); if (b?.error) msg = b.error; } catch {} + try { const b = await statusRes.json(); if (b?.error) msg = b.error; } catch { /* ignore */ } throw new Error(msg); } @@ -480,7 +482,7 @@ export function AdminPostView() { }); if (!commentRes.ok) { let msg = `Failed to post comment (${commentRes.status})`; - try { const b = await commentRes.json(); if (b?.error) msg = b.error; } catch {} + try { const b = await commentRes.json(); if (b?.error) msg = b.error; } catch { /* ignore */ } throw new Error(msg); } @@ -493,7 +495,7 @@ export function AdminPostView() { }); if (!statusRes.ok) { let msg = `Comment posted but status update failed (${statusRes.status})`; - try { const b = await statusRes.json(); if (b?.error) msg = b.error; } catch {} + try { const b = await statusRes.json(); if (b?.error) msg = b.error; } catch { /* ignore */ } throw new Error(msg); } @@ -534,7 +536,7 @@ export function AdminPostView() { }); if (!commentRes.ok) { let msg = `Failed to post comment (${commentRes.status})`; - try { const b = await commentRes.json(); if (b?.error) msg = b.error; } catch {} + try { const b = await commentRes.json(); if (b?.error) msg = b.error; } catch { /* ignore */ } throw new Error(msg); } @@ -755,15 +757,56 @@ export function AdminPostView() { {/* Combined Timeline */} - {timelineItems.length === 0 ? ( -

No history or comments yet.

- ) : ( -
- {timelineItems.map((item, idx) => { +
+ {/* Connecting pipeline/line */} +
+ + {timelineItems.length === 0 ? ( +
+

No activity or responses yet.

+
+ ) : ( + timelineItems.map((item, idx) => { if (item.type === 'audit') { const audit = item.data; const normEvent = audit.event.toLowerCase(); - const dotColor = STATUS_DOT[normEvent] ?? 'bg-gray-400'; + + const auditStyle = (() => { + if (normEvent.includes('resolved')) { + return { + bg: 'bg-emerald-50 border-emerald-300 text-emerald-600', + icon: , + textCls: 'text-emerald-700 font-bold', + }; + } + if (normEvent === 'pending_xen') { + return { + bg: 'bg-amber-50 border-amber-300 text-amber-600', + icon: , + textCls: 'text-amber-700 font-bold', + }; + } + if (normEvent === 'pending_ae') { + return { + bg: 'bg-sky-50 border-sky-300 text-sky-600', + icon: , + textCls: 'text-sky-700 font-bold', + }; + } + if (normEvent === 'pending_je') { + return { + bg: 'bg-violet-50 border-violet-300 text-violet-600', + icon: , + textCls: 'text-violet-700 font-bold', + }; + } + return { + bg: 'bg-gray-50 border-gray-300 text-gray-500', + icon: , + textCls: 'text-gray-700 font-bold', + }; + })(); + const eventText = (() => { if (normEvent === 'pending_xen') return 'Sent to XEN for review.'; if (normEvent === 'pending_ae') return 'Sent to AE for review.'; @@ -775,17 +818,15 @@ export function AdminPostView() { })(); return ( -
- - - -
- +
+
+ {auditStyle.icon} +
+
+ {eventText} + {formatDateTime(audit.timestamp)} - - {eventText} -
); @@ -798,15 +839,15 @@ export function AdminPostView() { const editExpired = isEditWindowExpired(c.created_at); return ( -
- - - -
-
- {who} - {c.email && {c.email}} - +
+ {/* Comment Bubble in GitHub Style */} +
+
+ + {who} + {c.email && {c.email}} + + {formatDateTime(c.created_at)} {isMyComment && !isEditing && !editExpired && ( @@ -820,7 +861,7 @@ export function AdminPostView() { className="p-0.5 rounded text-gray-400 hover:text-gray-700 hover:bg-gray-200/50 transition cursor-pointer" title="Edit comment" > - + )}
- {isEditing ? ( -
-