diff --git a/rohtak_grievance_MVP/.gitignore b/rohtak_grievance_MVP/.gitignore new file mode 100644 index 00000000..f650315f --- /dev/null +++ b/rohtak_grievance_MVP/.gitignore @@ -0,0 +1,27 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules + +# next.js +/.next/ +/out/ + +# production +/build + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts \ No newline at end of file diff --git a/rohtak_grievance_MVP/README.md b/rohtak_grievance_MVP/README.md new file mode 100644 index 00000000..9372a0df --- /dev/null +++ b/rohtak_grievance_MVP/README.md @@ -0,0 +1,45 @@ +# Rohtak Grievance Redressal Chatbot MVP + +This repository contains the MVP implementation for the [District-Level Chatbot for Grievance Redressal](https://github.com/Code4GovTech/C4GT/issues/512) project. + +## 🚀 Features + +- **Bilingual Support**: Interface in both Hindi and English +- **Citizen Chatbot Interface**: WhatsApp-style interface for complaint registration +- **Admin Dashboard**: For officials to manage and respond to complaints +- **Department Routing**: Automatic routing to appropriate departments +- **Tracking System**: Unique IDs and status updates for complaints + +## 📸 Screenshots + +![Alt text](https://res.cloudinary.com/dllgawv0f/image/upload/v1746464949/roh_1_kayxvw.jpg) +![Alt text](https://res.cloudinary.com/dllgawv0f/image/upload/v1746465096/roh_6_aor0ll.jpg) +![Alt text](https://res.cloudinary.com/dllgawv0f/image/upload/v1746465097/roh_4_jwhopj.jpg) +![Alt text](https://res.cloudinary.com/dllgawv0f/image/upload/v1746465097/roh_2_jjbfok.jpg) +![Alt text](https://res.cloudinary.com/dllgawv0f/image/upload/v1746465098/roh_3_mk3dam.jpg) + + +## 🛠️ Tech Stack + +- Next.js for frontend +- PostgreSQL for backend (in full implementation) +- Tailwind CSS for styling + +## 🚦 How to Run + +1. Clone this repository +2. Run `npm install` +3. Run `npm start` +4. Open `http://localhost:3000` in your browser + +## 📝 Implementation Notes + +This MVP demonstrates the core user flows and interfaces for both citizens and administrators. The next steps would include: +- WhatsApp API integration +- Firebase/MongoDB backend implementation +- Authentication for admin users +- Department-specific views and analytics + +## 🔗 Related Links + +- [Original Issue #512](https://github.com/Code4GovTech/C4GT/issues/512) diff --git a/rohtak_grievance_MVP/app/admin/loading.tsx b/rohtak_grievance_MVP/app/admin/loading.tsx new file mode 100644 index 00000000..f15322a8 --- /dev/null +++ b/rohtak_grievance_MVP/app/admin/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return null +} diff --git a/rohtak_grievance_MVP/app/admin/page.tsx b/rohtak_grievance_MVP/app/admin/page.tsx new file mode 100644 index 00000000..7d79fb0c --- /dev/null +++ b/rohtak_grievance_MVP/app/admin/page.tsx @@ -0,0 +1,449 @@ +"use client" + +import { useState } from "react" +import Link from "next/link" +import { ArrowLeft, Filter, Search, MoreHorizontal } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Badge } from "@/components/ui/badge" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { ComplaintDetails } from "@/components/complaint-details" + +// Mock data for complaints +const mockComplaints = [ + { + id: "RGR12345", + type: "Water Supply Issue", + description: "Low water pressure in Sector 4 area", + location: "Sector 4, Block B", + status: "In Progress", + priority: "Medium", + submittedBy: "Rahul Sharma", + submittedOn: "2025-05-03", + assignedTo: "Water Department", + lastUpdated: "2025-05-04", + updates: [ + { date: "2025-05-03", content: "Complaint registered" }, + { date: "2025-05-04", content: "Assigned to field team for inspection" }, + ], + }, + { + id: "RGR12346", + type: "Road Maintenance", + description: "Pothole on main road near market", + location: "Sector 2, Market Road", + status: "Pending", + priority: "High", + submittedBy: "Priya Patel", + submittedOn: "2025-05-02", + assignedTo: "Roads Department", + lastUpdated: "2025-05-02", + updates: [{ date: "2025-05-02", content: "Complaint registered" }], + }, + { + id: "RGR12347", + type: "Garbage Collection", + description: "Garbage not collected for 3 days", + location: "Sector 7, C Block", + status: "Resolved", + priority: "Medium", + submittedBy: "Amit Kumar", + submittedOn: "2025-05-01", + assignedTo: "Sanitation Department", + lastUpdated: "2025-05-03", + updates: [ + { date: "2025-05-01", content: "Complaint registered" }, + { date: "2025-05-02", content: "Assigned to sanitation team" }, + { date: "2025-05-03", content: "Issue resolved - garbage collected" }, + ], + }, + { + id: "RGR12348", + type: "Electricity Issue", + description: "Frequent power cuts in the evening", + location: "Sector 5, D Block", + status: "In Progress", + priority: "High", + submittedBy: "Neha Singh", + submittedOn: "2025-05-02", + assignedTo: "Electricity Department", + lastUpdated: "2025-05-04", + updates: [ + { date: "2025-05-02", content: "Complaint registered" }, + { date: "2025-05-03", content: "Assigned to electrical team" }, + { date: "2025-05-04", content: "Team dispatched for inspection" }, + ], + }, + { + id: "RGR12349", + type: "Water Supply Issue", + description: "Contaminated water supply", + location: "Sector 1, A Block", + status: "Pending", + priority: "Critical", + submittedBy: "Vikram Malhotra", + submittedOn: "2025-05-04", + assignedTo: "Water Department", + lastUpdated: "2025-05-04", + updates: [{ date: "2025-05-04", content: "Complaint registered" }], + }, +] + +export default function AdminDashboard() { + const [complaints, setComplaints] = useState(mockComplaints) + const [searchQuery, setSearchQuery] = useState("") + const [selectedComplaint, setSelectedComplaint] = useState(null) + const [showDetails, setShowDetails] = useState(false) + + const filteredComplaints = complaints.filter( + (complaint) => + complaint.id.toLowerCase().includes(searchQuery.toLowerCase()) || + complaint.type.toLowerCase().includes(searchQuery.toLowerCase()) || + complaint.description.toLowerCase().includes(searchQuery.toLowerCase()) || + complaint.location.toLowerCase().includes(searchQuery.toLowerCase()) || + complaint.submittedBy.toLowerCase().includes(searchQuery.toLowerCase()), + ) + + const handleStatusChange = (complaintId: string, newStatus: string) => { + setComplaints( + complaints.map((complaint) => + complaint.id === complaintId + ? { + ...complaint, + status: newStatus, + lastUpdated: new Date().toISOString().split("T")[0], + updates: [ + ...complaint.updates, + { + date: new Date().toISOString().split("T")[0], + content: `Status updated to ${newStatus}`, + }, + ], + } + : complaint, + ), + ) + + if (selectedComplaint && selectedComplaint.id === complaintId) { + setSelectedComplaint({ + ...selectedComplaint, + status: newStatus, + lastUpdated: new Date().toISOString().split("T")[0], + updates: [ + ...selectedComplaint.updates, + { + date: new Date().toISOString().split("T")[0], + content: `Status updated to ${newStatus}`, + }, + ], + }) + } + } + + const viewComplaintDetails = (complaint: any) => { + setSelectedComplaint(complaint) + setShowDetails(true) + } + + return ( +
+
+
+ + + Back to Home + +
+ Admin Dashboard +
+
+
+ +
+
+
+
+
+

