HEX
Server: nginx/1.18.0
System: Linux vcwordpress 5.15.0-174-generic #184-Ubuntu SMP Fri Mar 13 18:41:50 UTC 2026 x86_64
User: root (0)
PHP: 7.4.33
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /var/www/igsms.viitorcloud.co/igsmsportal/views/OfficerDashboard.tsx
import React, { useState, useMemo } from 'react';
import { Grievance, GrievanceStatus, User } from '../types';
import { STATUS_COLORS, PRIORITY_COLORS } from '../constants';
import { summarizeGrievance } from '../services/geminiService';

interface OfficerDashboardProps {
  activeTab: string;
  user: User;
  grievances: Grievance[];
  onUpdateStatus: (id: string, status: GrievanceStatus) => void;
}

type SortKey = 'ticketNumber' | 'district' | 'status';
type SortDirection = 'asc' | 'desc';

const OfficerDashboard: React.FC<OfficerDashboardProps> = ({ activeTab, user, grievances, onUpdateStatus }) => {
  const [selectedTicket, setSelectedTicket] = useState<Grievance | null>(null);
  const [summary, setSummary] = useState<string>('');
  const [isSummarizing, setIsSummarizing] = useState(false);
  const [sortConfig, setSortConfig] = useState<{ key: SortKey; direction: SortDirection }>({ key: 'ticketNumber', direction: 'asc' });

  // Explicitly matching user request for functional tabs
  const filteredByTab = useMemo(() => {
    switch (activeTab) {
      case 'pending': // "Active Queue"
        return grievances.filter(g => 
          g.status === GrievanceStatus.PENDING || 
          g.status === GrievanceStatus.ASSIGNED || 
          g.status === GrievanceStatus.IN_PROGRESS
        );
      case 'resolved': // "Completed"
        return grievances.filter(g => 
          g.status === GrievanceStatus.RESOLVED || 
          g.status === GrievanceStatus.CLOSED
        );
      case 'escalated': // "Critical Alerts"
        return grievances.filter(g => 
          g.status === GrievanceStatus.ESCALATED || 
          g.priority === 'URGENT'
        );
      default:
        return grievances; // 'dashboard' shows all in department (or system in prototype)
    }
  }, [grievances, activeTab]);

  const sortedGrievances = useMemo(() => {
    const sortableItems = [...filteredByTab];
    sortableItems.sort((a, b) => {
      let valA = a[sortConfig.key] || '';
      let valB = b[sortConfig.key] || '';
      if (valA < valB) return sortConfig.direction === 'asc' ? -1 : 1;
      if (valA > valB) return sortConfig.direction === 'asc' ? 1 : -1;
      return 0;
    });
    return sortableItems;
  }, [filteredByTab, sortConfig]);

  const requestSort = (key: SortKey) => {
    setSortConfig({ key, direction: sortConfig.key === key && sortConfig.direction === 'asc' ? 'desc' : 'asc' });
  };

  const getDaysSince = (dateString: string) => {
    const diff = Math.floor((Date.now() - new Date(dateString).getTime()) / (1000 * 3600 * 24));
    return diff === 0 ? 'Today' : `${diff} days ago`;
  };

  const getHeaderContent = () => {
    switch (activeTab) {
      case 'pending': return { title: 'Active Queue', desc: 'Tickets currently under review and field audit' };
      case 'resolved': return { title: 'Completed', desc: 'Resolved issues and officially closed cases' };
      case 'escalated': return { title: 'Critical Alerts', desc: 'Urgent priority and escalated items requiring immediate action' };
      default: return { title: 'My Workspace', desc: 'Consolidated view of all system grievances (Prototype Mode)' };
    }
  };

  const header = getHeaderContent();

  return (
    <div className="animate-fade-in space-y-8 py-4">
      <div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
        <div>
          <h2 className="text-3xl font-black text-slate-900 tracking-tight">{header.title}</h2>
          <p className="text-slate-500 font-medium mt-1">{header.desc}</p>
        </div>
        <div className="flex gap-3">
           <div className="bg-white px-5 py-3 rounded-2xl border border-slate-200 flex items-center gap-3 shadow-sm">
              <span className="text-[10px] font-black text-slate-400 uppercase tracking-widest">In view</span>
              <span className="text-lg font-black text-blue-600">{sortedGrievances.length}</span>
           </div>
           <button className="bg-slate-900 text-white px-6 py-3 rounded-2xl text-[10px] font-black hover:bg-slate-800 transition-all shadow-lg flex items-center gap-2 uppercase tracking-widest">
             <i className="fa-solid fa-file-export"></i> Export Report
           </button>
        </div>
      </div>

      <div className="bg-white rounded-[2.5rem] shadow-sm border border-slate-200 overflow-hidden min-h-[450px]">
        <div className="overflow-x-auto">
          <table className="min-w-full divide-y divide-slate-100">
            <thead className="bg-slate-50/50">
              <tr>
                {['ticketNumber', 'district', 'status'].map((k) => (
                  <th key={k} onClick={() => requestSort(k as SortKey)} className="px-8 py-6 text-left text-[10px] font-black text-slate-400 uppercase tracking-[0.2em] cursor-pointer hover:text-blue-600 transition-colors">
                    {k.replace('Number', '')} <i className={`fa-solid fa-sort ml-2 opacity-20`}></i>
                  </th>
                ))}
                <th className="px-8 py-6 text-right text-[10px] font-black text-slate-400 uppercase tracking-[0.2em]">Manage</th>
              </tr>
            </thead>
            <tbody className="divide-y divide-slate-50">
              {sortedGrievances.length > 0 ? (
                sortedGrievances.map((g) => (
                  <tr key={g.id} className="group hover:bg-slate-50/50 transition-all cursor-pointer" onClick={() => setSelectedTicket(g)}>
                    <td className="px-8 py-6">
                      <div className="flex items-center gap-4">
                        <div className={`w-2 h-8 rounded-full ${g.priority === 'URGENT' ? 'bg-red-500 animate-pulse' : 'bg-slate-100'}`}></div>
                        <div>
                          <div className="text-sm font-black text-blue-600 tracking-tight mb-0.5">{g.ticketNumber}</div>
                          <div className="text-xs font-bold text-slate-800 line-clamp-1">{g.title}</div>
                        </div>
                      </div>
                    </td>
                    <td className="px-8 py-6">
                      <div className="text-xs font-black text-slate-700">{g.district}</div>
                      <div className="text-[10px] font-bold text-slate-400 mt-1 uppercase tracking-tight">{g.taluka}</div>
                    </td>
                    <td className="px-8 py-6">
                      <span className={`text-[9px] px-3 py-1.5 rounded-xl font-black uppercase tracking-wider ${STATUS_COLORS[g.status]}`}>{g.status.replace('_', ' ')}</span>
                    </td>
                    <td className="px-8 py-6 text-right">
                      <button className="w-10 h-10 bg-white border border-slate-200 rounded-xl inline-flex items-center justify-center text-slate-400 group-hover:text-blue-600 group-hover:border-blue-200 transition-all shadow-sm">
                        <i className="fa-solid fa-arrow-right-long"></i>
                      </button>
                    </td>
                  </tr>
                ))
              ) : (
                <tr>
                  <td colSpan={4} className="px-8 py-32 text-center">
                    <div className="flex flex-col items-center">
                      <div className="w-20 h-20 bg-slate-50 rounded-[2rem] flex items-center justify-center text-slate-200 mb-6 border border-slate-100">
                        <i className="fa-solid fa-folder-open text-3xl"></i>
                      </div>
                      <p className="text-sm font-black text-slate-900 uppercase tracking-widest">Workspace is clear</p>
                      <p className="text-xs font-bold text-slate-400 mt-2">No tickets found matching your current view.</p>
                    </div>
                  </td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
      </div>

      {selectedTicket && (
        <div className="fixed inset-0 z-50 flex items-center justify-end bg-slate-900/70 transition-all animate-fade-in">
          <div className="w-full max-w-2xl h-full bg-white shadow-2xl animate-slide-in-right flex flex-col rounded-l-[3rem] border-l border-slate-200 relative overflow-hidden">
            
            <div className="p-8 border-b border-slate-100 flex justify-between items-start bg-slate-50">
              <div>
                <div className="flex items-center gap-3 mb-2">
                   <span className="text-[10px] font-black text-blue-600 bg-blue-50 border border-blue-100 px-3 py-1 rounded-lg uppercase tracking-widest">{selectedTicket.ticketNumber}</span>
                   <span className={`text-[10px] px-3 py-1 rounded-lg font-black uppercase tracking-wider ${PRIORITY_COLORS[selectedTicket.priority]}`}>{selectedTicket.priority} Priority</span>
                </div>
                <h3 className="text-2xl font-black text-slate-900 leading-tight pr-8">{selectedTicket.title}</h3>
              </div>
              <button onClick={() => { setSelectedTicket(null); setSummary(''); }} className="w-12 h-12 rounded-2xl bg-white border border-slate-200 text-slate-400 hover:text-slate-900 hover:border-slate-300 transition-all flex items-center justify-center shadow-sm">
                <i className="fa-solid fa-xmark"></i>
              </button>
            </div>
            
            <div className="p-10 space-y-10 flex-1 overflow-y-auto bg-white">
              <div className="bg-slate-50 rounded-[2.5rem] p-8 border border-slate-100 flex items-center justify-between shadow-sm">
                <div className="flex items-center gap-5">
                  <div className="w-14 h-14 bg-white rounded-2xl flex items-center justify-center text-blue-600 shadow-sm border border-slate-100">
                    <i className="fa-solid fa-id-card text-xl"></i>
                  </div>
                  <div>
                    <p className="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-1">Citizen Details</p>
                    <p className="font-black text-slate-900 text-lg">{selectedTicket.citizenName}</p>
                    <p className="text-xs font-bold text-slate-500 tracking-wider">+91 {selectedTicket.contactNumber}</p>
                  </div>
                </div>
                <div className="text-right">
                  <p className="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-1">Timeline</p>
                  <p className="font-black text-slate-900 text-sm">{new Date(selectedTicket.createdAt).toLocaleDateString()}</p>
                  <p className="text-[10px] font-black text-blue-600 mt-1 uppercase tracking-tighter">{getDaysSince(selectedTicket.createdAt)}</p>
                </div>
              </div>

              <div>
                <div className="flex items-center gap-3 mb-4">
                   <div className="w-1 h-4 bg-orange-500 rounded-full"></div>
                   <p className="text-[10px] font-black text-slate-800 uppercase tracking-[0.2em]">Statement of Issue</p>
                </div>
                <div className="p-8 bg-slate-50 rounded-[2.5rem] border border-slate-100 text-sm font-bold text-slate-600 leading-relaxed italic">
                  "{selectedTicket.description}"
                </div>
              </div>

              <div className="bg-blue-50 border border-blue-100 rounded-[2.5rem] p-8 space-y-6">
                <div className="flex items-center justify-between">
                  <div className="flex items-center gap-3">
                     <div className="w-10 h-10 rounded-xl bg-white flex items-center justify-center shadow-sm text-blue-600">
                        <i className="fa-solid fa-sparkles"></i>
                     </div>
                     <p className="text-[10px] font-black text-blue-900 uppercase tracking-widest">Case Intelligence</p>
                  </div>
                  <button 
                    onClick={async () => { 
                      setIsSummarizing(true); 
                      setSummary(await summarizeGrievance(selectedTicket.description) || ''); 
                      setIsSummarizing(false); 
                    }} 
                    disabled={isSummarizing}
                    className="px-6 py-2.5 bg-blue-600 text-white rounded-xl text-[10px] font-black uppercase tracking-widest hover:bg-blue-700 transition-all disabled:opacity-50 shadow-lg shadow-blue-500/20"
                  >
                    {isSummarizing ? <i className="fa-solid fa-spinner animate-spin"></i> : 'Run AI Analysis'}
                  </button>
                </div>
                
                {summary && (
                  <div className="space-y-4 animate-fade-in p-6 bg-white rounded-[2rem] border border-blue-200/50">
                    <div className="text-blue-900 font-bold text-xs leading-relaxed whitespace-pre-line">
                      {summary}
                    </div>
                  </div>
                )}
              </div>

              <div className="grid grid-cols-2 gap-6">
                 <div className="bg-white p-6 rounded-[2rem] border border-slate-100 shadow-sm">
                    <p className="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-4">Jurisdiction</p>
                    <div className="flex items-center gap-4">
                      <div className="w-10 h-10 bg-slate-50 rounded-xl flex items-center justify-center text-slate-400 border border-slate-100">
                        <i className="fa-solid fa-map-location-dot"></i>
                      </div>
                      <p className="text-xs font-black text-slate-900">{selectedTicket.district}</p>
                    </div>
                 </div>
                 <div className="bg-white p-6 rounded-[2rem] border border-slate-100 shadow-sm">
                    <p className="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-4">SLA Deadline</p>
                    <div className="flex items-center gap-4">
                      <div className="w-10 h-10 bg-slate-50 rounded-xl flex items-center justify-center text-slate-400 border border-slate-100">
                        <i className="fa-solid fa-hourglass-half"></i>
                      </div>
                      <p className="text-xs font-black text-slate-900">{new Date(selectedTicket.slaDeadline).toLocaleDateString()}</p>
                    </div>
                 </div>
              </div>
            </div>

            <div className="p-10 border-t border-slate-100 bg-slate-50/50 flex flex-col gap-4">
               <div className="flex gap-4">
                  <button onClick={() => onUpdateStatus(selectedTicket.id, GrievanceStatus.RESOLVED)} className="flex-1 py-5 bg-emerald-600 hover:bg-emerald-700 text-white rounded-[1.5rem] font-black text-xs uppercase tracking-[0.2em] shadow-xl shadow-emerald-500/20 transition-all">
                    Resolve Issue
                  </button>
                  <button onClick={() => onUpdateStatus(selectedTicket.id, GrievanceStatus.ESCALATED)} className="px-10 py-5 bg-white border border-slate-200 text-rose-600 hover:bg-rose-50 rounded-[1.5rem] font-black text-xs uppercase tracking-[0.2em] transition-all">
                    Escalate
                  </button>
               </div>
               <div className="flex gap-4">
                  <button onClick={() => onUpdateStatus(selectedTicket.id, GrievanceStatus.IN_PROGRESS)} className="flex-1 py-4 bg-slate-900 text-white rounded-[1.5rem] font-black text-[10px] uppercase tracking-widest transition-all">
                    Mark In-Progress
                  </button>
                  <button className="px-10 py-4 bg-white border border-slate-200 text-slate-400 rounded-[1.5rem] font-black text-[10px] uppercase tracking-widest transition-all">
                    Officer Remark
                  </button>
               </div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

export default OfficerDashboard;