File: /var/www/igsms.viitorcloud.co/igsmsportal/App.tsx
import React, { useState, useMemo } from 'react';
import { UserRole, User, Grievance, GrievanceStatus } from './types';
import Sidebar from './components/Sidebar';
import CitizenDashboard from './views/CitizenDashboard';
import OfficerDashboard from './views/OfficerDashboard';
import AnalyticsOverview from './views/AnalyticsOverview';
import ProfileView from './views/ProfileView';
import Navbar from './components/Navbar';
import FloatingChatbot from './components/FloatingChatbot';
const INITIAL_GRIEVANCES: Grievance[] = [
{
id: '1', ticketNumber: 'GUJ-2023-8821', title: 'Water pipe leakage in Sector 24', description: 'The main supply pipe is leaking for the last 3 days causing significant water waste.', department: 'Water Resources', category: 'Pipeline Leakage', status: GrievanceStatus.IN_PROGRESS, createdAt: '2023-11-20T10:00:00Z', updatedAt: '2023-11-21T14:30:00Z', citizenName: 'Jayesh Patel', contactNumber: '9876543210', district: 'Gandhinagar', taluka: 'Gandhinagar', slaDeadline: '2023-11-23T10:00:00Z', priority: 'MEDIUM'
},
{
id: '2', ticketNumber: 'GUJ-2023-1044', title: 'Garbage Collection Irregularity', description: 'Door to door garbage collection vehicle has not visited our society for the past 4 days.', department: 'Urban Development and Urban Housing', category: 'Sanitation', status: GrievanceStatus.PENDING, createdAt: new Date(Date.now() - 1000 * 3600 * 72).toISOString(), updatedAt: new Date(Date.now() - 1000 * 3600 * 2).toISOString(), citizenName: 'Jayesh Patel', contactNumber: '9876543210', district: 'Ahmedabad', taluka: 'Ahmedabad City', slaDeadline: new Date(Date.now() - 1000 * 3600 * 4).toISOString(), priority: 'URGENT'
},
{
id: '3', ticketNumber: 'GUJ-2023-4412', title: 'Potholes on Highway 41', description: 'Large potholes formed after recent rains near the bypass.', department: 'Roads and Buildings', category: 'Road Maintenance', status: GrievanceStatus.ASSIGNED, createdAt: '2023-11-18T09:00:00Z', updatedAt: '2023-11-19T11:00:00Z', citizenName: 'Jayesh Patel', contactNumber: '9876543210', district: 'Rajkot', taluka: 'Rajkot', slaDeadline: '2023-11-25T09:00:00Z', priority: 'HIGH'
},
{
id: '4', ticketNumber: 'GUJ-2023-5591', title: 'Land Encroachment on Public Path', description: 'Illegal construction starting on the common utility path in my village area.', department: 'Revenue', category: 'Encroachment', status: GrievanceStatus.ESCALATED, createdAt: '2023-11-15T14:20:00Z', updatedAt: '2023-11-20T09:15:00Z', citizenName: 'Jayesh Patel', contactNumber: '9876543210', district: 'Bhavnagar', taluka: 'Talaja', slaDeadline: '2023-11-22T14:20:00Z', priority: 'HIGH'
},
{
id: '5', ticketNumber: 'GUJ-2023-1282', title: 'Primary Health Center Closed', description: 'The local PHC was closed during official hours when I went for my child vaccination.', department: 'Health and Family Welfare', category: 'Service Availability', status: GrievanceStatus.RESOLVED, createdAt: '2023-11-10T08:00:00Z', updatedAt: '2023-11-12T16:00:00Z', citizenName: 'Jayesh Patel', contactNumber: '9876543210', district: 'Surat', taluka: 'Choryasi', slaDeadline: '2023-11-17T08:00:00Z', priority: 'URGENT', resolutionRemarks: 'Staff has been issued a warning and regular attendance monitoring is now active.'
},
{
id: '6', ticketNumber: 'GUJ-2023-3390', title: 'Streetlights Not Working', description: 'Entire street from Crossroad to Temple has no functioning lights for a week.', department: 'Urban Development and Urban Housing', category: 'Public Lighting', status: GrievanceStatus.IN_PROGRESS, createdAt: '2023-11-22T19:00:00Z', updatedAt: '2023-11-23T10:00:00Z', citizenName: 'Jayesh Patel', contactNumber: '9876543210', district: 'Vadodara', taluka: 'Vadodara', slaDeadline: '2023-11-26T19:00:00Z', priority: 'MEDIUM'
},
{
id: '7', ticketNumber: 'GUJ-2023-7712', title: 'Delayed Seed Subsidy', description: 'Applied for groundnut seed subsidy 2 months ago, still no transfer in DBT account.', department: 'Agriculture, Farmers Welfare and Co-operation', category: 'Subsidy', status: GrievanceStatus.PENDING, createdAt: '2023-11-12T11:00:00Z', updatedAt: '2023-11-12T11:00:00Z', citizenName: 'Jayesh Patel', contactNumber: '9876543210', district: 'Jamnagar', taluka: 'Dhrol', slaDeadline: '2023-11-28T11:00:00Z', priority: 'MEDIUM'
},
{
id: '8', ticketNumber: 'GUJ-2023-6623', title: 'Noise Pollution from Factory', description: 'Nearby small scale unit is operating heavy machinery late at night.', department: 'Home', category: 'Public Nuisance', status: GrievanceStatus.ASSIGNED, createdAt: '2023-11-21T23:30:00Z', updatedAt: '2023-11-22T08:00:00Z', citizenName: 'Jayesh Patel', contactNumber: '9876543210', district: 'Junagadh', taluka: 'Junagadh City', slaDeadline: '2023-11-24T23:30:00Z', priority: 'LOW'
},
{
id: '9', ticketNumber: 'GUJ-2023-9901', title: 'Mid-day Meal Quality', description: 'The quality of food served at the Primary School in Sector 2 is substandard.', department: 'Education', category: 'Student Welfare', status: GrievanceStatus.RESOLVED, createdAt: '2023-11-05T12:00:00Z', updatedAt: '2023-11-07T14:00:00Z', citizenName: 'Jayesh Patel', contactNumber: '9876543210', district: 'Gandhinagar', taluka: 'Gandhinagar', slaDeadline: '2023-11-12T12:00:00Z', priority: 'HIGH', resolutionRemarks: 'Contractor has been replaced and a new kitchen supervisor appointed.'
},
{
id: '10', ticketNumber: 'GUJ-2023-2110', title: 'Drainage Blockage in Society', description: 'Main manhole is overflowing onto the street, causing health hazard.', department: 'Urban Development and Urban Housing', category: 'Sewage', status: GrievanceStatus.PENDING, createdAt: '2023-11-23T06:00:00Z', updatedAt: '2023-11-23T06:00:00Z', citizenName: 'Jayesh Patel', contactNumber: '9876543210', district: 'Ahmedabad', taluka: 'Sabarmati', slaDeadline: '2023-11-25T06:00:00Z', priority: 'URGENT'
}
];
const App: React.FC = () => {
const [currentUser, setCurrentUser] = useState<User>({ id: '1', name: 'Jayesh Patel', role: UserRole.CITIZEN });
const [grievances, setGrievances] = useState<Grievance[]>(INITIAL_GRIEVANCES);
const [activeTab, setActiveTab] = useState<string>('dashboard');
const [searchQuery, setSearchQuery] = useState<string>('');
const addGrievance = (newGrievance: Grievance) => setGrievances(prev => [newGrievance, ...prev]);
const updateGrievanceStatus = (id: string, status: GrievanceStatus) => setGrievances(prev => prev.map(g => g.id === id ? { ...g, status, updatedAt: new Date().toISOString() } : g));
const handleUpdateUser = (updatedFields: Partial<User>) => {
setCurrentUser(prev => ({ ...prev, ...updatedFields }));
};
const roleGrievances = useMemo(() => {
switch (currentUser.role) {
case UserRole.CITIZEN:
return grievances.filter(g => g.citizenName === currentUser.name);
case UserRole.OFFICER:
// Prototype change: Show ALL grievances to the officer so the portal isn't empty
// In production, this would be: grievances.filter(g => !currentUser.department || g.department === currentUser.department)
return grievances;
case UserRole.CMO:
return grievances;
default:
return [];
}
}, [grievances, currentUser]);
const filteredGrievances = useMemo(() => {
if (!searchQuery.trim()) return roleGrievances;
const query = searchQuery.toLowerCase().trim();
return roleGrievances.filter(g =>
g.ticketNumber.toLowerCase().includes(query) ||
g.title.toLowerCase().includes(query) ||
g.description.toLowerCase().includes(query)
);
}, [roleGrievances, searchQuery]);
const handleRoleChange = (role: UserRole) => {
const newUser: User = {
id: role === UserRole.CITIZEN ? '1' : (role === UserRole.OFFICER ? '101' : '500'),
name: role === UserRole.CITIZEN ? 'Jayesh Patel' : (role === UserRole.OFFICER ? 'Urban Officer' : 'Chief Secretary'),
role,
department: role === UserRole.OFFICER ? 'Urban Development and Urban Housing' : undefined
};
setCurrentUser(newUser);
setActiveTab('dashboard');
setSearchQuery('');
};
const renderContent = () => {
if (activeTab === 'profile') {
return <ProfileView user={currentUser} grievances={roleGrievances} onUpdateUser={handleUpdateUser} />;
}
switch (currentUser.role) {
case UserRole.CITIZEN:
return <CitizenDashboard activeTab={activeTab} grievances={filteredGrievances} onAddGrievance={addGrievance} />;
case UserRole.OFFICER:
return <OfficerDashboard activeTab={activeTab} user={currentUser} grievances={filteredGrievances} onUpdateStatus={updateGrievanceStatus} />;
case UserRole.CMO:
return <AnalyticsOverview activeTab={activeTab} grievances={filteredGrievances} />;
default:
return (
<div className="h-96 flex flex-col items-center justify-center text-center p-12 bg-white rounded-[2.5rem] border border-slate-100 shadow-sm">
<div className="w-16 h-16 bg-red-50 text-red-500 rounded-full flex items-center justify-center text-2xl mb-4">
<i className="fa-solid fa-lock"></i>
</div>
<h3 className="text-xl font-black text-slate-800">Unauthorized Access</h3>
<p className="text-slate-500 mt-2">You do not have the required permissions to view this dashboard.</p>
</div>
);
}
};
return (
<div className="flex h-screen bg-slate-50 font-sans overflow-hidden">
<Sidebar role={currentUser.role} activeTab={activeTab} onTabChange={setActiveTab} />
<div className="flex-1 flex flex-col min-w-0">
<Navbar user={currentUser} onRoleChange={handleRoleChange} onSearch={setSearchQuery} />
<main className="flex-1 overflow-y-auto px-10 pb-10">
<div className="max-w-7xl mx-auto py-6">
<div className="mb-4 flex items-center justify-between px-2">
<div className="flex items-center gap-2">
<div className={`w-2 h-2 rounded-full ${currentUser.role === UserRole.CMO ? 'bg-orange-500 animate-pulse' : 'bg-blue-500'}`}></div>
<span className="text-[10px] font-black text-slate-400 uppercase tracking-widest">
{currentUser.role === UserRole.CITIZEN ? 'Restricted: Personal Records' :
currentUser.role === UserRole.OFFICER ? 'Prototype Mode: All Departments' :
'Full Access: State Insights'}
</span>
</div>
</div>
{searchQuery && (
<div className="mb-6 flex items-center justify-between px-6 py-3 bg-blue-50/50 rounded-2xl border border-blue-100 animate-fade-in">
<div className="flex items-center gap-3 text-sm font-bold text-blue-600">
<i className="fa-solid fa-magnifying-glass"></i>
<span>Searching within <span className="text-slate-900 font-black">"{currentUser.role.toLowerCase()}"</span> workspace for <span className="text-slate-900 font-black">"{searchQuery}"</span></span>
</div>
<button onClick={() => setSearchQuery('')} className="text-[10px] font-black uppercase text-blue-400 hover:text-blue-600 tracking-widest">Reset</button>
</div>
)}
<div className="transition-all duration-500 transform">
{renderContent()}
</div>
</div>
</main>
</div>
<FloatingChatbot grievances={grievances} />
</div>
);
};
export default App;