Complaint Management

+

Manage and respond to citizen complaints

+
+
+
+ + setSearchQuery(e.target.value)} + /> +
+ +
+
+ + {showDetails && selectedComplaint ? ( +
+ + +
+ ) : ( + + + All Complaints + Pending + In Progress + Resolved + + + + + + + + Ticket ID + Type + Location + Submitted On + Status + Priority + Actions + + + + {filteredComplaints.map((complaint) => ( + + {complaint.id} + {complaint.type} + {complaint.location} + {complaint.submittedOn} + + + {complaint.status} + + + + + {complaint.priority} + + + +
+ + + + + + + handleStatusChange(complaint.id, "In Progress")}> + Mark as In Progress + + handleStatusChange(complaint.id, "Resolved")}> + Mark as Resolved + + Reassign Complaint + + +
+
+
+ ))} +
+
+
+
+
+ + + + + + + Ticket ID + Type + Location + Submitted On + Priority + Actions + + + + {filteredComplaints + .filter((complaint) => complaint.status === "Pending") + .map((complaint) => ( + + {complaint.id} + {complaint.type} + {complaint.location} + {complaint.submittedOn} + + + {complaint.priority} + + + + + + + ))} + +
+
+
+
+ + + + + + + Ticket ID + Type + Location + Assigned To + Last Updated + Actions + + + + {filteredComplaints + .filter((complaint) => complaint.status === "In Progress") + .map((complaint) => ( + + {complaint.id} + {complaint.type} + {complaint.location} + {complaint.assignedTo} + {complaint.lastUpdated} + + + + + ))} + +
+
+
+
+ + + + + + + Ticket ID + Type + Location + Resolved On + Actions + + + + {filteredComplaints + .filter((complaint) => complaint.status === "Resolved") + .map((complaint) => ( + + {complaint.id} + {complaint.type} + {complaint.location} + {complaint.lastUpdated} + + + + + ))} + +
+
+
+
+
+ )} + +
+ + + Total Complaints + + +
{complaints.length}
+

+12% from last month

+
+
+ + + Pending + + +
{complaints.filter((c) => c.status === "Pending").length}
+

Requires attention

+
+
+ + + In Progress + + +
+ {complaints.filter((c) => c.status === "In Progress").length} +
+

Being addressed

+
+
+ + + Resolved + + +
{complaints.filter((c) => c.status === "Resolved").length}
+

Successfully completed

+
+
+
+
+
+
+
+ ) +} diff --git a/rohtak_grievance_MVP/app/chat/page.tsx b/rohtak_grievance_MVP/app/chat/page.tsx new file mode 100644 index 00000000..aafbd9fa --- /dev/null +++ b/rohtak_grievance_MVP/app/chat/page.tsx @@ -0,0 +1,207 @@ +"use client" + +import { useState, useRef, useEffect } from "react" +import Link from "next/link" +import { ArrowLeft, Send, Paperclip } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { ChatMessage } from "@/components/chat-message" +import { ComplaintForm } from "@/components/complaint-form" +import { TicketConfirmation } from "@/components/ticket-confirmation" + +type MessageType = { + id: string + content: string + sender: "user" | "bot" + timestamp: Date +} + +type ChatStage = "welcome" | "complaint-type" | "complaint-form" | "confirmation" + +export default function ChatPage() { + const [messages, setMessages] = useState([ + { + id: "1", + content: "Welcome to Rohtak Grievance Redressal System. How can I help you today?", + sender: "bot", + timestamp: new Date(), + }, + ]) + const [inputValue, setInputValue] = useState("") + const [stage, setStage] = useState("welcome") + const [complaintType, setComplaintType] = useState(null) + const [ticketId, setTicketId] = useState(null) + + const messagesEndRef = useRef(null) + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }) + } + + useEffect(() => { + scrollToBottom() + }, [messages]) + + const handleSend = () => { + if (!inputValue.trim()) return + + // Add user message + const newUserMessage: MessageType = { + id: Date.now().toString(), + content: inputValue, + sender: "user", + timestamp: new Date(), + } + setMessages((prev) => [...prev, newUserMessage]) + setInputValue("") + + // Process based on current stage + if (stage === "welcome") { + setTimeout(() => { + const botResponse: MessageType = { + id: (Date.now() + 1).toString(), + content: "Please select the type of complaint you want to register:", + sender: "bot", + timestamp: new Date(), + } + setMessages((prev) => [...prev, botResponse]) + setStage("complaint-type") + }, 1000) + } else if (stage === "complaint-type") { + setComplaintType(inputValue) + setTimeout(() => { + const botResponse: MessageType = { + id: (Date.now() + 1).toString(), + content: `You've selected: ${inputValue}. Please provide more details about your complaint.`, + sender: "bot", + timestamp: new Date(), + } + setMessages((prev) => [...prev, botResponse]) + setStage("complaint-form") + }, 1000) + } + } + + const handleComplaintSubmit = (data: any) => { + // Generate a random ticket ID + const newTicketId = `RGR${Math.floor(10000 + Math.random() * 90000)}` + setTicketId(newTicketId) + + setTimeout(() => { + const botResponse: MessageType = { + id: (Date.now() + 1).toString(), + content: `Thank you for submitting your complaint. Your ticket ID is: ${newTicketId}`, + sender: "bot", + timestamp: new Date(), + } + setMessages((prev) => [...prev, botResponse]) + setStage("confirmation") + }, 1000) + } + + return ( +
+
+
+ + + Back to Home + +
+ Rohtak Grievance Chat +
+
+
+ +
+
+
+
+ {messages.map((message) => ( + + ))} + + {stage === "complaint-type" && ( +
+ + + + +
+ )} + + {stage === "complaint-form" && ( + + )} + + {stage === "confirmation" && ticketId && } + +
+
+
+ +
+
+ + setInputValue(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && handleSend()} + disabled={stage === "complaint-form" || stage === "confirmation"} + /> + +
+
+
+
+
+ ) +} diff --git a/rohtak_grievance_MVP/app/globals.css b/rohtak_grievance_MVP/app/globals.css new file mode 100644 index 00000000..ac684423 --- /dev/null +++ b/rohtak_grievance_MVP/app/globals.css @@ -0,0 +1,94 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: Arial, Helvetica, sans-serif; +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/rohtak_grievance_MVP/app/layout.tsx b/rohtak_grievance_MVP/app/layout.tsx new file mode 100644 index 00000000..17b2ce8c --- /dev/null +++ b/rohtak_grievance_MVP/app/layout.tsx @@ -0,0 +1,20 @@ +import type { Metadata } from 'next' +import './globals.css' + +export const metadata: Metadata = { + title: 'v0 App', + description: 'Created with v0', + generator: 'v0.dev', +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + {children} + + ) +} diff --git a/rohtak_grievance_MVP/app/page.tsx b/rohtak_grievance_MVP/app/page.tsx new file mode 100644 index 00000000..6f02b3c4 --- /dev/null +++ b/rohtak_grievance_MVP/app/page.tsx @@ -0,0 +1,103 @@ +import Link from "next/link" +import { ArrowRight } from "lucide-react" + +import { Button } from "@/components/ui/button" + +export default function Home() { + return ( +
+
+
+
+ + Rohtak Grievance Portal + +
+
+ + + +
+
+
+
+
+
+
+
+

+ Rohtak Grievance Redressal System +

+

+ A simple and effective way to register your complaints and track their resolution. Our WhatsApp-style + interface makes it easy to communicate your concerns. +

+
+ + + + + + +
+
+
+ Grievance Redressal System +
+
+
+
+
+
+
+
+

How It Works

+

+ Our simple 3-step process makes it easy to get your grievances addressed +

+
+
+
+
+
+ 1 +
+

Register Complaint

+

Use our WhatsApp-style chat interface to describe your issue

+
+
+
+ 2 +
+

Get Ticket Number

+

Receive a unique ticket ID to track your complaint status

+
+
+
+ 3 +
+

Track Resolution

+

+ Follow the progress as your complaint is addressed by the relevant department +

+
+
+
+
+
+
+
+

© 2025 Rohtak Municipal Corporation. All rights reserved.

+
+
+
+ ) +} diff --git a/rohtak_grievance_MVP/app/track/loading.tsx b/rohtak_grievance_MVP/app/track/loading.tsx new file mode 100644 index 00000000..f15322a8 --- /dev/null +++ b/rohtak_grievance_MVP/app/track/loading.tsx @@ -0,0 +1,3 @@ +export default function Loading() { + return null +} diff --git a/rohtak_grievance_MVP/app/track/page.tsx b/rohtak_grievance_MVP/app/track/page.tsx new file mode 100644 index 00000000..24147247 --- /dev/null +++ b/rohtak_grievance_MVP/app/track/page.tsx @@ -0,0 +1,154 @@ +"use client" + +import { useState } from "react" +import Link from "next/link" +import { ArrowLeft, Search } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Label } from "@/components/ui/label" +import { Badge } from "@/components/ui/badge" + +export default function TrackPage() { + const [ticketId, setTicketId] = useState("") + const [searchPerformed, setSearchPerformed] = useState(false) + const [ticketDetails, setTicketDetails] = useState(null) + const [error, setError] = useState("") + + const handleSearch = () => { + if (!ticketId.trim()) { + setError("Please enter a ticket ID") + return + } + + setSearchPerformed(true) + setError("") + + // Mock ticket lookup - in a real app, this would be an API call + if (ticketId.startsWith("RGR")) { + setTicketDetails({ + id: ticketId, + status: "In Progress", + type: "Water Supply Issue", + description: "Low water pressure in Sector 4 area", + location: "Sector 4, Block B", + submittedOn: "2025-05-03", + assignedTo: "Water Department", + lastUpdated: "2025-05-04", + updates: [ + { date: "2025-05-03", content: "Complaint registered" }, + { date: "2025-05-04", content: "Assigned to field team for inspection" }, + ], + }) + } else { + setTicketDetails(null) + setError("No ticket found with this ID. Please check and try again.") + } + } + + return ( +
+
+
+ + + Back to Home + +
+ Track Your Complaint +
+
+
+ +
+
+ + + Track Complaint Status + Enter your ticket ID to check the status of your complaint + + +
+
+ +
+ setTicketId(e.target.value)} + /> + +
+ {error &&

{error}

} +
+ + {searchPerformed && ticketDetails && ( +
+
+

Ticket: {ticketDetails.id}

+ + {ticketDetails.status} + +
+ +
+
+

Complaint Type

+

{ticketDetails.type}

+
+
+

Submitted On

+

{ticketDetails.submittedOn}

+
+
+

Location

+

{ticketDetails.location}

+
+
+

Assigned To

+

{ticketDetails.assignedTo}

+
+
+ +
+

Description

+

{ticketDetails.description}

+
+ +
+

Status Updates

+
+ {ticketDetails.updates.map((update: any, index: number) => ( +
+
{update.date}
+
{update.content}
+
+ ))} +
+
+
+ )} +
+
+ +

Need help? Contact our support at support@rohtak.gov.in

+
+
+
+
+
+ ) +} diff --git a/rohtak_grievance_MVP/components.json b/rohtak_grievance_MVP/components.json new file mode 100644 index 00000000..d9ef0ae5 --- /dev/null +++ b/rohtak_grievance_MVP/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/rohtak_grievance_MVP/components/chat-message.tsx b/rohtak_grievance_MVP/components/chat-message.tsx new file mode 100644 index 00000000..d7206ebb --- /dev/null +++ b/rohtak_grievance_MVP/components/chat-message.tsx @@ -0,0 +1,24 @@ +import { format } from "date-fns" + +interface ChatMessageProps { + content: string + sender: "user" | "bot" + timestamp: Date +} + +export function ChatMessage({ content, sender, timestamp }: ChatMessageProps) { + return ( +
+
+
{content}
+
+ {format(timestamp, "HH:mm")} +
+
+
+ ) +} diff --git a/rohtak_grievance_MVP/components/complaint-details.tsx b/rohtak_grievance_MVP/components/complaint-details.tsx new file mode 100644 index 00000000..fc33bd60 --- /dev/null +++ b/rohtak_grievance_MVP/components/complaint-details.tsx @@ -0,0 +1,142 @@ +"use client" + +import { useState } from "react" +import { Send } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { Textarea } from "@/components/ui/textarea" +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { Separator } from "@/components/ui/separator" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" + +interface ComplaintDetailsProps { + complaint: any + onStatusChange: (complaintId: string, newStatus: string) => void +} + +export function ComplaintDetails({ complaint, onStatusChange }: ComplaintDetailsProps) { + const [newUpdate, setNewUpdate] = useState("") + + const handleAddUpdate = () => { + if (!newUpdate.trim()) return + + // In a real app, this would call an API to add the update + console.log("Adding update:", newUpdate) + setNewUpdate("") + } + + return ( +
+ + +
+
+ Complaint #{complaint.id} + Submitted on {complaint.submittedOn} +
+ + {complaint.status} + +
+
+ +
+
+

Complaint Type

+

{complaint.type}

+
+
+

Priority

+ + {complaint.priority} + +
+
+

Location

+

{complaint.location}

+
+
+

Assigned To

+

{complaint.assignedTo}

+
+
+

Submitted By

+

{complaint.submittedBy}

+
+
+

Last Updated

+

{complaint.lastUpdated}

+
+
+ +
+

Description

+

{complaint.description}

+
+ + + +
+
+

Status Updates

+
+ +
+
+ +
+ {complaint.updates.map((update: any, index: number) => ( +
+
{update.date}
+
{update.content}
+
+ ))} +
+
+
+ +
+

Add Update

+
+