devarshia5 commited on
Commit
523c564
·
verified ·
1 Parent(s): c974655

Upload 30 files

Browse files
.env.example ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # GEMINI_API_KEY: Required for Gemini AI API calls.
2
+ # AI Studio automatically injects this at runtime from user secrets.
3
+ # Users configure this via the Secrets panel in the AI Studio UI.
4
+ GEMINI_API_KEY="MY_GEMINI_API_KEY"
5
+
6
+ # APP_URL: The URL where this applet is hosted.
7
+ # AI Studio automatically injects this at runtime with the Cloud Run service URL.
8
+ # Used for self-referential links, OAuth callbacks, and API endpoints.
9
+ APP_URL="MY_APP_URL"
.gitignore ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ node_modules/
2
+ build/
3
+ dist/
4
+ coverage/
5
+ .DS_Store
6
+ *.log
7
+ .env*
8
+ !.env.example
9
+
10
+ # OpenMemory - IDE/Assistant specific rules
11
+ .windsurf\rules\openmemory.md
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18-alpine
2
+
3
+ WORKDIR /app
4
+
5
+ # Copy package files
6
+ COPY package*.json ./
7
+
8
+ # Install dependencies
9
+ RUN npm install --legacy-peer-deps
10
+
11
+ # Copy all files
12
+ COPY . .
13
+
14
+ # Build the app
15
+ RUN npm run build
16
+
17
+ # Expose port 7860 (Hugging Face Spaces default)
18
+ EXPOSE 7860
19
+
20
+ # Start the preview server
21
+ CMD ["npm", "run", "preview", "--", "--port", "7860", "--host", "0.0.0.0"]
index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>WRD JALDRISHTI</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
13
+
metadata.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "name": "WRD Insight Dashboard",
3
+ "description": "Centralized repository system for Government GRs, Acts, circulars, and flood prediction dashboard for the Water Resources Department.",
4
+ "requestFramePermissions": []
5
+ }
openmemory.md ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # WRD Insight Dashboard (JalDrishti)
3
+
4
+ ## Overview
5
+ React + TypeScript (Vite) dashboard for Maharashtra WRD concept demo. Key modules:
6
+ - Dashboard (KPIs/analytics)
7
+ - GIS Map (Leaflet)
8
+ - Repository (documents)
9
+ - AI Assistant (chat-style document assistant)
10
+
11
+ ## Architecture
12
+ - Top-level layout: `src/components/Layout.tsx` with sidebar + header + routed page content via `react-router-dom` `Outlet`.
13
+ - Routes: `src/App.tsx` mounts `Chatbot` at `/ai-assistant`.
14
+
15
+ ## Components
16
+ - `src/pages/Chatbot.tsx`
17
+ - Desktop chat renders within the routed page content area.
18
+ - Mobile chat uses a floating button and a `fixed inset-0` full-screen overlay.
19
+ - `src/pages/GisMap.tsx`
20
+ - Leaflet-based GIS map with district boundaries and dam markers.
21
+ - Loads cached GeoJSON from `public/data/maharashtra-districts.geojson`.
22
+ - Live Data button toggles simulated rainfall/flood pulse animation overlay.
23
+
24
+ ## Patterns
25
+ - **Nested flex scroll fix**: for chat UIs inside dashboards, ensure the scrollable region is in a `flex-1 min-h-0 overflow-y-auto` container, and parents also include `min-h-0` when needed. This prevents the input/footer from being pushed off-screen.
26
+ - **Route-specific page wrapper**: for `/ai-assistant` and `/map`, `Layout.tsx` uses `overflow-hidden` and removes outer padding so each page manages its internal scrolling and spacing.
27
+ - **GeoJSON caching for GIS**: District boundaries are cached locally to avoid Nominatim rate-limiting. Run `python scripts/cache_maharashtra_districts_geojson.py` to refresh. Script uses bbox fallback when Nominatim is unavailable.
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "react-example",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --port=3000 --host=0.0.0.0",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "clean": "rm -rf dist",
11
+ "lint": "tsc --noEmit"
12
+ },
13
+ "dependencies": {
14
+ "@google/genai": "^1.29.0",
15
+ "@tailwindcss/vite": "^4.1.14",
16
+ "@types/leaflet": "^1.9.21",
17
+ "@vitejs/plugin-react": "^5.0.4",
18
+ "better-sqlite3": "^12.4.1",
19
+ "clsx": "^2.1.1",
20
+ "date-fns": "^4.1.0",
21
+ "dotenv": "^17.2.3",
22
+ "express": "^4.21.2",
23
+ "leaflet": "^1.9.4",
24
+ "lucide-react": "^0.546.0",
25
+ "motion": "^12.23.24",
26
+ "react": "^19.0.0",
27
+ "react-dom": "^19.0.0",
28
+ "react-leaflet": "^5.0.0",
29
+ "react-router-dom": "^7.13.1",
30
+ "recharts": "^3.7.0",
31
+ "tailwind-merge": "^3.5.0",
32
+ "vite": "^6.2.0"
33
+ },
34
+ "devDependencies": {
35
+ "@types/express": "^4.17.21",
36
+ "@types/node": "^22.14.0",
37
+ "autoprefixer": "^10.4.21",
38
+ "tailwindcss": "^4.1.14",
39
+ "tsx": "^4.21.0",
40
+ "typescript": "~5.8.2",
41
+ "vite": "^6.2.0"
42
+ }
43
+ }
public/data/README.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ Place cached GeoJSON/JSON files here to avoid hitting Nominatim/OSRM rate limits.
2
+
3
+ Expected by GisMap.tsx:
4
+ - public/data/maharashtra-districts.geojson (FeatureCollection; each feature has properties.name matching district)
5
+
6
+ If this file is missing/empty, district boundaries will not render, but the app will still run.
public/data/maharashtra-districts.geojson ADDED
The diff for this file is too large to render. See raw diff
 
scripts/cache_maharashtra_districts_geojson.py ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import random
4
+ import sys
5
+ import time
6
+ import urllib.error
7
+ import urllib.parse
8
+ import urllib.request
9
+
10
+ DISTRICTS = [
11
+ "Kolhapur",
12
+ "Sangli",
13
+ "Satara",
14
+ "Pune",
15
+ "Ahmednagar",
16
+ "Nashik",
17
+ "Aurangabad",
18
+ "Thane",
19
+ "Raigad",
20
+ "Ratnagiri",
21
+ ]
22
+
23
+ # Approximate bounding boxes for Maharashtra districts (lon_min, lat_min, lon_max, lat_max)
24
+ # Used as fallback when Nominatim is rate-limited
25
+ DISTRICT_BBOXES = {
26
+ "Kolhapur": (73.6, 16.0, 74.8, 17.2),
27
+ "Sangli": (73.8, 16.4, 75.0, 17.2),
28
+ "Satara": (73.3, 17.0, 74.5, 18.0),
29
+ "Pune": (73.2, 18.0, 74.5, 19.0),
30
+ "Ahmednagar": (74.0, 18.5, 75.5, 19.5),
31
+ "Nashik": (73.0, 19.5, 74.5, 20.5),
32
+ "Aurangabad": (74.5, 19.0, 76.0, 20.0),
33
+ "Thane": (72.8, 19.0, 73.6, 20.0),
34
+ "Raigad": (72.8, 18.0, 73.5, 18.8),
35
+ "Ratnagiri": (72.8, 16.5, 73.8, 17.5),
36
+ }
37
+
38
+
39
+ def _request_json(url: str, timeout: int = 10) -> object:
40
+ req = urllib.request.Request(
41
+ url,
42
+ headers={
43
+ "Accept": "application/json",
44
+ "User-Agent": "wrd-insight-dashboard/1.0 (cache script; contact: local-demo)",
45
+ },
46
+ )
47
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
48
+ body = resp.read().decode("utf-8")
49
+ return json.loads(body)
50
+
51
+
52
+ def fetch_district_geojson_from_nominatim(district: str) -> dict | None:
53
+ """Try to fetch district boundary from Nominatim. Returns None on any error."""
54
+ params = {
55
+ "county": district,
56
+ "state": "Maharashtra",
57
+ "country": "India",
58
+ "polygon_geojson": 1,
59
+ "format": "json",
60
+ }
61
+ url = "https://nominatim.openstreetmap.org/search?" + urllib.parse.urlencode(params)
62
+ try:
63
+ data = _request_json(url, timeout=10)
64
+ except Exception:
65
+ return None
66
+
67
+ if isinstance(data, list) and data and isinstance(data[0], dict) and data[0].get("geojson"):
68
+ geo = data[0]["geojson"]
69
+ return {
70
+ "type": "Feature",
71
+ "properties": {"name": district, "source": "nominatim"},
72
+ "geometry": geo,
73
+ }
74
+ return None
75
+
76
+
77
+ def create_bbox_polygon(district: str) -> dict:
78
+ """Create a simple rectangular polygon from bounding box as fallback."""
79
+ bbox = DISTRICT_BBOXES.get(district)
80
+ if not bbox:
81
+ # Default to a placeholder in central Maharashtra
82
+ bbox = (73.5, 18.0, 75.0, 19.5)
83
+
84
+ lon_min, lat_min, lon_max, lat_max = bbox
85
+ # Create a simple rectangular polygon (5 points to close the ring)
86
+ coords = [
87
+ [lon_min, lat_min],
88
+ [lon_max, lat_min],
89
+ [lon_max, lat_max],
90
+ [lon_min, lat_max],
91
+ [lon_min, lat_min], # Close the ring
92
+ ]
93
+ return {
94
+ "type": "Feature",
95
+ "properties": {"name": district, "source": "bbox_fallback"},
96
+ "geometry": {"type": "Polygon", "coordinates": [coords]},
97
+ }
98
+
99
+
100
+ def main() -> int:
101
+ repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
102
+ out_path = os.path.join(repo_root, "public", "data", "maharashtra-districts.geojson")
103
+
104
+ os.makedirs(os.path.dirname(out_path), exist_ok=True)
105
+
106
+ features: list[dict] = []
107
+ nominatim_success = 0
108
+ fallback_used = 0
109
+
110
+ print("Fetching Maharashtra district boundaries...")
111
+ print("Strategy: Try Nominatim first (quick timeout), use bbox fallback if rate-limited.\n")
112
+
113
+ for district in DISTRICTS:
114
+ # Try Nominatim with just 1 attempt (quick)
115
+ feature = fetch_district_geojson_from_nominatim(district)
116
+
117
+ if feature:
118
+ features.append(feature)
119
+ nominatim_success += 1
120
+ print(f" ✓ {district}: fetched from Nominatim")
121
+ else:
122
+ # Use bounding box fallback immediately
123
+ feature = create_bbox_polygon(district)
124
+ features.append(feature)
125
+ fallback_used += 1
126
+ print(f" ○ {district}: using bbox fallback (Nominatim unavailable)")
127
+
128
+ # Minimal delay to be polite (Nominatim asks for 1 req/sec)
129
+ time.sleep(1.0)
130
+
131
+ # Write the FeatureCollection
132
+ fc = {"type": "FeatureCollection", "features": features}
133
+ with open(out_path, "w", encoding="utf-8") as f:
134
+ json.dump(fc, f, ensure_ascii=False, indent=2)
135
+
136
+ print(f"\n✓ Wrote {len(features)} district features to: {out_path}")
137
+ print(f" - From Nominatim: {nominatim_success}")
138
+ print(f" - Bbox fallback: {fallback_used}")
139
+
140
+ if fallback_used > 0:
141
+ print(
142
+ "\nNote: Some districts use approximate bounding boxes. "
143
+ "For precise boundaries, re-run when Nominatim is available "
144
+ "or download a proper Maharashtra district GeoJSON from a trusted source."
145
+ )
146
+
147
+ return 0
148
+
149
+
150
+ if __name__ == "__main__":
151
+ raise SystemExit(main())
src/App.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @license
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import React from 'react';
7
+ import { BrowserRouter, Routes, Route } from 'react-router-dom';
8
+ import { Layout } from './components/Layout';
9
+ import { Dashboard } from './pages/Dashboard';
10
+ import { GisMap } from './pages/GisMap';
11
+ import { Repository } from './pages/Repository';
12
+ import { Chatbot } from './pages/Chatbot';
13
+ import { GrFinder } from './pages/GrFinder';
14
+ import { Faq } from './pages/Faq';
15
+
16
+ export default function App() {
17
+ return (
18
+ <BrowserRouter>
19
+ <Routes>
20
+ <Route path="/" element={<Layout />}>
21
+ <Route index element={<Dashboard />} />
22
+ <Route path="map" element={<GisMap />} />
23
+ <Route path="gr-finder" element={<GrFinder />} />
24
+ <Route path="faq" element={<Faq />} />
25
+ <Route path="repository" element={<Repository />} />
26
+ <Route path="ai-assistant" element={<Chatbot />} />
27
+ </Route>
28
+ </Routes>
29
+ </BrowserRouter>
30
+ );
31
+ }
src/components/Layout.tsx ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Outlet, NavLink, useLocation } from 'react-router-dom';
3
+ import {
4
+ LayoutDashboard,
5
+ Map,
6
+ FileText,
7
+ MessageSquare,
8
+ Settings,
9
+ Bell,
10
+ Search,
11
+ Droplets,
12
+ HelpCircle,
13
+ FileSearch
14
+ } from 'lucide-react';
15
+ import { cn } from '@/src/utils/cn';
16
+ import { Button } from './ui/Button';
17
+ import { Input } from './ui/Input';
18
+
19
+ export function Layout() {
20
+ const location = useLocation();
21
+ const isAIAssistant = location.pathname === '/ai-assistant';
22
+ const isGisMap = location.pathname === '/map';
23
+
24
+ const navItems = [
25
+ { name: 'Dashboard', path: '/', icon: LayoutDashboard },
26
+ { name: 'GIS Map', path: '/map', icon: Map },
27
+ { name: 'GR Finder', path: '/gr-finder', icon: FileSearch },
28
+ { name: 'GR Helper', path: '/ai-assistant', icon: MessageSquare },
29
+ { name: 'FAQ', path: '/faq', icon: HelpCircle },
30
+ { name: 'Repository', path: '/repository', icon: FileText },
31
+ ];
32
+
33
+ return (
34
+ <div className="flex h-screen w-full bg-slate-50 overflow-hidden">
35
+ {/* Desktop Sidebar */}
36
+ <aside className="hidden md:flex w-64 flex-col border-r border-slate-200 bg-white shrink-0">
37
+ <div className="flex h-16 items-center px-6 border-b border-slate-200">
38
+ <Droplets className="h-6 w-6 text-blue-600 mr-2" />
39
+ <span className="text-xl font-bold text-slate-900 tracking-tight">JalDrishti</span>
40
+ </div>
41
+ <nav className="flex-1 space-y-1 px-3 py-4 overflow-y-auto">
42
+ {navItems.map((item) => {
43
+ const Icon = item.icon;
44
+ const isActive = location.pathname === item.path;
45
+ return (
46
+ <NavLink
47
+ key={item.name}
48
+ to={item.path}
49
+ className={cn(
50
+ "flex items-center px-3 py-2.5 text-sm font-medium rounded-md transition-colors",
51
+ isActive
52
+ ? "bg-blue-50 text-blue-700"
53
+ : "text-slate-700 hover:bg-slate-100 hover:text-slate-900"
54
+ )}
55
+ >
56
+ <Icon className={cn("mr-3 h-5 w-5", isActive ? "text-blue-700" : "text-slate-400")} />
57
+ {item.name}
58
+ </NavLink>
59
+ );
60
+ })}
61
+ </nav>
62
+ <div className="p-4 border-t border-slate-200">
63
+ <div className="flex items-center">
64
+ <div className="h-8 w-8 rounded-full bg-blue-100 flex items-center justify-center text-blue-700 font-bold text-xs">
65
+ SD
66
+ </div>
67
+ <div className="ml-3">
68
+ <p className="text-sm font-medium text-slate-900">Swapnali Deshpande</p>
69
+ <p className="text-xs text-slate-500">WRD Main Incharge</p>
70
+ </div>
71
+ </div>
72
+ </div>
73
+ </aside>
74
+
75
+ {/* Main Content */}
76
+ <main className="flex-1 flex flex-col min-w-0 overflow-hidden pb-16 md:pb-0">
77
+ {/* Header */}
78
+ <header className="flex h-14 md:h-16 items-center justify-between border-b border-slate-200 bg-white px-4 sm:px-6 lg:px-8 shrink-0">
79
+ <div className="flex items-center flex-1">
80
+ <div className="md:hidden flex items-center mr-4">
81
+ <Droplets className="h-6 w-6 text-blue-600 mr-2" />
82
+ <span className="text-lg font-bold text-slate-900 tracking-tight">JalDrishti</span>
83
+ </div>
84
+ <div className="w-full max-w-lg hidden sm:flex items-center relative">
85
+ <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-slate-400" />
86
+ <Input
87
+ type="search"
88
+ placeholder="Search GRs, Acts, Circulars..."
89
+ className="w-full bg-slate-50 pl-9 border-none focus-visible:ring-1 focus-visible:ring-blue-500"
90
+ />
91
+ </div>
92
+ </div>
93
+ <div className="flex items-center space-x-2 md:space-x-4">
94
+ <Button variant="ghost" size="icon" className="relative text-slate-500 h-9 w-9">
95
+ <Bell className="h-5 w-5" />
96
+ <span className="absolute top-1.5 right-1.5 h-2 w-2 rounded-full bg-red-500 ring-2 ring-white"></span>
97
+ </Button>
98
+ <Button variant="ghost" size="icon" className="text-slate-500 h-9 w-9 hidden sm:flex">
99
+ <Settings className="h-5 w-5" />
100
+ </Button>
101
+ <div className="h-8 w-8 rounded-full bg-blue-100 flex items-center justify-center text-blue-700 font-bold text-xs md:hidden ml-2">
102
+ SD
103
+ </div>
104
+ </div>
105
+ </header>
106
+
107
+ {/* Page Content */}
108
+ <div
109
+ className={cn(
110
+ 'flex-1 bg-slate-50/50',
111
+ (isAIAssistant || isGisMap) ? 'overflow-hidden p-0 sm:p-0 lg:p-0' : 'overflow-auto p-3 sm:p-6 lg:p-8'
112
+ )}
113
+ >
114
+ <Outlet />
115
+ </div>
116
+ </main>
117
+
118
+ {/* Mobile Bottom Navigation */}
119
+ <nav className="md:hidden fixed bottom-0 left-0 right-0 bg-white border-t border-slate-200 flex justify-around items-center h-16 z-50 pb-safe">
120
+ {navItems.map((item) => {
121
+ const Icon = item.icon;
122
+ const isActive = location.pathname === item.path;
123
+ return (
124
+ <NavLink
125
+ key={item.name}
126
+ to={item.path}
127
+ className={cn(
128
+ "flex flex-col items-center justify-center w-full h-full space-y-1 transition-colors",
129
+ isActive ? "text-blue-600" : "text-slate-500 hover:text-slate-900"
130
+ )}
131
+ >
132
+ <Icon className={cn("h-5 w-5", isActive ? "fill-blue-50/50" : "")} />
133
+ <span className="text-[10px] font-medium">{item.name}</span>
134
+ </NavLink>
135
+ );
136
+ })}
137
+ </nav>
138
+ </div>
139
+ );
140
+ }
src/components/ui/Badge.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cn } from "@/src/utils/cn"
3
+
4
+ export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ variant?: "default" | "secondary" | "destructive" | "outline";
6
+ className?: string;
7
+ children?: React.ReactNode;
8
+ }
9
+
10
+ const Badge = React.forwardRef<HTMLDivElement, BadgeProps>(
11
+ ({ className, variant = "default", ...props }, ref) => {
12
+ return (
13
+ <div
14
+ ref={ref}
15
+ className={cn(
16
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-slate-950 focus:ring-offset-2",
17
+ {
18
+ "border-transparent bg-blue-600 text-white shadow hover:bg-blue-700": variant === "default",
19
+ "border-transparent bg-slate-100 text-slate-900 hover:bg-slate-100/80": variant === "secondary",
20
+ "border-transparent bg-red-500 text-slate-50 shadow hover:bg-red-500/80": variant === "destructive",
21
+ "text-slate-950": variant === "outline",
22
+ },
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ )
28
+ }
29
+ )
30
+ Badge.displayName = "Badge"
31
+
32
+ export { Badge }
src/components/ui/Button.tsx ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cn } from "@/src/utils/cn"
3
+
4
+ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
5
+ variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"
6
+ size?: "default" | "sm" | "lg" | "icon"
7
+ }
8
+
9
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
10
+ ({ className, variant = "default", size = "default", ...props }, ref) => {
11
+ return (
12
+ <button
13
+ className={cn(
14
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-slate-950 disabled:pointer-events-none disabled:opacity-50",
15
+ {
16
+ "bg-blue-600 text-white shadow hover:bg-blue-700": variant === "default",
17
+ "bg-red-500 text-slate-50 shadow-sm hover:bg-red-500/90": variant === "destructive",
18
+ "border border-slate-200 bg-white shadow-sm hover:bg-slate-100 hover:text-slate-900": variant === "outline",
19
+ "bg-slate-100 text-slate-900 shadow-sm hover:bg-slate-100/80": variant === "secondary",
20
+ "hover:bg-slate-100 hover:text-slate-900": variant === "ghost",
21
+ "text-slate-900 underline-offset-4 hover:underline": variant === "link",
22
+ "h-9 px-4 py-2": size === "default",
23
+ "h-8 rounded-md px-3 text-xs": size === "sm",
24
+ "h-10 rounded-md px-8": size === "lg",
25
+ "h-9 w-9": size === "icon",
26
+ },
27
+ className
28
+ )}
29
+ ref={ref}
30
+ {...props}
31
+ />
32
+ )
33
+ }
34
+ )
35
+ Button.displayName = "Button"
36
+
37
+ export { Button }
src/components/ui/Card.tsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cn } from "@/src/utils/cn"
3
+
4
+ const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
5
+ <div ref={ref} className={cn("rounded-xl border border-slate-200 bg-white text-slate-950 shadow-sm", className)} {...props} />
6
+ ))
7
+ Card.displayName = "Card"
8
+
9
+ const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
10
+ <div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
11
+ ))
12
+ CardHeader.displayName = "CardHeader"
13
+
14
+ const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(({ className, ...props }, ref) => (
15
+ <h3 ref={ref} className={cn("font-semibold leading-none tracking-tight", className)} {...props} />
16
+ ))
17
+ CardTitle.displayName = "CardTitle"
18
+
19
+ const CardDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(({ className, ...props }, ref) => (
20
+ <p ref={ref} className={cn("text-sm text-slate-500", className)} {...props} />
21
+ ))
22
+ CardDescription.displayName = "CardDescription"
23
+
24
+ const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
25
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
26
+ ))
27
+ CardContent.displayName = "CardContent"
28
+
29
+ const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
30
+ <div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
31
+ ))
32
+ CardFooter.displayName = "CardFooter"
33
+
34
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
src/components/ui/Input.tsx ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cn } from "@/src/utils/cn"
3
+
4
+ export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
5
+
6
+ const Input = React.forwardRef<HTMLInputElement, InputProps>(
7
+ ({ className, type, ...props }, ref) => {
8
+ return (
9
+ <input
10
+ type={type}
11
+ className={cn(
12
+ "flex h-9 w-full rounded-md border border-slate-200 bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-blue-600 disabled:cursor-not-allowed disabled:opacity-50",
13
+ className
14
+ )}
15
+ ref={ref}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+ )
21
+ Input.displayName = "Input"
22
+
23
+ export { Input }
src/data/mockFAQs.ts ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Mock FAQ data for WRD demo
3
+ */
4
+
5
+ export interface FAQItem {
6
+ id: string;
7
+ question: string;
8
+ answer: string;
9
+ category: 'Flood Management' | 'Dam Operations' | 'Irrigation & Water Rights' | 'Compensation & Relief' | 'GRs & Acts';
10
+ relatedGRs?: string[];
11
+ keywords: string[];
12
+ }
13
+
14
+ export const faqCategories = [
15
+ { id: 'flood', name: 'Flood Management', icon: 'Waves' },
16
+ { id: 'dam', name: 'Dam Operations', icon: 'Database' },
17
+ { id: 'irrigation', name: 'Irrigation & Water Rights', icon: 'Droplets' },
18
+ { id: 'compensation', name: 'Compensation & Relief', icon: 'IndianRupee' },
19
+ { id: 'grs', name: 'GRs & Acts', icon: 'FileText' },
20
+ ] as const;
21
+
22
+ export const faqData: FAQItem[] = [
23
+ // Flood Management
24
+ {
25
+ id: 'faq-flood-1',
26
+ question: 'What should I do when a flood warning is issued for my area?',
27
+ answer: 'When a flood warning is issued:\n\n1. **Immediate Actions:** Move to higher ground immediately if told to evacuate. Do not wait for instructions.\n\n2. **Essential Items:** Carry important documents (ID proof, property papers), medicines, and emergency supplies.\n\n3. **Avoid:** Do not walk through moving water (6 inches can make you fall). Do not drive through flooded roads.\n\n4. **Stay Informed:** Listen to local radio/TV for updates. Follow WRD official alerts.\n\n5. **After Flood:** Do not return home until authorities declare it safe. Watch for contaminated water and electrical hazards.\n\nRefer to GR-2023-WRD-156 for complete evacuation protocols.',
28
+ category: 'Flood Management',
29
+ relatedGRs: ['GR-2023-WRD-156', 'GR-2024-WRD-056'],
30
+ keywords: ['flood warning', 'evacuation', 'safety', 'emergency']
31
+ },
32
+ {
33
+ id: 'faq-flood-2',
34
+ question: 'How are flood warning levels classified?',
35
+ answer: 'Flood warning levels in Maharashtra are classified as:\n\n**Level 1 - Normal:** River flowing within normal limits. No action required.\n\n**Level 2 - Alert:** River rising but below danger level. Stay informed, prepare for evacuation.\n\n**Level 3 - Warning:** River at danger level. Evacuation may be ordered. Move valuables to higher floors.\n\n**Level 4 - Critical:** River above danger level. Mandatory evacuation. Emergency response activated.\n\nThese levels are monitored by WRD control rooms and communicated via SMS alerts, sirens, and local officials.',
36
+ category: 'Flood Management',
37
+ relatedGRs: ['GR-2023-WRD-156'],
38
+ keywords: ['warning levels', 'classification', 'danger level', 'alert']
39
+ },
40
+ {
41
+ id: 'faq-flood-3',
42
+ question: 'Who issues flood evacuation orders?',
43
+ answer: 'Flood evacuation orders are issued by:\n\n1. **District Collector:** Primary authority for evacuation orders in the district.\n\n2. **Tahsildar:** Issues orders at taluka level on Collector\'s direction.\n\n3. **WRD Chief Engineer:** Can recommend evacuation when dam discharge exceeds safe levels.\n\n4. **State Disaster Management Authority:** Issues orders for large-scale emergencies.\n\nThe order is communicated through:\n- SMS alerts to registered mobile numbers\n- Sirens and public announcements\n- Local police and revenue officials\n- Radio and TV broadcasts',
44
+ category: 'Flood Management',
45
+ relatedGRs: ['GR-2023-WRD-156'],
46
+ keywords: ['evacuation order', 'collector', 'authority', 'disaster management']
47
+ },
48
+ {
49
+ id: 'faq-flood-4',
50
+ question: 'What is the early warning system for floods in Konkan region?',
51
+ answer: 'The Konkan Flood Early Warning System includes:\n\n**Monitoring Network:**\n- 45 automated rainfall monitoring stations\n- 28 river level sensors\n- 12 tidal gauges along the coast\n\n**Alert Mechanism:**\n- SMS alerts sent 6-12 hours in advance\n- Color-coded warnings (Yellow/Orange/Red)\n- Community radio announcements\n\n**Communication:**\n- Village-level volunteers with megaphones\n- Temple/mosque loudspeakers\n- WhatsApp groups managed by Gram Panchayat\n\nThis system was implemented under GR-2024-WRD-056 and covers all 7 coastal districts.',
52
+ category: 'Flood Management',
53
+ relatedGRs: ['GR-2024-WRD-056'],
54
+ keywords: ['early warning', 'Konkan', 'SMS', 'monitoring', 'alert']
55
+ },
56
+ {
57
+ id: 'faq-flood-5',
58
+ question: 'How can I check current flood status in my district?',
59
+ answer: 'You can check flood status through:\n\n**Online:**\n- JalDrishti Dashboard (this portal) - Real-time dam levels and flood zones\n- wrd.maharashtra.gov.in - Official WRD portal\n- IMD website - Rainfall predictions\n\n**Mobile:**\n- Download "MahaFlood" app (Android/iOS)\n- SMS "FLOOD <District>" to 9223456789\n\n**Offline:**\n- Call District Control Room (available 24x7)\n- Contact local Tahsildar office\n- Listen to All India Radio regional broadcasts\n\nThe GIS Map on this portal shows live flood zones and affected areas.',
60
+ category: 'Flood Management',
61
+ keywords: ['flood status', 'check', 'online', 'mobile app', 'SMS']
62
+ },
63
+
64
+ // Dam Operations
65
+ {
66
+ id: 'faq-dam-1',
67
+ question: 'What happens when a dam reaches 90% capacity?',
68
+ answer: 'When a dam reaches 90% capacity:\n\n**Immediate Actions:**\n1. Control room staff increased to 24x7 monitoring\n2. Downstream districts notified (minimum 2 hours advance)\n3. Gate operation team placed on standby\n\n**Gate Operations:**\n- Gradual release begins at 10% gate opening\n- Incremental increase every 15 minutes\n- Maximum safe discharge calculated based on downstream capacity\n\n**Public Communication:**\n- Media briefing by Chief Engineer\n- SMS alerts to downstream villages\n- Siren activation in danger zones\n\n**Emergency Protocol:** If level exceeds 95%, Chief Engineer can authorize immediate release without waiting for full notification period.\n\nSee GR-2024-WRD-056 and GR-2023-WRD-124 for complete protocols.',
69
+ category: 'Dam Operations',
70
+ relatedGRs: ['GR-2024-WRD-056', 'GR-2023-WRD-124'],
71
+ keywords: ['dam capacity', '90%', 'gate operation', 'discharge', 'emergency']
72
+ },
73
+ {
74
+ id: 'faq-dam-2',
75
+ question: 'How often are dams inspected?',
76
+ answer: 'Dam inspection frequency as per GR-2022-WRD-112:\n\n**Daily Inspection (Monsoon Season - June to October):**\n- Visual inspection of dam structure\n- Water level and inflow measurement\n- Seepage monitoring\n- Gate mechanism check\n\n**Weekly Inspection (Non-Monsoon):**\n- Structural assessment\n- Instrumentation reading\n- Downstream condition check\n\n**Annual Comprehensive Inspection (Before Monsoon - May):**\n- Full structural integrity assessment\n- Seismic risk evaluation\n- Emergency action plan review\n- Gate testing under load\n\n**Special Inspections:**\n- After any seismic event >4.0 magnitude\n- After extreme flood event\n- When ordered by Dam Safety Review Committee\n\nAll inspections must be logged in the DAMS (Dam Asset Management System) within 24 hours.',
77
+ category: 'Dam Operations',
78
+ relatedGRs: ['GR-2022-WRD-112'],
79
+ keywords: ['inspection', 'frequency', 'monsoon', 'annual', 'seismic']
80
+ },
81
+ {
82
+ id: 'faq-dam-3',
83
+ question: 'What is the procedure for dam gate opening?',
84
+ answer: 'Dam gate opening procedure as per GR-2023-WRD-124:\n\n**Pre-Opening Checklist:**\n✓ Seismic sensors calibrated\n✓ Downstream flow sensors active\n✓ Alert systems tested\n✓ Control room staffed (minimum 2 engineers)\n✓ Emergency backup power available\n\n**Notification Timeline:**\n- T-2 hours: Alert downstream district collectors\n- T-1 hour: Public announcement via media\n- T-30 min: Siren activation\n\n**Opening Sequence:**\n1. Start with 10% gate opening\n2. Wait 15 minutes, observe downstream flow\n3. Increase by 10% increments\n4. Maximum 50% opening per hour\n\n**Emergency Override:**\nChief Engineer can authorize immediate release if dam level exceeds 95% or structural risk detected.\n\n**Documentation:** All operations logged in DAMS within 1 hour of completion.',
85
+ category: 'Dam Operations',
86
+ relatedGRs: ['GR-2023-WRD-124'],
87
+ keywords: ['gate opening', 'procedure', 'notification', 'SOP', 'discharge']
88
+ },
89
+ {
90
+ id: 'faq-dam-4',
91
+ question: 'Which dams in Maharashtra are classified as critical?',
92
+ answer: 'Currently, dams are classified as critical based on:\n\n**Critical Status Criteria:**\n- Storage level >90% AND\n- High inflow rate (>10,000 cusecs) OR\n- Downstream areas at flood risk\n\n**Currently Critical Dams (as of latest update):**\n1. **Koyna Dam, Satara** - 92.4% capacity, 45,000 cusecs inflow\n2. **Radhanagari Dam, Kolhapur** - 95% capacity, 25,000 cusecs inflow\n\n**Warning Status Dams:**\n1. Ujani Dam, Solapur - 85%\n2. Ghatghar Dam, Thane - 88%\n\n**Historically Critical Dams:**\n- Jayakwadi (Aurangabad) - Flood 2019\n- Tillari (Sindhudurg) - Flood 2021\n\nStatus changes are updated every 3 hours during monsoon. Check the GIS Map for real-time status.',
93
+ category: 'Dam Operations',
94
+ keywords: ['critical dams', 'Koyna', 'Radhanagari', 'status', 'monitoring']
95
+ },
96
+ {
97
+ id: 'faq-dam-5',
98
+ question: 'What safety measures are in place for dam failure prevention?',
99
+ answer: 'Dam safety measures include:\n\n**Structural Monitoring:**\n- Instrumentation: Piezometers, inclinometers, settlement gauges\n- Seismic sensors: Real-time earthquake detection\n- Drone surveys: Annual structural imaging\n\n**Operational Safety:**\n- Rule curve compliance: Mandatory storage limits by date\n- Regular gate testing: Monthly during non-monsoon\n- Emergency action plans: Updated annually\n\n**Regulatory Framework:**\n- Dam Safety Bill 2019 compliance\n- Annual safety audit (GR-2022-WRD-067)\n- Dam Safety Review Committee inspections\n\n**Emergency Preparedness:**\n- Downstream evacuation plans\n- Early warning systems\n- Mock drills every 2 years\n\n**Insurance:** All major dams covered under Dam Safety Insurance Scheme.',
100
+ category: 'Dam Operations',
101
+ relatedGRs: ['GR-2022-WRD-067', 'GR-2022-WRD-112'],
102
+ keywords: ['safety', 'failure', 'prevention', 'monitoring', 'insurance']
103
+ },
104
+
105
+ // Irrigation & Water Rights
106
+ {
107
+ id: 'faq-irrigation-1',
108
+ question: 'What is the water distribution priority in Maharashtra?',
109
+ answer: 'Water distribution priority as per Maharashtra Irrigation Act and GR-2024-WRD-042:\n\n**Priority Order:**\n1. **Drinking Water** - Highest priority for municipal supply\n2. **Agriculture/Irrigation** - Second priority for crop cultivation\n3. **Industrial Use** - Third priority for factories and processing\n4. **Recreation/Other** - Lowest priority\n\n**During Scarcity (Drought):**\n- Industrial supply reduced by 50%\n- Irrigation limited to protective irrigation (life-saving water)\n- Drinking water supply maintained at all costs\n\n**Real-time Adjustments:**\n- WRD can modify allocations based on storage levels\n- Water User Associations informed 48 hours in advance\n- Grievance redressal through digital portal\n\nViolations: Unauthorized diversion punishable with up to 3 years imprisonment.',
110
+ category: 'Irrigation & Water Rights',
111
+ relatedGRs: ['GR-2024-WRD-042'],
112
+ keywords: ['priority', 'drinking water', 'agriculture', 'industrial', 'scarcity']
113
+ },
114
+ {
115
+ id: 'faq-irrigation-2',
116
+ question: 'How do I form a Water User Association (WUA)?',
117
+ answer: 'Water User Association formation process as per GR-2023-WRD-078:\n\n**Eligibility:**\n- Minimum 50 farmer members\n- Command area of 100-500 hectares\n- All members must be landowners/tenants in the area\n\n**Formation Steps:**\n1. **Application:** Submit to Executive Engineer, Irrigation Division\n2. **Verification:** Revenue records checked (30 days)\n3. **Registration:** Society registration under Maharashtra Societies Act\n4. **Election:** Managing committee elected (7 members)\n5. **Recognition:** WRD issues recognition certificate\n\n**WUA Responsibilities:**\n- Water distribution within command area\n- Minor canal maintenance\n- Collection of water cess (5%)\n- Dispute resolution among members\n\n**Benefits:**\n- Direct water allocation from WRD\n- 10% discount on water cess\n- Priority in water allocation during scarcity\n\nApplication form available at wrd.maharashtra.gov.in/wua',
118
+ category: 'Irrigation & Water Rights',
119
+ relatedGRs: ['GR-2023-WRD-078'],
120
+ keywords: ['WUA', 'water user association', 'formation', 'farmer', 'registration']
121
+ },
122
+ {
123
+ id: 'faq-irrigation-3',
124
+ question: 'What are the rights of a Tahsildar regarding water disputes?',
125
+ answer: 'Tahsildar\'s water-related powers as per GR-2023-WRD-089:\n\n**Dispute Resolution:**\n- Resolve disputes between farmers for canal water\n- Mediate conflicts between WUAs\n- Order temporary water allocation during scarcity\n\n**Administrative Powers:**\n- Issue NOC for borewell/tubewell\n- Grant temporary water rights (up to 30 days)\n- Impose penalties for unauthorized water use (up to ₹10,000)\n\n**Emergency Powers:**\n- Order water tanker supply during drought\n- Requisition private wells for drinking water\n- Coordinate with WRD for emergency releases\n\n**Limitations:**\n- Cannot modify permanent water rights\n- Cannot override WRD allocation decisions\n- Appeals lie with Sub-Divisional Officer\n\nFor grievances, use: wrd.maharashtra.gov.in/grievance',
126
+ category: 'Irrigation & Water Rights',
127
+ relatedGRs: ['GR-2023-WRD-089'],
128
+ keywords: ['tahsildar', 'powers', 'dispute', 'water rights', 'grievance']
129
+ },
130
+ {
131
+ id: 'faq-irrigation-4',
132
+ question: 'How is water cess calculated and collected?',
133
+ answer: 'Water cess calculation and collection:\n\n**Cess Rates (per hectare per season):**\n- Kharif (Monsoon crop): ₹150\n- Rabi (Winter crop): ₹250\n- Perennial (Sugarcane, banana): ₹600\n\n**Collection Process:**\n1. Assessment by Irrigation Department based on cropped area\n2. Bill issued to farmer/WUA\n3. Payment at Talathi office or online via wrd.maharashtra.gov.in\n4. Receipt mandatory for next season\'s water supply\n\n**Concessions:**\n- 10% discount for WUA members\n- 50% concession for SC/ST farmers\n- Full waiver for crop failure due to natural calamity\n\n**Usage of Cess:**\n- 5% retained for local maintenance fund\n- 95% deposited to WRD consolidated fund\n\nDefaulters: Water supply disconnected; penalty of 25% on arrears.',
134
+ category: 'Irrigation & Water Rights',
135
+ keywords: ['water cess', 'calculation', 'collection', 'rates', 'payment']
136
+ },
137
+ {
138
+ id: 'faq-irrigation-5',
139
+ question: 'Can I use groundwater for irrigation without permission?',
140
+ answer: 'Groundwater extraction rules as per GR-2023-WRD-045:\n\n**Registration Required:**\n- All tubewells/borewells >60m depth\n- Extraction >100 cubic meters/day\n- Commercial/industrial use\n\n**NOC Requirements:**\n- NOC from Tahsildar for wells >60m\n- NOC from Groundwater Authority for >200m\n- Environmental clearance for >500m\n\n**Exemptions (No Permission Needed):**\n- Hand pumps (any depth)\n- Wells <60m for personal use\n- Extraction <50 cubic meters/day for domestic use\n\n**Penalties:**\n- Unauthorized extraction: ₹10,000 fine + well sealing\n- Over-extraction: ₹5,000 per 100 cubic meters\n- Repeated violation: Criminal prosecution\n\n**Apply Online:** groundwater.maharashtra.gov.in\n\nNote: Over-exploited blocks (marked red) have stricter controls.',
141
+ category: 'Irrigation & Water Rights',
142
+ relatedGRs: ['GR-2023-WRD-045'],
143
+ keywords: ['groundwater', 'permission', 'NOC', 'irrigation', 'penalty']
144
+ },
145
+
146
+ // Compensation & Relief
147
+ {
148
+ id: 'faq-compensation-1',
149
+ question: 'What compensation is available for flood damage?',
150
+ answer: 'Flood relief compensation under GR-2024-WRD-089:\n\n**Crop Loss:**\n- ₹5,000 per acre (maximum 10 acres)\n- Covers all crops including horticulture\n- Assessment by joint team (Revenue + Agriculture)\n\n**House Damage:**\n- Fully damaged: ₹95,000\n- Partially damaged: ₹50,000\n- Hut/temporary structure: ₹25,000\n\n**Livestock Loss:**\n- Large animals (cow, buffalo): ₹30,000\n- Small animals (goat, sheep): ₹4,000\n- Maximum 5 animals per household\n\n**Eligibility:**\n- Residence in notified flood-affected area\n- Damage during notified period (July 1 - October 31, 2024)\n- Registered with local Tahsildar\n\n**Application Process:**\n1. Submit Form WRD-FLOOD-01 to Tahsildar\n2. Attach damage assessment report\n3. Processing time: 30 days\n4. Payment via direct bank transfer',
151
+ category: 'Compensation & Relief',
152
+ relatedGRs: ['GR-2024-WRD-089'],
153
+ keywords: ['compensation', 'flood', 'crop loss', 'house damage', 'livestock']
154
+ },
155
+ {
156
+ id: 'faq-compensation-2',
157
+ question: 'How do I apply for flood compensation?',
158
+ answer: 'Flood compensation application process:\n\n**Step 1: Report Damage (Within 7 Days)**\n- Inform Gram Panchayat/Talathi\n- Get damage report number\n\n**Step 2: Document Damage**\n- Take photos/videos of damage\n- Preserve damaged items for inspection\n- Get witness statements\n\n**Step 3: Submit Application**\n- Form: WRD-FLOOD-01 (available at Tahsil office)\n- Documents: Aadhaar, bank passbook, land records, damage photos\n- Submit to: Tahsildar office\n\n**Step 4: Inspection**\n- Joint team visits within 15 days\n- Assessment report prepared\n- You get inspection copy\n\n**Step 5: Approval & Payment**\n- Tahsildar approves within 7 days\n- Payment within 30 days\n- Direct bank transfer\n\n**Track Application:**\n- SMS "FLOOD <Application ID>" to 9223456789\n- Online: wrd.maharashtra.gov.in/relief\n\n**Appeal:** If rejected, appeal to SDO within 30 days.',
159
+ category: 'Compensation & Relief',
160
+ relatedGRs: ['GR-2024-WRD-089'],
161
+ keywords: ['apply', 'compensation', 'process', 'documents', 'tracking']
162
+ },
163
+ {
164
+ id: 'faq-compensation-3',
165
+ question: 'What drought relief is available for farmers?',
166
+ answer: 'Drought relief package (GR-2024-WRD-078 for Vidarbha, similar for other regions):\n\n**Drinking Water Supply:**\n- Free tanker water supply\n- Borewell drilling at government cost\n- Pipeline extension to uncovered villages\n\n**Agricultural Support:**\n- Crop loss compensation: ₹6,800/ha (same as flood)\n- Input subsidy: ₹2,000/ha for re-sowing\n- Seed distribution: Free drought-resistant seeds\n\n**Employment:**\n- MGNREGA works extended (150 days instead of 100)\n- Priority for water conservation works\n- Fodder camp employment\n\n**Livestock Support:**\n- Fodder camps (free fodder + water)\n- Cattle camps with veterinary care\n- Subsidized cattle feed\n\n**Financial:**\n- Crop loan waiver for affected farmers\n- Fresh crop loans at 0% interest\n- Electricity bill waiver for agricultural pumps\n\n**Application:** Through Gram Panchayat to District Collector.',
167
+ category: 'Compensation & Relief',
168
+ relatedGRs: ['GR-2024-WRD-078'],
169
+ keywords: ['drought', 'relief', 'farmer', 'employment', 'MGNREGA']
170
+ },
171
+ {
172
+ id: 'faq-compensation-4',
173
+ question: 'How long does it take to receive compensation?',
174
+ answer: 'Compensation processing timelines:\n\n**Standard Timeline (30 Days):**\n- Application submission to payment: 30 days\n- Breakdown:\n - Inspection: 15 days\n - Approval: 7 days\n - Payment processing: 8 days\n\n**Fast-Track Cases (15 Days):**\n- Death compensation\n- Complete house collapse\n- Large-scale crop failure (>50% village)\n\n**Delayed Cases:**\n- Disputed land ownership: Additional 30 days\n- Multiple claimants: Additional 15 days\n- Bank account issues: Resolved within 7 days\n\n**Tracking:**\n- SMS updates at each stage\n- Online portal shows current status\n- Helpline: 1800-XXX-XXXX\n\n**Complaints:**\n- Delay beyond 30 days: Complain to SDO\n- No response in 7 days: Escalate to Collector\n- Grievance portal: wrd.maharashtra.gov.in/grievance\n\n**Interest:** If delayed beyond 45 days, 6% interest payable.',
175
+ category: 'Compensation & Relief',
176
+ keywords: ['timeline', 'processing', 'days', 'tracking', 'delay']
177
+ },
178
+ {
179
+ id: 'faq-compensation-5',
180
+ question: 'What documents are needed for compensation claim?',
181
+ answer: 'Required documents for compensation claim:\n\n**Identity & Address:**\n- Aadhaar card (mandatory)\n- Ration card\n- Voter ID (if Aadhaar not available)\n\n**Land Documents:**\n- 7/12 extract (Satbara)\n- 8A extract\n- Land ownership proof\n\n**Bank Details:**\n- Bank passbook copy (first page)\n- Account must be Aadhaar-linked\n- Cancelled cheque (optional)\n\n**Damage Evidence:**\n- Photos of damage (minimum 5)\n- Video if available\n- Witness statements (2 witnesses)\n\n**Additional for Specific Claims:**\n- Crop loss: Girdawari report\n- House damage: Structural assessment report\n- Livestock: Veterinary certificate, ear tag number\n\n**Self-Declaration:**\n- Affidavit stating damage details\n- Declaration of no previous claim\n- Signature verification\n\n**Submission:**\n- Originals for verification\n- Self-attested copies retained\n- Online upload option available',
182
+ category: 'Compensation & Relief',
183
+ keywords: ['documents', 'required', 'Aadhaar', 'bank', 'evidence']
184
+ },
185
+
186
+ // GRs & Acts
187
+ {
188
+ id: 'faq-grs-1',
189
+ question: 'What is the difference between a GR, Act, and Circular?',
190
+ answer: 'Government document types explained:\n\n**Government Resolution (GR):**\n- Executive order by government department\n- Implements policy decisions\n- Can be amended or superseded by later GR\n- Example: GR-2024-WRD-089 (Flood compensation)\n\n**Act (Law):**\n- Passed by State Legislature\n- Creates legal framework\n- Cannot be changed by GR (only amendment by legislature)\n- Example: Maharashtra Irrigation Act, 1976\n\n**Circular:**\n- Administrative instruction\n- Clarifies implementation of GR/Act\n- Internal departmental guidance\n- Example: Monsoon preparedness circular\n\n**Hierarchy:**\nAct > GR > Circular\n\n**Legal Force:**\n- Act: Full legal force, courts enforce\n- GR: Administrative binding, can be challenged\n- Circular: Internal guidance, not legally binding\n\n**Finding Documents:** Use the GR Finder on this portal to search all types.',
191
+ category: 'GRs & Acts',
192
+ keywords: ['GR', 'Act', 'Circular', 'difference', 'types']
193
+ },
194
+ {
195
+ id: 'faq-grs-2',
196
+ question: 'How do I find a specific Government Resolution?',
197
+ answer: 'Ways to find a Government Resolution:\n\n**On This Portal:**\n1. Go to "GR Finder" from menu\n2. Search by GR number (e.g., "GR-2024-WRD-089")\n3. Or search by keywords (e.g., "flood compensation")\n4. Filter by year, department, category\n\n**Official Sources:**\n- wrd.maharashtra.gov.in → Documents section\n- maharashtra.gov.in → GR search\n- Mantralaya library (in-person)\n\n**GR Number Format:**\nGR-YYYY-DEPT-NUMBER\nExample: GR-2024-WRD-089\n- 2024: Year\n- WRD: Water Resources Department\n- 089: Serial number\n\n**Tips:**\n- Recent GRs are digitized (2015 onwards)\n- Older GRs may need RTI request\n- Popular GRs shown in GR Finder homepage\n\n**Need Help?** Use "GR Helper" AI assistant to find related GRs.',
198
+ category: 'GRs & Acts',
199
+ keywords: ['find', 'search', 'GR number', 'portal', 'official']
200
+ },
201
+ {
202
+ id: 'faq-grs-3',
203
+ question: 'What are the key Acts governing water resources in Maharashtra?',
204
+ answer: 'Major water resource Acts in Maharashtra:\n\n**Maharashtra Irrigation Act, 1976**\n- Governs irrigation water distribution\n- Defines WRD powers and responsibilities\n- Establishes water rights framework\n- Key sections: 33 (water priority), 45 (maintenance)\n\n**MWRRA Act, 2005**\n- Created Maharashtra Water Resources Regulatory Authority\n- Water pricing and tariff determination\n- Inter-basin water transfer approval\n- Annual water audit requirement\n\n**Maharashtra Groundwater Act, 2013**\n- Groundwater extraction regulation\n- Registration of wells\n- Protection of drinking water sources\n\n**Dam Safety Act, 2021 (Central)**\n- Dam classification and inspection\n- Emergency action plans\n- Safety review committees\n\n**Related Acts:**\n- Maharashtra Water Supply & Sewerage Act, 1976\n- Maharashtra Farmers\' Participation Act, 2005\n\nAll available in Repository section of this portal.',
205
+ category: 'GRs & Acts',
206
+ keywords: ['Acts', 'Irrigation Act', 'MWRRA', 'Groundwater', 'Dam Safety']
207
+ },
208
+ {
209
+ id: 'faq-grs-4',
210
+ question: 'How can I get a copy of an old GR?',
211
+ answer: 'Methods to obtain old Government Resolutions:\n\n**Online (2015 onwards):**\n- wrd.maharashtra.gov.in → Documents\n- This portal\'s GR Finder\n- Download PDF directly\n\n**RTI Application (Pre-2015):**\n- Apply to WRD Public Information Officer\n- Fee: ₹10 per page\n- Timeline: 30 days\n- Format: RTI application with GR details\n\n**Mantralaya Library:**\n- Visit: Mantralaya, Mumbai\n- Working hours: 10 AM - 5 PM\n- Reference section has GR archives\n- Photocopy available (₹2/page)\n\n**District Collector Office:**\n- Regional GR archives\n- Available in Marathi and English\n- Prior appointment recommended\n\n**GR Helper AI:**\n- Ask "Find GR about [topic] from [year]"\n- AI will search and provide summary\n- Link to download if digitized\n\n**Note:** Some sensitive GRs may have restricted access.',
212
+ category: 'GRs & Acts',
213
+ keywords: ['old GR', 'copy', 'RTI', 'archive', 'download']
214
+ },
215
+ {
216
+ id: 'faq-grs-5',
217
+ question: 'How do I know if a GR is still active or superseded?',
218
+ answer: 'Checking GR status:\n\n**On This Portal:**\n- GR Finder shows status badge: Active/Amended/Superseded\n- Click GR to see full history\n- Related GRs section shows amendments\n\n**Status Types:**\n- **Active:** Currently in force, follow this\n- **Amended:** Modified by later GR, check both\n- **Superseded:** Replaced by new GR, follow new one\n\n**Official Verification:**\n- WRD website shows current status\n- Contact department for clarification\n- Legal cell can confirm validity\n\n**Common Reasons for Supersession:**\n- Policy change by new government\n- Court order striking down provisions\n- Expiry of time-bound GR\n- Amendment incorporating new provisions\n\n**Example:**\nGR-2022-WRD-089 (Crop compensation) was superseded by GR-2024-WRD-089 with revised rates.\n\n**Best Practice:** Always check the latest GR on the subject before taking action.',
219
+ category: 'GRs & Acts',
220
+ keywords: ['status', 'active', 'superseded', 'amended', 'validity']
221
+ },
222
+ ];
223
+
224
+ // Helper functions
225
+ export const getFAQsByCategory = (category: string) => faqData.filter(faq => faq.category === category);
226
+ export const searchFAQs = (query: string) => {
227
+ const q = query.toLowerCase();
228
+ return faqData.filter(faq =>
229
+ faq.question.toLowerCase().includes(q) ||
230
+ faq.answer.toLowerCase().includes(q) ||
231
+ faq.keywords.some(k => k.includes(q))
232
+ );
233
+ };
src/data/mockGRs.ts ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Mock Government Resolutions (GRs) data for WRD demo
3
+ */
4
+
5
+ export interface GRDocument {
6
+ id: string;
7
+ title: string;
8
+ department: string;
9
+ date: string;
10
+ year: number;
11
+ category: 'Flood Management' | 'Dam Safety' | 'Irrigation' | 'Compensation' | 'Water Policy' | 'Administrative';
12
+ status: 'Active' | 'Superseded' | 'Amended';
13
+ summary: string;
14
+ keywords: string[];
15
+ relatedGRs?: string[];
16
+ popularQuery?: boolean;
17
+ }
18
+
19
+ export const mockGRs: GRDocument[] = [
20
+ {
21
+ id: 'GR-2024-WRD-089',
22
+ title: 'Flood Relief Compensation Guidelines 2024',
23
+ department: 'Relief & Rehabilitation',
24
+ date: '2024-07-15',
25
+ year: 2024,
26
+ category: 'Compensation',
27
+ status: 'Active',
28
+ summary: 'Comprehensive guidelines for flood relief compensation including crop loss (₹5,000/acre), house damage (₹95,000 full), and livestock loss. Covers eligibility criteria, application process through Tahsildar, and direct bank transfer mechanism.',
29
+ keywords: ['compensation', 'flood relief', 'crop loss', 'house damage', 'tahsildar'],
30
+ popularQuery: true
31
+ },
32
+ {
33
+ id: 'GR-2024-WRD-056',
34
+ title: 'Emergency Dam Safety Protocol',
35
+ department: 'Dam Safety',
36
+ date: '2024-06-20',
37
+ year: 2024,
38
+ category: 'Dam Safety',
39
+ status: 'Active',
40
+ summary: 'Mandatory protocols for dam safety during monsoon including 2-hour advance notification for downstream districts, seismic monitoring requirements, and emergency discharge procedures.',
41
+ keywords: ['dam safety', 'emergency', 'discharge', 'monsoon', 'notification'],
42
+ relatedGRs: ['GR-2023-WRD-124'],
43
+ popularQuery: true
44
+ },
45
+ {
46
+ id: 'GR-2024-WRD-042',
47
+ title: 'Water Resource Allocation Policy',
48
+ department: 'Water Resources Planning',
49
+ date: '2024-04-20',
50
+ year: 2024,
51
+ category: 'Water Policy',
52
+ status: 'Active',
53
+ summary: 'Defines water allocation priority: Drinking water > Agriculture > Industrial use. Includes provisions for real-time allocation adjustments during scarcity periods.',
54
+ keywords: ['water allocation', 'priority', 'drinking water', 'agriculture', 'scarcity'],
55
+ popularQuery: true
56
+ },
57
+ {
58
+ id: 'GR-2023-WRD-124',
59
+ title: 'Dam Gate Operations Standard Operating Procedure',
60
+ department: 'Operations',
61
+ date: '2023-08-10',
62
+ year: 2023,
63
+ category: 'Dam Safety',
64
+ status: 'Active',
65
+ summary: 'Detailed SOP for dam gate operations including pre-opening checklist, gradual opening procedure (10% increments every 15 minutes), downstream notification requirements, and emergency override provisions.',
66
+ keywords: ['gate operation', 'SOP', 'dam gate', 'discharge', 'downstream'],
67
+ relatedGRs: ['GR-2024-WRD-056'],
68
+ popularQuery: true
69
+ },
70
+ {
71
+ id: 'GR-2023-WRD-098',
72
+ title: 'Canal Maintenance Guidelines',
73
+ department: 'Irrigation',
74
+ date: '2023-07-25',
75
+ year: 2023,
76
+ category: 'Irrigation',
77
+ status: 'Active',
78
+ summary: 'Guidelines for canal maintenance including responsibilities of WRD (major repairs >₹1 lakh) and Water User Associations (minor maintenance). Includes 5% water cess provision for maintenance fund.',
79
+ keywords: ['canal', 'maintenance', 'WUA', 'repair', 'water cess'],
80
+ },
81
+ {
82
+ id: 'GR-2022-WRD-089',
83
+ title: 'Crop Damage Compensation Policy',
84
+ department: 'Revenue & Forest',
85
+ date: '2022-09-22',
86
+ year: 2022,
87
+ category: 'Compensation',
88
+ status: 'Amended',
89
+ summary: 'Revised compensation rates and assessment methodology for agricultural losses caused by natural calamities. Superseded by GR-2024-WRD-089.',
90
+ keywords: ['crop damage', 'compensation', 'agriculture', 'natural calamity'],
91
+ relatedGRs: ['GR-2024-WRD-089']
92
+ },
93
+ {
94
+ id: 'GR-2024-WRD-023',
95
+ title: 'Monsoon Preparedness Circular',
96
+ department: 'Operations',
97
+ date: '2024-05-01',
98
+ year: 2024,
99
+ category: 'Flood Management',
100
+ status: 'Active',
101
+ summary: 'Pre-monsoon checklist for all dam control rooms including sensor calibration, alert system testing, control room staffing requirements (min 2 engineers), and coordination protocols.',
102
+ keywords: ['monsoon', 'preparedness', 'control room', 'sensor', 'alert'],
103
+ },
104
+ {
105
+ id: 'GR-2024-WRD-067',
106
+ title: 'Real-time Monitoring Guidelines',
107
+ department: 'IT & Operations',
108
+ date: '2024-04-10',
109
+ year: 2024,
110
+ category: 'Dam Safety',
111
+ status: 'Active',
112
+ summary: 'IoT sensor deployment guidelines for live dam monitoring including water level sensors, flow meters, seismic monitors, and data transmission protocols.',
113
+ keywords: ['IoT', 'monitoring', 'sensor', 'real-time', 'data'],
114
+ },
115
+ {
116
+ id: 'GR-2023-WRD-156',
117
+ title: 'Flood Evacuation Standard Operating Procedure',
118
+ department: 'Disaster Management',
119
+ date: '2023-06-15',
120
+ year: 2023,
121
+ category: 'Flood Management',
122
+ status: 'Active',
123
+ summary: 'Comprehensive evacuation protocols for flood-affected areas including warning levels, evacuation zones, relief camp setup, and inter-departmental coordination.',
124
+ keywords: ['evacuation', 'flood', 'relief camp', 'warning', 'coordination'],
125
+ popularQuery: true
126
+ },
127
+ {
128
+ id: 'GR-2023-WRD-078',
129
+ title: 'Water User Association Formation Guidelines',
130
+ department: 'Irrigation',
131
+ date: '2023-03-20',
132
+ year: 2023,
133
+ category: 'Irrigation',
134
+ status: 'Active',
135
+ summary: 'Guidelines for forming Water User Associations including membership criteria, election procedures, responsibilities, and water distribution management.',
136
+ keywords: ['WUA', 'water user association', 'formation', 'irrigation'],
137
+ },
138
+ {
139
+ id: 'GR-2022-WRD-112',
140
+ title: 'Dam Inspection Frequency Requirements',
141
+ department: 'Dam Safety',
142
+ date: '2022-12-05',
143
+ year: 2022,
144
+ category: 'Dam Safety',
145
+ status: 'Active',
146
+ summary: 'Mandatory inspection schedules for dams: Daily during monsoon, Weekly during normal period, Comprehensive annual inspection before monsoon.',
147
+ keywords: ['inspection', 'dam', 'frequency', 'monsoon', 'annual'],
148
+ },
149
+ {
150
+ id: 'GR-2024-WRD-034',
151
+ title: 'Inter-state River Water Sharing Agreement',
152
+ department: 'Water Resources',
153
+ date: '2024-03-15',
154
+ year: 2024,
155
+ category: 'Water Policy',
156
+ status: 'Active',
157
+ summary: 'Agreement with neighboring states on Krishna and Godavari river water sharing including allocation percentages, dispute resolution mechanism, and monitoring committee.',
158
+ keywords: ['inter-state', 'river', 'water sharing', 'Krishna', 'Godavari'],
159
+ },
160
+ {
161
+ id: 'GR-2023-WRD-045',
162
+ title: 'Groundwater Extraction Regulation',
163
+ department: 'Groundwater',
164
+ date: '2023-05-10',
165
+ year: 2023,
166
+ category: 'Water Policy',
167
+ status: 'Active',
168
+ summary: 'Regulations for groundwater extraction including registration requirements, permissible limits, NOC requirements, and penalty provisions for over-extraction.',
169
+ keywords: ['groundwater', 'extraction', 'regulation', 'NOC', 'penalty'],
170
+ },
171
+ {
172
+ id: 'GR-2024-WRD-101',
173
+ title: 'Digital Grievance Portal Launch',
174
+ department: 'Administration',
175
+ date: '2024-08-01',
176
+ year: 2024,
177
+ category: 'Administrative',
178
+ status: 'Active',
179
+ summary: 'Launch of digital grievance portal (wrd.maharashtra.gov.in/grievance) for water-related complaints including tracking system, response timelines (7-30 days), and escalation matrix.',
180
+ keywords: ['grievance', 'portal', 'digital', 'complaint', 'tracking'],
181
+ },
182
+ {
183
+ id: 'GR-2023-WRD-089',
184
+ title: 'Tahsildar Water Rights Powers',
185
+ department: 'Revenue',
186
+ date: '2023-04-15',
187
+ year: 2023,
188
+ category: 'Administrative',
189
+ status: 'Active',
190
+ summary: 'Defines powers of Tahsildar regarding water rights including dispute resolution, temporary water allocation during scarcity, and penalty imposition for unauthorized use.',
191
+ keywords: ['tahsildar', 'water rights', 'powers', 'dispute', 'scarcity'],
192
+ },
193
+ {
194
+ id: 'GR-2024-WRD-078',
195
+ title: 'Drought Relief Package for Vidarbha',
196
+ department: 'Relief & Rehabilitation',
197
+ date: '2024-05-20',
198
+ year: 2024,
199
+ category: 'Compensation',
200
+ status: 'Active',
201
+ summary: 'Special drought relief package for Vidarbha region including drinking water supply arrangements, fodder camps, crop loss compensation, and employment guarantee schemes.',
202
+ keywords: ['drought', 'Vidarbha', 'relief', 'drinking water', 'employment'],
203
+ },
204
+ {
205
+ id: 'GR-2022-WRD-067',
206
+ title: 'Dam Safety Audit Requirements',
207
+ department: 'Dam Safety',
208
+ date: '2022-10-15',
209
+ year: 2022,
210
+ category: 'Dam Safety',
211
+ status: 'Active',
212
+ summary: 'Mandatory annual safety audit requirements for all large dams including structural integrity assessment, seismic risk evaluation, and emergency action plan review.',
213
+ keywords: ['safety audit', 'dam', 'structural', 'seismic', 'emergency plan'],
214
+ },
215
+ {
216
+ id: 'GR-2024-WRD-056',
217
+ title: 'Konkan Region Flood Early Warning System',
218
+ department: 'Disaster Management',
219
+ date: '2024-06-01',
220
+ year: 2024,
221
+ category: 'Flood Management',
222
+ status: 'Active',
223
+ summary: 'Implementation of early warning system for Konkan region including rainfall monitoring, river level sensors, SMS alerts, and community communication network.',
224
+ keywords: ['early warning', 'Konkan', 'flood', 'SMS', 'monitoring'],
225
+ },
226
+ ];
227
+
228
+ // Helper functions
229
+ export const getPopularGRs = () => mockGRs.filter(gr => gr.popularQuery);
230
+ export const getGRsByCategory = (category: string) => mockGRs.filter(gr => gr.category === category);
231
+ export const getGRsByYear = (year: number) => mockGRs.filter(gr => gr.year === year);
232
+ export const searchGRs = (query: string) => {
233
+ const q = query.toLowerCase();
234
+ return mockGRs.filter(gr =>
235
+ gr.title.toLowerCase().includes(q) ||
236
+ gr.id.toLowerCase().includes(q) ||
237
+ gr.keywords.some(k => k.includes(q)) ||
238
+ gr.summary.toLowerCase().includes(q)
239
+ );
240
+ };
241
+
242
+ export const grCategories = ['Flood Management', 'Dam Safety', 'Irrigation', 'Compensation', 'Water Policy', 'Administrative'] as const;
243
+ export const grYears = [...new Set(mockGRs.map(gr => gr.year))].sort((a, b) => b - a);
244
+ export const grDepartments = [...new Set(mockGRs.map(gr => gr.department))];
src/index.css ADDED
@@ -0,0 +1 @@
 
 
1
+ @import "tailwindcss";
src/main.tsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import {StrictMode} from 'react';
2
+ import {createRoot} from 'react-dom/client';
3
+ import App from './App.tsx';
4
+ import './index.css';
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
src/pages/Chatbot.tsx ADDED
@@ -0,0 +1,781 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { useLocation, useNavigate } from 'react-router-dom';
3
+ import { Input } from '@/src/components/ui/Input';
4
+ import { Button } from '@/src/components/ui/Button';
5
+ import { Badge } from '@/src/components/ui/Badge';
6
+ import {
7
+ Send, Bot, User, Sparkles, Loader2, X, MessageCircle,
8
+ ScrollText, BookOpen, Scale, FileText, AlertTriangle,
9
+ Droplets, MapPin, TrendingUp, Clock,
10
+ Shield, Waves, Database, Building2, FileSearch,
11
+ Search, ChevronRight, Star, ArrowRight, HelpCircle
12
+ } from 'lucide-react';
13
+ import { cn } from '@/src/utils/cn';
14
+ import { getPopularGRs, mockGRs } from '@/src/data/mockGRs';
15
+
16
+ type DocumentType = 'GR' | 'Act' | 'Circular' | 'Manual' | 'SOP' | 'Report';
17
+
18
+ interface Document {
19
+ id: string;
20
+ title: string;
21
+ type: DocumentType;
22
+ date: string;
23
+ department: string;
24
+ relevance: number;
25
+ summary?: string;
26
+ }
27
+
28
+ interface Insight {
29
+ title: string;
30
+ value: string;
31
+ trend?: string;
32
+ trendUp?: boolean;
33
+ icon: React.ElementType;
34
+ color: string;
35
+ }
36
+
37
+ type Message = {
38
+ id: string;
39
+ role: 'user' | 'assistant';
40
+ content: string;
41
+ sources?: Document[];
42
+ insights?: Insight[];
43
+ timestamp?: Date;
44
+ };
45
+
46
+ // Extended mock document database
47
+ const mockDocuments: Document[] = [
48
+ { id: 'GR-2024-WRD-089', title: 'Flood Relief Compensation Guidelines 2024', type: 'GR', date: '2024-07-15', department: 'Relief & Rehabilitation', relevance: 95, summary: 'Compensation rates: ₹5,000/acre for crop loss, ₹95,000 for house damage' },
49
+ { id: 'GR-2024-WRD-056', title: 'Emergency Dam Safety Protocol', type: 'GR', date: '2024-06-20', department: 'Dam Safety', relevance: 92, summary: 'Mandatory 2-hour advance notification for downstream districts' },
50
+ { id: 'ACT-1976-012', title: 'Maharashtra Irrigation Act', type: 'Act', date: '1976-05-15', department: 'Legislative', relevance: 88, summary: 'Governs water distribution, irrigation rights, and canal maintenance' },
51
+ { id: 'CIRC-2024-WRD-023', title: 'Monsoon Preparedness Circular', type: 'Circular', date: '2024-05-01', department: 'Operations', relevance: 85, summary: 'Pre-monsoon checklist for all dam control rooms' },
52
+ { id: 'GR-2023-WRD-124', title: 'Dam Gate Operations SOP', type: 'SOP', date: '2023-08-10', department: 'Operations', relevance: 90, summary: 'Standard procedures for gate opening/closing with safety checks' },
53
+ { id: 'MANUAL-2024-001', title: 'Flood Management Field Manual', type: 'Manual', date: '2024-03-15', department: 'Training', relevance: 82, summary: 'Field guide for emergency response teams' },
54
+ { id: 'GR-2024-WRD-042', title: 'Water Resource Allocation Policy', type: 'GR', date: '2024-04-20', department: 'Planning', relevance: 78, summary: 'Priority: Drinking > Agriculture > Industrial use' },
55
+ { id: 'ACT-2005-WRD-003', title: 'MWRRA Act', type: 'Act', date: '2005-08-12', department: 'Legislative', relevance: 75, summary: 'Regulatory authority for water pricing and distribution' },
56
+ { id: 'CIRC-2024-WRD-015', title: 'Real-time Monitoring Guidelines', type: 'Circular', date: '2024-04-10', department: 'IT & Operations', relevance: 80, summary: 'IoT sensor deployment for live dam monitoring' },
57
+ { id: 'RPT-2024-001', title: 'Koyna Dam Safety Audit', type: 'Report', date: '2024-06-01', department: 'Dam Safety', relevance: 94, summary: 'Structural integrity: GOOD | Seismic risk: MODERATE' },
58
+ ];
59
+
60
+ // GR Helper Quick Actions
61
+ const grQuickActions = [
62
+ { text: 'Find latest flood relief GR', icon: Search, color: 'blue' },
63
+ { text: 'Explain GR-2024-WRD-089', icon: FileText, color: 'indigo' },
64
+ { text: 'Compare compensation GRs', icon: Scale, color: 'purple' },
65
+ { text: 'Dam safety protocols', icon: Shield, color: 'red' },
66
+ ];
67
+
68
+ // Query suggestions with categories - Focused on rainfall regions and GR rules
69
+ const queryCategories = [
70
+ {
71
+ title: 'Rainfall & Weather',
72
+ icon: Droplets,
73
+ color: 'blue',
74
+ queries: [
75
+ { text: 'Konkan region rainfall data', icon: Droplets },
76
+ { text: 'Vidarbha drought relief GR', icon: FileText },
77
+ ]
78
+ },
79
+ {
80
+ title: 'GR Rules & Acts',
81
+ icon: Scale,
82
+ color: 'purple',
83
+ queries: [
84
+ { text: 'Water distribution priority rules', icon: Shield },
85
+ { text: 'Canal maintenance responsibility', icon: ScrollText },
86
+ ]
87
+ },
88
+ {
89
+ title: 'Relief & Compensation',
90
+ icon: Waves,
91
+ color: 'red',
92
+ queries: [
93
+ { text: 'Flood affected area compensation', icon: FileText },
94
+ { text: 'Crop damage claim procedure', icon: BookOpen },
95
+ ]
96
+ },
97
+ {
98
+ title: 'Administrative',
99
+ icon: Building2,
100
+ color: 'emerald',
101
+ queries: [
102
+ { text: 'Tahsildar water rights powers', icon: Scale },
103
+ { text: 'WUA formation guidelines', icon: MapPin },
104
+ ]
105
+ },
106
+ ];
107
+
108
+ // Generate insights based on query
109
+ const generateInsights = (query: string): Insight[] => {
110
+ const q = query.toLowerCase();
111
+
112
+ if (q.includes('koyna')) {
113
+ return [
114
+ { title: 'Current Level', value: '92.4%', trend: '+2.1%', trendUp: true, icon: Droplets, color: 'text-blue-600' },
115
+ { title: 'Storage', value: '2.8 TMC', trend: 'of 3.0 TMC', icon: Database, color: 'text-cyan-600' },
116
+ { title: 'Inflow', value: '45,000', trend: 'cusecs', icon: Waves, color: 'text-indigo-600' },
117
+ { title: 'Outflow', value: '32,000', trend: 'cusecs', icon: Waves, color: 'text-orange-600' },
118
+ ];
119
+ }
120
+
121
+ if (q.includes('flood') && q.includes('compensation')) {
122
+ return [
123
+ { title: 'Crop Loss', value: '₹5,000', trend: 'per acre', icon: FileText, color: 'text-emerald-600' },
124
+ { title: 'House Damage', value: '₹95,000', trend: 'full damage', icon: Building2, color: 'text-blue-600' },
125
+ { title: 'Processing Time', value: '30', trend: 'days', icon: Clock, color: 'text-orange-600' },
126
+ { title: 'Claims 2024', value: '12,450', trend: '+23%', trendUp: true, icon: TrendingUp, color: 'text-purple-600' },
127
+ ];
128
+ }
129
+
130
+ if (q.includes('dam') && q.includes('gate')) {
131
+ return [
132
+ { title: 'Active Releases', value: '12', trend: '+3 today', trendUp: true, icon: Waves, color: 'text-blue-600' },
133
+ { title: 'Critical Dams', value: '3', trend: 'High alert', icon: AlertTriangle, color: 'text-red-600' },
134
+ { title: 'Total Outflow', value: '1.2M', trend: 'cusecs', icon: Droplets, color: 'text-cyan-600' },
135
+ { title: 'Downstream Alert', value: '18', trend: 'districts', icon: Shield, color: 'text-orange-600' },
136
+ ];
137
+ }
138
+
139
+ return [
140
+ { title: 'Total Dams', value: '287', trend: 'monitored', icon: Database, color: 'text-blue-600' },
141
+ { title: 'GRs 2024', value: '142', trend: 'indexed', icon: FileText, color: 'text-emerald-600' },
142
+ { title: 'Active Alerts', value: '7', trend: 'critical', icon: AlertTriangle, color: 'text-red-600' },
143
+ { title: 'Avg Storage', value: '68%', trend: '+5%', trendUp: true, icon: Droplets, color: 'text-cyan-600' },
144
+ ];
145
+ };
146
+
147
+ // Generate response based on query
148
+ const generateResponse = (query: string): { content: string; docs: Document[] } => {
149
+ const q = query.toLowerCase();
150
+
151
+ if (q.includes('koyna')) {
152
+ return {
153
+ content: `**Koyna Dam - Current Status (as of ${new Date().toLocaleTimeString('en-IN')})**
154
+
155
+ • **Storage Level:** 92.4% (2.8 TMC of 3.0 TMC capacity)
156
+ • **Inflow:** 45,000 cusecs | **Outflow:** 32,000 cusecs
157
+ • **Status:** ⚠️ CRITICAL - Emergency discharge initiated
158
+ • **Districts Alerted:** Satara, Sangli, Kolhapur collectors notified
159
+
160
+ **Recent Actions:**
161
+ - 4 radial gates opened at 50% capacity
162
+ - Downstream evacuation advisory issued
163
+ - Hourly monitoring activated
164
+
165
+ Refer to **GR-2024-WRD-056** for emergency protocols.`,
166
+ docs: mockDocuments.filter(d => d.id.includes('056') || d.id.includes('RPT-2024-001'))
167
+ };
168
+ }
169
+
170
+ if (q.includes('flood') && q.includes('compensation')) {
171
+ return {
172
+ content: `**Flood Relief Compensation - 2024 Guidelines (GR-2024-WRD-089)**
173
+
174
+ **Eligibility Criteria:**
175
+ • Residence in notified flood-affected area
176
+ • Damage between July 1 - October 31, 2024
177
+ • Registration with local Tahsildar office
178
+
179
+ **Compensation Rates:**
180
+ • Crop Loss: ₹5,000 per acre (max 10 acres)
181
+ • House Damage: ₹95,000 (full) / ₹50,000 (partial)
182
+ • Livestock: ₹50,000 per animal (max 5)
183
+
184
+ **Application Process:**
185
+ 1. Submit Form WRD-FLOOD-01 to Tahsildar
186
+ 2. Attach damage assessment report
187
+ 3. Processing time: 30 days
188
+ 4. Direct bank transfer
189
+
190
+ Contact: District Relief Office for assistance.`,
191
+ docs: mockDocuments.filter(d => d.id.includes('089') || d.id.includes('001'))
192
+ };
193
+ }
194
+
195
+ if (q.includes('gate') || q.includes('sop')) {
196
+ return {
197
+ content: `**Dam Gate Operations SOP (GR-2023-WRD-124)**
198
+
199
+ **Pre-Opening Checklist:**
200
+ ✓ Seismic sensors calibrated
201
+ ✓ Downstream flow sensors active
202
+ ✓ Alert systems tested
203
+ ✓ Control room staffed (min 2 engineers)
204
+
205
+ **Opening Procedure:**
206
+ 1. **Notification** (T-2 hours): Alert downstream districts
207
+ 2. **Gradual Opening**: 10% increments every 15 minutes
208
+ 3. **Flow Monitoring**: Continuous discharge measurement
209
+ 4. **Public Announcement**: Local media + sirens
210
+
211
+ **Emergency Override:**
212
+ Chief Engineer can authorize immediate release if dam level exceeds 95%.
213
+
214
+ **Documentation:** All gate operations logged in DAMS system within 1 hour.`,
215
+ docs: mockDocuments.filter(d => d.id.includes('124') || d.id.includes('056'))
216
+ };
217
+ }
218
+
219
+ if (q.includes('irrigation') || q.includes('act')) {
220
+ return {
221
+ content: `**Maharashtra Irrigation Act, 1976 - Key Provisions**
222
+
223
+ **Section 33 - Water Rights:**
224
+ • Priority order: Drinking water > Agriculture > Industry
225
+ • Canal water distribution by WRD, not local bodies
226
+ • Penalty for unauthorized diversion: Up to 3 years imprisonment
227
+
228
+ **Section 45 - Maintenance:**
229
+ • WRD responsible for major repairs (>₹1 lakh)
230
+ • Water User Associations (WUAs) for minor maintenance
231
+ • 5% water cess collected for maintenance fund
232
+
233
+ **Recent Amendment (2024):**
234
+ • IoT-based usage monitoring mandated
235
+ • Real-time allocation adjustments during scarcity
236
+ • Digital grievance portal: wrd.maharashtra.gov.in/grievance`,
237
+ docs: mockDocuments.filter(d => d.type === 'Act')
238
+ };
239
+ }
240
+
241
+ const relevantDocs = mockDocuments
242
+ .filter(d => q.includes(d.type.toLowerCase()) ||
243
+ d.title.toLowerCase().includes(q.split(' ')[0]))
244
+ .slice(0, 3);
245
+
246
+ return {
247
+ content: `I found **${relevantDocs.length} relevant document(s)** for your query on "${query}".
248
+
249
+ ${relevantDocs.map(d => `**${d.id}** - ${d.title}\n• ${d.summary || 'No summary available'}`).join('\n\n')}
250
+
251
+ Would you like me to provide detailed information about any of these documents, or search for something more specific?`,
252
+ docs: relevantDocs
253
+ };
254
+ };
255
+
256
+ export function Chatbot() {
257
+ const location = useLocation();
258
+ const navigate = useNavigate();
259
+ const [messages, setMessages] = useState<Message[]>([]);
260
+ const [input, setInput] = useState('');
261
+ const [isLoading, setIsLoading] = useState(false);
262
+ const [isMobileOpen, setIsMobileOpen] = useState(false);
263
+ const [showWelcome, setShowWelcome] = useState(true);
264
+ const [showSidebar, setShowSidebar] = useState(true);
265
+ const messagesEndRef = useRef<HTMLDivElement>(null);
266
+ const inputRef = useRef<HTMLInputElement>(null);
267
+ const popularGRs = getPopularGRs();
268
+
269
+ useEffect(() => {
270
+ const searchParams = new URLSearchParams(location.search);
271
+ const query = searchParams.get('q');
272
+ if (query) {
273
+ handleSend(query);
274
+ setShowWelcome(false);
275
+ }
276
+ }, [location]);
277
+
278
+ useEffect(() => {
279
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
280
+ }, [messages]);
281
+
282
+ const handleSend = async (text: string) => {
283
+ if (!text.trim()) return;
284
+
285
+ setShowWelcome(false);
286
+ const userMsg: Message = {
287
+ id: Date.now().toString(),
288
+ role: 'user',
289
+ content: text,
290
+ timestamp: new Date()
291
+ };
292
+ setMessages(prev => [...prev, userMsg]);
293
+ setInput('');
294
+ setIsLoading(true);
295
+
296
+ await new Promise(r => setTimeout(r, 800));
297
+
298
+ const { content, docs } = generateResponse(text);
299
+ const insights = generateInsights(text);
300
+
301
+ const aiMsg: Message = {
302
+ id: (Date.now() + 1).toString(),
303
+ role: 'assistant',
304
+ content,
305
+ sources: docs,
306
+ insights,
307
+ timestamp: new Date()
308
+ };
309
+
310
+ setMessages(prev => [...prev, aiMsg]);
311
+ setIsLoading(false);
312
+ };
313
+
314
+ const getTypeIcon = (type: DocumentType) => {
315
+ switch (type) {
316
+ case 'GR': return ScrollText;
317
+ case 'Act': return Scale;
318
+ case 'Circular': return FileText;
319
+ case 'Manual': return BookOpen;
320
+ case 'SOP': return Shield;
321
+ case 'Report': return FileSearch;
322
+ }
323
+ };
324
+
325
+ const formatTime = (date?: Date) => {
326
+ if (!date) return '';
327
+ return date.toLocaleTimeString('en-IN', { hour: '2-digit', minute: '2-digit' });
328
+ };
329
+
330
+ const WelcomeScreen = () => (
331
+ <div className="flex flex-col justify-center gap-4 h-full w-full max-w-2xl mx-auto py-2">
332
+ {/* Top Section - Title */}
333
+ <div className="text-center">
334
+ <div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-xl flex items-center justify-center mx-auto mb-2 shadow-md">
335
+ <Sparkles className="h-6 w-6 text-white" />
336
+ </div>
337
+ <h2 className="text-lg font-semibold text-slate-800">GR Helper</h2>
338
+ <p className="text-xs text-slate-500 mt-1">AI Assistant for Government Resolutions & Acts</p>
339
+ </div>
340
+
341
+ {/* GR Quick Actions */}
342
+ <div className="px-2 mt-2">
343
+ <p className="text-xs font-medium text-slate-500 mb-2 px-1">Quick Actions</p>
344
+ <div className="grid grid-cols-2 gap-2">
345
+ {grQuickActions.map((action, idx) => {
346
+ const Icon = action.icon;
347
+ return (
348
+ <button
349
+ key={idx}
350
+ onClick={() => handleSend(action.text)}
351
+ className="text-left p-3 bg-white rounded-lg border border-slate-200 hover:border-indigo-300 hover:shadow-sm active:bg-slate-50 transition-all"
352
+ >
353
+ <div className="flex items-center gap-2">
354
+ <div className={cn("p-1 rounded", `bg-${action.color}-100`)}>
355
+ <Icon className={cn("h-3.5 w-3.5", `text-${action.color}-600`)} />
356
+ </div>
357
+ <span className="text-xs font-medium text-slate-700 line-clamp-1">{action.text}</span>
358
+ </div>
359
+ </button>
360
+ );
361
+ })}
362
+ </div>
363
+ </div>
364
+
365
+ {/* Middle Section - Category Cards */}
366
+ <div className="px-2 mt-2">
367
+ <p className="text-xs font-medium text-slate-500 mb-2 px-1">Browse by Category</p>
368
+ <div className="grid grid-cols-2 gap-2">
369
+ {queryCategories.map((cat, idx) => {
370
+ const Icon = cat.icon;
371
+ return (
372
+ <button
373
+ key={idx}
374
+ onClick={() => handleSend(cat.queries[0].text)}
375
+ className="text-left p-3 bg-white rounded-lg border border-slate-200 hover:border-blue-300 active:bg-slate-50 transition-all shadow-sm"
376
+ >
377
+ <div className="flex items-center gap-2 mb-1">
378
+ <div className={cn("p-1 rounded", `bg-${cat.color}-100`)}>
379
+ <Icon className={cn("h-3.5 w-3.5", `text-${cat.color}-600`)} />
380
+ </div>
381
+ <span className="text-xs font-semibold text-slate-700">{cat.title}</span>
382
+ </div>
383
+ <p className="text-[10px] text-slate-500 truncate">{cat.queries[0].text}</p>
384
+ </button>
385
+ );
386
+ })}
387
+ </div>
388
+ </div>
389
+
390
+ {/* Bottom Section - Quick Links */}
391
+ <div className="flex flex-wrap justify-center gap-1.5 px-2 mt-2">
392
+ {['Konkan rainfall', 'Vidarbha GR', 'Water rights', 'Compensation'].map((s, i) => (
393
+ <button
394
+ key={i}
395
+ onClick={() => handleSend(s)}
396
+ className="text-[10px] px-2.5 py-1 bg-slate-100 text-slate-600 rounded-full hover:bg-slate-200 transition-colors"
397
+ >
398
+ {s}
399
+ </button>
400
+ ))}
401
+ </div>
402
+ </div>
403
+ );
404
+
405
+ const RecentGRsSidebar = () => (
406
+ <div className={cn(
407
+ "w-64 border-l border-slate-200 bg-white flex-col shrink-0 hidden lg:flex",
408
+ showSidebar ? "lg:flex" : "lg:hidden"
409
+ )}>
410
+ <div className="p-3 border-b border-slate-100">
411
+ <div className="flex items-center justify-between">
412
+ <h3 className="text-xs font-semibold text-slate-700">Popular GRs</h3>
413
+ <Star className="h-3 w-3 text-yellow-500" />
414
+ </div>
415
+ </div>
416
+ <div className="flex-1 overflow-y-auto p-2 space-y-1.5">
417
+ {popularGRs.map(gr => (
418
+ <button
419
+ key={gr.id}
420
+ onClick={() => handleSend(`Explain ${gr.id}: ${gr.title}`)}
421
+ className="w-full text-left p-2 rounded-md hover:bg-slate-50 transition-colors group"
422
+ >
423
+ <div className="flex items-start justify-between gap-1">
424
+ <p className="text-[10px] font-mono text-indigo-600">{gr.id}</p>
425
+ <ChevronRight className="h-3 w-3 text-slate-300 group-hover:text-indigo-500 shrink-0 mt-0.5" />
426
+ </div>
427
+ <p className="text-xs text-slate-700 line-clamp-2 mt-0.5">{gr.title}</p>
428
+ <div className="flex items-center gap-1 mt-1">
429
+ <Badge className="text-[8px] px-1 py-0 h-auto bg-slate-100 text-slate-600">
430
+ {gr.category}
431
+ </Badge>
432
+ </div>
433
+ </button>
434
+ ))}
435
+ </div>
436
+ <div className="p-2 border-t border-slate-100">
437
+ <Button
438
+ variant="ghost"
439
+ size="sm"
440
+ className="w-full text-xs h-8"
441
+ onClick={() => navigate('/gr-finder')}
442
+ >
443
+ <Search className="h-3 w-3 mr-1" />
444
+ Browse All GRs
445
+ </Button>
446
+ </div>
447
+ </div>
448
+ );
449
+
450
+ const MessageBubble = ({ msg }: { msg: Message }) => (
451
+ <div className={cn(
452
+ "flex w-full mb-4",
453
+ msg.role === 'user' ? "justify-end" : "justify-start"
454
+ )}>
455
+ <div className={cn(
456
+ "flex gap-3 max-w-[90%] md:max-w-[80%]",
457
+ msg.role === 'user' ? "flex-row-reverse" : "flex-row"
458
+ )}>
459
+ <div className={cn(
460
+ "flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center",
461
+ msg.role === 'user'
462
+ ? "bg-blue-600 text-white"
463
+ : "bg-gradient-to-br from-blue-500 to-indigo-600 text-white"
464
+ )}>
465
+ {msg.role === 'user' ? <User className="h-4 w-4" /> : <Bot className="h-4 w-4" />}
466
+ </div>
467
+
468
+ <div className="flex flex-col gap-2">
469
+ <div className={cn(
470
+ "px-4 py-3 rounded-2xl text-sm leading-relaxed",
471
+ msg.role === 'user'
472
+ ? "bg-blue-600 text-white rounded-tr-sm"
473
+ : "bg-white border border-slate-200 text-slate-700 rounded-tl-sm shadow-sm"
474
+ )}>
475
+ <div className="whitespace-pre-wrap">
476
+ {msg.content.split('**').map((part, i) =>
477
+ i % 2 === 0 ? part : <strong key={i} className="font-semibold">{part}</strong>
478
+ )}
479
+ </div>
480
+ </div>
481
+
482
+ {msg.insights && msg.insights.length > 0 && (
483
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-2 mt-1">
484
+ {msg.insights.map((insight, idx) => {
485
+ const Icon = insight.icon;
486
+ return (
487
+ <div key={idx} className="bg-white border border-slate-200 rounded-lg p-2.5 shadow-sm">
488
+ <div className="flex items-center gap-1.5 mb-1">
489
+ <Icon className={cn("h-3.5 w-3.5", insight.color)} />
490
+ <span className="text-[10px] text-slate-500 uppercase tracking-wider">{insight.title}</span>
491
+ </div>
492
+ <div className="flex items-baseline gap-1">
493
+ <span className="text-lg font-bold text-slate-800">{insight.value}</span>
494
+ {insight.trend && (
495
+ <span className={cn(
496
+ "text-[10px]",
497
+ insight.trendUp ? "text-emerald-600" : "text-slate-400"
498
+ )}>
499
+ {insight.trend}
500
+ </span>
501
+ )}
502
+ </div>
503
+ </div>
504
+ );
505
+ })}
506
+ </div>
507
+ )}
508
+
509
+ {msg.sources && msg.sources.length > 0 && (
510
+ <div className="flex flex-wrap gap-1.5 mt-1">
511
+ {msg.sources.map((doc, idx) => {
512
+ const TypeIcon = getTypeIcon(doc.type);
513
+ return (
514
+ <div
515
+ key={idx}
516
+ className="flex items-center gap-1.5 text-[11px] px-2.5 py-1.5 bg-slate-100 text-slate-600 rounded-md border border-slate-200"
517
+ >
518
+ <TypeIcon className="h-3 w-3" />
519
+ <span className="font-medium">{doc.id}</span>
520
+ <span className="text-slate-400">|</span>
521
+ <span className="truncate max-w-[120px]">{doc.title}</span>
522
+ </div>
523
+ );
524
+ })}
525
+ </div>
526
+ )}
527
+
528
+ <span className="text-[10px] text-slate-400 ml-1">
529
+ {formatTime(msg.timestamp)}
530
+ </span>
531
+ </div>
532
+ </div>
533
+ </div>
534
+ );
535
+
536
+ return (
537
+ <>
538
+ {/* Desktop */}
539
+ <div className="hidden md:flex h-full w-full flex-row bg-slate-50 overflow-hidden rounded-lg min-h-0">
540
+ {/* Main Chat Area */}
541
+ <div className="flex-1 flex flex-col min-w-0">
542
+ <div className="bg-white border-b border-slate-200 px-4 py-3 shrink-0">
543
+ <div className="flex items-center justify-between">
544
+ <div className="flex items-center gap-3">
545
+ <div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-lg flex items-center justify-center shadow-sm">
546
+ <Sparkles className="h-4 w-4 text-white" />
547
+ </div>
548
+ <div>
549
+ <h1 className="text-base font-semibold text-slate-900">GR Helper</h1>
550
+ <p className="text-[11px] text-slate-500">AI Assistant for Government Resolutions</p>
551
+ </div>
552
+ </div>
553
+ <div className="flex items-center gap-2">
554
+ <Button
555
+ variant="ghost"
556
+ size="sm"
557
+ className="text-xs h-8"
558
+ onClick={() => navigate('/faq')}
559
+ >
560
+ <HelpCircle className="h-3.5 w-3.5 mr-1" />
561
+ FAQ
562
+ </Button>
563
+ <Button
564
+ variant="ghost"
565
+ size="sm"
566
+ className="text-xs h-8"
567
+ onClick={() => navigate('/gr-finder')}
568
+ >
569
+ <Search className="h-3.5 w-3.5 mr-1" />
570
+ GR Finder
571
+ </Button>
572
+ </div>
573
+ </div>
574
+ </div>
575
+
576
+ <div className="flex-1 overflow-hidden px-4 py-4 min-h-0">
577
+ {showWelcome && messages.length === 0 ? (
578
+ <WelcomeScreen />
579
+ ) : (
580
+ <div className="max-w-3xl mx-auto h-full overflow-y-auto pr-2 min-h-0">
581
+ {messages.map((msg, i) => (
582
+ <React.Fragment key={msg.id || String(i)}>
583
+ <MessageBubble msg={msg} />
584
+ </React.Fragment>
585
+ ))}
586
+ {isLoading && (
587
+ <div className="flex items-center gap-3 text-slate-500">
588
+ <div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-full flex items-center justify-center">
589
+ <Bot className="h-4 w-4 text-white" />
590
+ </div>
591
+ <div className="flex items-center gap-2 bg-white border border-slate-200 px-4 py-2.5 rounded-2xl rounded-tl-sm shadow-sm">
592
+ <Loader2 className="h-4 w-4 animate-spin text-blue-500" />
593
+ <span className="text-sm">Searching GR database...</span>
594
+ </div>
595
+ </div>
596
+ )}
597
+ <div ref={messagesEndRef} />
598
+ </div>
599
+ )}
600
+ </div>
601
+
602
+ <div className="bg-white border-t border-slate-200 px-4 py-3 shrink-0">
603
+ <div className="max-w-3xl mx-auto">
604
+ <form onSubmit={(e) => { e.preventDefault(); handleSend(input); }} className="flex gap-3">
605
+ <div className="flex-1 relative">
606
+ <Input
607
+ ref={inputRef}
608
+ type="text"
609
+ placeholder="Ask about GRs, Acts, compensation, flood relief..."
610
+ value={input}
611
+ onChange={(e) => setInput(e.target.value)}
612
+ className="h-11 pl-4 pr-10 bg-slate-50 border-slate-200 text-sm focus:bg-white focus:border-blue-500 transition-all rounded-xl"
613
+ disabled={isLoading}
614
+ />
615
+ <div className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400">
616
+ <FileSearch className="h-4 w-4" />
617
+ </div>
618
+ </div>
619
+ <Button
620
+ type="submit"
621
+ disabled={!input.trim() || isLoading}
622
+ className="h-11 px-5 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white text-sm font-medium rounded-xl shadow-sm"
623
+ >
624
+ <Send className="h-4 w-4 mr-1.5" />
625
+ Send
626
+ </Button>
627
+ </form>
628
+ </div>
629
+ </div>
630
+ </div>
631
+
632
+ {/* Sidebar with Popular GRs */}
633
+ <RecentGRsSidebar />
634
+ </div>
635
+
636
+ {/* Mobile */}
637
+ <>
638
+ {!isMobileOpen && (
639
+ <button
640
+ onClick={() => setIsMobileOpen(true)}
641
+ className="fixed bottom-20 right-4 z-50 w-14 h-14 bg-gradient-to-br from-blue-600 to-indigo-600 text-white rounded-full shadow-lg flex items-center justify-center md:hidden"
642
+ >
643
+ <MessageCircle className="h-6 w-6" />
644
+ </button>
645
+ )}
646
+
647
+ {isMobileOpen && (
648
+ <div className="fixed inset-0 z-50 bg-slate-50 flex flex-col md:hidden">
649
+ <div className="bg-white border-b border-slate-200 px-4 py-3 flex items-center justify-between">
650
+ <div className="flex items-center gap-2.5">
651
+ <div className="w-9 h-9 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-xl flex items-center justify-center">
652
+ <Sparkles className="h-4 w-4 text-white" />
653
+ </div>
654
+ <div>
655
+ <h1 className="font-semibold text-slate-900 text-sm">GR Helper</h1>
656
+ <p className="text-[10px] text-slate-500">AI Assistant for Government GRs</p>
657
+ </div>
658
+ </div>
659
+ <button
660
+ onClick={() => setIsMobileOpen(false)}
661
+ className="w-8 h-8 flex items-center justify-center rounded-full hover:bg-slate-100"
662
+ >
663
+ <X className="h-5 w-5 text-slate-500" />
664
+ </button>
665
+ </div>
666
+
667
+ <div className="flex-1 overflow-hidden px-3 py-1 min-h-0">
668
+ {showWelcome && messages.length === 0 ? (
669
+ <div className="flex flex-col justify-center gap-4 h-full w-full max-w-sm mx-auto">
670
+ {/* Top */}
671
+ <div className="text-center">
672
+ <div className="w-10 h-10 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-xl flex items-center justify-center mx-auto mb-2 shadow-md">
673
+ <Sparkles className="h-5 w-5 text-white" />
674
+ </div>
675
+ <h2 className="text-base font-semibold text-slate-800">GR Helper</h2>
676
+ <p className="text-[11px] text-slate-500 mt-1">AI Assistant for Government GRs</p>
677
+ </div>
678
+
679
+ {/* Quick Actions */}
680
+ <div className="px-2">
681
+ <p className="text-[10px] font-medium text-slate-500 mb-1.5 px-1">Quick Actions</p>
682
+ <div className="grid grid-cols-2 gap-1.5">
683
+ {grQuickActions.slice(0, 4).map((action, idx) => (
684
+ <button
685
+ key={idx}
686
+ onClick={() => handleSend(action.text)}
687
+ className="text-left p-2 bg-white rounded-lg border border-slate-200 active:bg-slate-50"
688
+ >
689
+ <div className="flex items-center gap-1.5">
690
+ <action.icon className={cn("h-3 w-3", `text-${action.color}-600`)} />
691
+ <span className="text-[10px] font-medium text-slate-700 line-clamp-1">{action.text}</span>
692
+ </div>
693
+ </button>
694
+ ))}
695
+ </div>
696
+ </div>
697
+
698
+ {/* Middle - Cards */}
699
+ <div className="px-2">
700
+ <p className="text-[10px] font-medium text-slate-500 mb-1.5 px-1">Browse by Category</p>
701
+ <div className="grid grid-cols-2 gap-1.5">
702
+ {queryCategories.map((cat, idx) => (
703
+ <button
704
+ key={idx}
705
+ onClick={() => handleSend(cat.queries[0].text)}
706
+ className="text-left p-2 bg-white rounded-lg border border-slate-200 active:bg-slate-50"
707
+ >
708
+ <div className="flex items-center gap-1.5 mb-0.5">
709
+ <div className={cn("p-0.5 rounded", `bg-${cat.color}-100`)}>
710
+ <cat.icon className={cn("h-3 w-3", `text-${cat.color}-600`)} />
711
+ </div>
712
+ <span className="text-[10px] font-medium text-slate-700">{cat.title}</span>
713
+ </div>
714
+ <p className="text-[9px] text-slate-500 truncate">{cat.queries[0].text}</p>
715
+ </button>
716
+ ))}
717
+ </div>
718
+ </div>
719
+
720
+ {/* Bottom - Quick Links */}
721
+ <div className="flex flex-wrap justify-center gap-1.5 px-2">
722
+ {['Konkan rainfall', 'Vidarbha GR', 'Water rights', 'Compensation'].map((s, i) => (
723
+ <button
724
+ key={i}
725
+ onClick={() => handleSend(s)}
726
+ className="text-[10px] px-2.5 py-1 bg-slate-100 text-slate-600 rounded-full"
727
+ >
728
+ {s}
729
+ </button>
730
+ ))}
731
+ </div>
732
+ </div>
733
+ ) : (
734
+ <div className="w-full h-full overflow-y-auto space-y-3 min-h-0">
735
+ {messages.map((msg, i) => (
736
+ <React.Fragment key={msg.id || String(i)}>
737
+ <MessageBubble msg={msg} />
738
+ </React.Fragment>
739
+ ))}
740
+ {isLoading && (
741
+ <div className="flex items-center gap-2 text-slate-500">
742
+ <div className="w-7 h-7 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-full flex items-center justify-center">
743
+ <Bot className="h-3.5 w-3.5 text-white" />
744
+ </div>
745
+ <div className="flex items-center gap-2 bg-white border border-slate-200 px-3 py-2 rounded-xl rounded-tl-sm">
746
+ <Loader2 className="h-3 w-3 animate-spin text-blue-500" />
747
+ <span className="text-xs">Searching...</span>
748
+ </div>
749
+ </div>
750
+ )}
751
+ <div ref={messagesEndRef} />
752
+ </div>
753
+ )}
754
+ </div>
755
+
756
+ <div className="bg-white border-t border-slate-200 px-4 py-2 shrink-0">
757
+ <form onSubmit={(e) => { e.preventDefault(); handleSend(input); }} className="flex gap-2">
758
+ <Input
759
+ type="text"
760
+ placeholder="Ask about GRs, Acts, compensation..."
761
+ value={input}
762
+ onChange={(e) => setInput(e.target.value)}
763
+ className="flex-1 h-10 bg-slate-50 text-sm rounded-xl"
764
+ disabled={isLoading}
765
+ />
766
+ <Button
767
+ type="submit"
768
+ size="icon"
769
+ disabled={!input.trim() || isLoading}
770
+ className="h-10 w-10 bg-gradient-to-r from-blue-600 to-indigo-600 rounded-xl"
771
+ >
772
+ <Send className="h-4 w-4" />
773
+ </Button>
774
+ </form>
775
+ </div>
776
+ </div>
777
+ )}
778
+ </>
779
+ </>
780
+ );
781
+ }
src/pages/Dashboard.tsx ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/src/components/ui/Card';
4
+ import { Badge } from '@/src/components/ui/Badge';
5
+ import { Button } from '@/src/components/ui/Button';
6
+ import {
7
+ AlertTriangle,
8
+ Droplets,
9
+ FileText,
10
+ ArrowRight,
11
+ CloudRain,
12
+ Bell,
13
+ Database,
14
+ MapPin,
15
+ TrendingUp,
16
+ TrendingDown,
17
+ Waves,
18
+ Calendar,
19
+ Search,
20
+ MessageSquare,
21
+ HelpCircle,
22
+ FileSearch,
23
+ Sparkles
24
+ } from 'lucide-react';
25
+ import { cn } from '@/src/utils/cn';
26
+ import {
27
+ AreaChart,
28
+ Area,
29
+ XAxis,
30
+ YAxis,
31
+ CartesianGrid,
32
+ Tooltip,
33
+ ResponsiveContainer,
34
+ BarChart,
35
+ Bar,
36
+ Legend,
37
+ PieChart,
38
+ Pie,
39
+ Cell
40
+ } from 'recharts';
41
+
42
+ // Extended water level data with more WRD-specific metrics
43
+ const waterLevelData = [
44
+ { name: 'Jan', level: 40, avg: 35, rainfall: 0 },
45
+ { name: 'Feb', level: 35, avg: 30, rainfall: 2 },
46
+ { name: 'Mar', level: 30, avg: 25, rainfall: 5 },
47
+ { name: 'Apr', level: 25, avg: 20, rainfall: 12 },
48
+ { name: 'May', level: 20, avg: 15, rainfall: 28 },
49
+ { name: 'Jun', level: 45, avg: 40, rainfall: 85 },
50
+ { name: 'Jul', level: 75, avg: 65, rainfall: 245 },
51
+ { name: 'Aug', level: 90, avg: 85, rainfall: 298 },
52
+ { name: 'Sep', level: 85, avg: 80, rainfall: 180 },
53
+ { name: 'Oct', level: 70, avg: 65, rainfall: 65 },
54
+ { name: 'Nov', level: 60, avg: 55, rainfall: 18 },
55
+ { name: 'Dec', level: 50, avg: 45, rainfall: 3 },
56
+ ];
57
+
58
+ // Dam storage by region
59
+ const regionData = [
60
+ { name: 'Konkan', full: 85, capacity: '45 TMC' },
61
+ { name: 'Western Maharashtra', full: 72, capacity: '120 TMC' },
62
+ { name: 'Marathwada', full: 45, capacity: '89 TMC' },
63
+ { name: 'Vidarbha', full: 58, capacity: '67 TMC' },
64
+ { name: 'North Maharashtra', full: 63, capacity: '34 TMC' },
65
+ ];
66
+
67
+ // Recent alerts with WRD-specific details
68
+ const alerts = [
69
+ { id: 1, title: 'Critical Water Level', location: 'Koyna Dam, Satara', severity: 'Critical', time: '30 min ago', type: 'dam', description: '92% capacity - Emergency discharge initiated' },
70
+ { id: 2, title: 'Flash Flood Warning', location: 'Panchaganga Basin, Kolhapur', severity: 'Critical', time: '1 hour ago', type: 'flood', description: 'Rising levels - Evacuation alert issued' },
71
+ { id: 3, title: 'Heavy Rainfall Alert', location: 'Ratnagiri District', severity: 'High', time: '3 hours ago', type: 'rain', description: '250mm in 24h - Landslide risk' },
72
+ { id: 4, title: 'New GR Published', location: 'WRD Circular No. 2024/08', severity: 'Info', time: '5 hours ago', type: 'gr', description: 'Updated flood response protocols' },
73
+ { id: 5, title: 'Gate Maintenance', location: 'Jayakwadi Dam', severity: 'Medium', time: '1 day ago', type: 'maintenance', description: 'Scheduled maintenance - Reduced outflow' },
74
+ ];
75
+
76
+ // WRD KPI data
77
+ const kpiData = [
78
+ { title: 'Critical Alerts', value: '7', trend: '+2', trendUp: false, icon: AlertTriangle, color: 'red', subtitle: 'Immediate action required' },
79
+ { title: 'Active Releases', value: '12', trend: '+3', trendUp: true, icon: Waves, color: 'blue', subtitle: 'Dams with open gates' },
80
+ { title: 'Avg Storage', value: '68%', trend: '+5%', trendUp: true, icon: Droplets, color: 'cyan', subtitle: 'Above normal for season' },
81
+ { title: '24h Rainfall', value: '145mm', trend: '+85mm', trendUp: false, icon: CloudRain, color: 'indigo', subtitle: 'Konkan region' },
82
+ { title: 'GRs This Month', value: '24', trend: '+8', trendUp: true, icon: FileText, color: 'emerald', subtitle: 'New policies/circulars' },
83
+ { title: 'Flood Zones', value: '3', trend: '+1', trendUp: false, icon: MapPin, color: 'orange', subtitle: 'Active predictions' },
84
+ ];
85
+
86
+ const COLORS = ['#3b82f6', '#06b6d4', '#8b5cf6', '#f59e0b', '#ef4444'];
87
+
88
+ export function Dashboard() {
89
+ const navigate = useNavigate();
90
+
91
+ const quickLinks = [
92
+ { name: 'Find a GR', description: 'Search Government Resolutions', icon: FileSearch, path: '/gr-finder', color: 'blue' },
93
+ { name: 'Ask GR Helper', description: 'AI assistant for GR queries', icon: Sparkles, path: '/ai-assistant', color: 'indigo' },
94
+ { name: 'View FAQ', description: 'Common WRD questions', icon: HelpCircle, path: '/faq', color: 'emerald' },
95
+ { name: 'Flood Map', description: 'Real-time flood prediction', icon: MapPin, path: '/map', color: 'cyan' },
96
+ ];
97
+
98
+ return (
99
+ <div className="space-y-4 lg:space-y-6 p-4 lg:p-6">
100
+ {/* Header */}
101
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
102
+ <div>
103
+ <h1 className="text-xl lg:text-2xl font-bold tracking-tight text-slate-900">JalDrishti Dashboard</h1>
104
+ <p className="text-sm text-slate-500">Maharashtra WRD - Real-time Water Resources Monitoring</p>
105
+ </div>
106
+ <div className="flex items-center gap-2 text-xs lg:text-sm text-slate-500">
107
+ <Calendar className="h-4 w-4" />
108
+ <span>Last updated: {new Date().toLocaleString('en-IN', { dateStyle: 'medium', timeStyle: 'short' })}</span>
109
+ </div>
110
+ </div>
111
+
112
+ {/* Quick Links */}
113
+ <div className="grid grid-cols-2 lg:grid-cols-4 gap-3">
114
+ {quickLinks.map((link, idx) => {
115
+ const Icon = link.icon;
116
+ return (
117
+ <Card
118
+ key={idx}
119
+ className="cursor-pointer hover:shadow-md transition-all border-slate-200 group"
120
+ onClick={() => navigate(link.path)}
121
+ >
122
+ <CardContent className="p-4">
123
+ <div className="flex items-start gap-3">
124
+ <div className={cn(
125
+ "rounded-lg p-2 shrink-0",
126
+ link.color === 'blue' ? "bg-blue-100 text-blue-600" :
127
+ link.color === 'indigo' ? "bg-indigo-100 text-indigo-600" :
128
+ link.color === 'emerald' ? "bg-emerald-100 text-emerald-600" :
129
+ "bg-cyan-100 text-cyan-600"
130
+ )}>
131
+ <Icon className="h-5 w-5" />
132
+ </div>
133
+ <div className="flex-1 min-w-0">
134
+ <p className="text-sm font-semibold text-slate-900 group-hover:text-blue-600 transition-colors">{link.name}</p>
135
+ <p className="text-xs text-slate-500 mt-0.5">{link.description}</p>
136
+ </div>
137
+ <ArrowRight className="h-4 w-4 text-slate-300 group-hover:text-blue-500 transition-colors shrink-0" />
138
+ </div>
139
+ </CardContent>
140
+ </Card>
141
+ );
142
+ })}
143
+ </div>
144
+
145
+ {/* KPI Cards - Horizontal scroll on mobile */}
146
+ <div className="overflow-x-auto -mx-4 px-4 lg:mx-0 lg:px-0 pb-2 lg:pb-0">
147
+ <div className="flex gap-3 lg:grid lg:grid-cols-6 min-w-max lg:min-w-0">
148
+ {kpiData.map((kpi, idx) => {
149
+ const Icon = kpi.icon;
150
+ return (
151
+ <Card key={idx} className="w-[160px] lg:w-auto shrink-0">
152
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2 p-3">
153
+ <CardTitle className="text-xs font-medium">{kpi.title}</CardTitle>
154
+ <Icon className={cn("h-4 w-4", `text-${kpi.color}-500`)} />
155
+ </CardHeader>
156
+ <CardContent className="p-3 pt-0">
157
+ <div className="text-xl lg:text-2xl font-bold">{kpi.value}</div>
158
+ <div className="flex items-center gap-1 mt-1">
159
+ {kpi.trendUp ? (
160
+ <TrendingUp className={cn("h-3 w-3", kpi.color === 'red' ? "text-red-500" : "text-green-500")} />
161
+ ) : (
162
+ <TrendingDown className={cn("h-3 w-3", kpi.color === 'red' ? "text-red-500" : "text-orange-500")} />
163
+ )}
164
+ <span className={cn("text-xs", kpi.trendUp ? "text-green-600" : "text-red-600")}>
165
+ {kpi.trend}
166
+ </span>
167
+ </div>
168
+ <p className="text-[10px] text-slate-400 mt-1 hidden lg:block">{kpi.subtitle}</p>
169
+ </CardContent>
170
+ </Card>
171
+ );
172
+ })}
173
+ </div>
174
+ </div>
175
+
176
+ {/* Main Grid */}
177
+ <div className="grid gap-4 lg:grid-cols-12">
178
+ {/* Water Level Chart - Full width on mobile */}
179
+ <Card className="lg:col-span-7">
180
+ <CardHeader className="pb-2">
181
+ <CardTitle className="text-base lg:text-lg">State Water Storage Trends</CardTitle>
182
+ <CardDescription className="text-xs lg:text-sm">Current year vs Historical Average with Rainfall overlay</CardDescription>
183
+ </CardHeader>
184
+ <CardContent className="pl-0 lg:pl-2">
185
+ <div className="h-[250px] lg:h-[300px] w-full">
186
+ <ResponsiveContainer width="100%" height="100%">
187
+ <AreaChart data={waterLevelData} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
188
+ <defs>
189
+ <linearGradient id="colorLevel" x1="0" y1="0" x2="0" y2="1">
190
+ <stop offset="5%" stopColor="#3b82f6" stopOpacity={0.3}/>
191
+ <stop offset="95%" stopColor="#3b82f6" stopOpacity={0}/>
192
+ </linearGradient>
193
+ <linearGradient id="colorAvg" x1="0" y1="0" x2="0" y2="1">
194
+ <stop offset="5%" stopColor="#94a3b8" stopOpacity={0.3}/>
195
+ <stop offset="95%" stopColor="#94a3b8" stopOpacity={0}/>
196
+ </linearGradient>
197
+ </defs>
198
+ <XAxis dataKey="name" stroke="#888888" fontSize={10} tickLine={false} axisLine={false} />
199
+ <YAxis stroke="#888888" fontSize={10} tickLine={false} axisLine={false} tickFormatter={(value) => `${value}%`} />
200
+ <CartesianGrid strokeDasharray="3 3" vertical={false} stroke="#e2e8f0" />
201
+ <Tooltip
202
+ contentStyle={{ backgroundColor: '#fff', borderRadius: '8px', border: '1px solid #e2e8f0', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', fontSize: '12px' }}
203
+ />
204
+ <Legend wrapperStyle={{ fontSize: '12px' }} />
205
+ <Area type="monotone" dataKey="level" name="Current Storage %" stroke="#3b82f6" strokeWidth={2} fillOpacity={1} fill="url(#colorLevel)" />
206
+ <Area type="monotone" dataKey="avg" name="Historical Avg" stroke="#94a3b8" strokeWidth={2} fillOpacity={1} fill="url(#colorAvg)" />
207
+ </AreaChart>
208
+ </ResponsiveContainer>
209
+ </div>
210
+ </CardContent>
211
+ </Card>
212
+
213
+ {/* Regional Storage + Alerts */}
214
+ <div className="lg:col-span-5 space-y-4">
215
+ {/* Regional Storage Chart */}
216
+ <Card>
217
+ <CardHeader className="pb-2">
218
+ <CardTitle className="text-sm">Storage by Region</CardTitle>
219
+ </CardHeader>
220
+ <CardContent>
221
+ <div className="h-[150px] lg:h-[180px]">
222
+ <ResponsiveContainer width="100%" height="100%">
223
+ <BarChart data={regionData} layout="vertical" margin={{ top: 5, right: 30, left: 40, bottom: 5 }}>
224
+ <XAxis type="number" stroke="#888888" fontSize={10} tickLine={false} axisLine={false} tickFormatter={(v) => `${v}%`} />
225
+ <YAxis dataKey="name" type="category" stroke="#888888" fontSize={10} tickLine={false} axisLine={false} width={100} />
226
+ <Tooltip
227
+ contentStyle={{ fontSize: '12px', borderRadius: '8px' }}
228
+ formatter={(value: number) => [`${value}% Full`, 'Storage']}
229
+ />
230
+ <Bar dataKey="full" fill="#3b82f6" radius={[0, 4, 4, 0]} />
231
+ </BarChart>
232
+ </ResponsiveContainer>
233
+ </div>
234
+ </CardContent>
235
+ </Card>
236
+
237
+ {/* Quick Stats */}
238
+ <Card>
239
+ <CardContent className="p-4">
240
+ <div className="grid grid-cols-3 gap-4 text-center">
241
+ <div>
242
+ <div className="text-2xl font-bold text-blue-600">287</div>
243
+ <div className="text-xs text-slate-500">Total Dams</div>
244
+ </div>
245
+ <div>
246
+ <div className="text-2xl font-bold text-red-600">18</div>
247
+ <div className="text-xs text-slate-500">Critical</div>
248
+ </div>
249
+ <div>
250
+ <div className="text-2xl font-bold text-emerald-600">142</div>
251
+ <div className="text-xs text-slate-500">Indexed GRs</div>
252
+ </div>
253
+ </div>
254
+ </CardContent>
255
+ </Card>
256
+ </div>
257
+ </div>
258
+
259
+ {/* Recent Alerts Section */}
260
+ <Card>
261
+ <CardHeader className="pb-3">
262
+ <div className="flex items-center justify-between">
263
+ <div>
264
+ <CardTitle className="text-base lg:text-lg flex items-center gap-2">
265
+ <Bell className="h-5 w-5 text-slate-500" />
266
+ Recent Alerts & Notifications
267
+ </CardTitle>
268
+ <CardDescription className="text-xs lg:text-sm">System alerts, GR updates, and operational notifications</CardDescription>
269
+ </div>
270
+ <Button variant="outline" size="sm" className="hidden sm:flex text-xs h-8">
271
+ View All <ArrowRight className="ml-2 h-3 w-3" />
272
+ </Button>
273
+ </div>
274
+ </CardHeader>
275
+ <CardContent>
276
+ <div className="space-y-3">
277
+ {alerts.map((alert) => (
278
+ <div key={alert.id} className="flex items-start gap-3 p-3 rounded-lg bg-slate-50/50 border border-slate-100">
279
+ <div className={cn(
280
+ "mt-0.5 rounded-full p-1.5 shrink-0",
281
+ alert.severity === 'Critical' ? "bg-red-100 text-red-600" :
282
+ alert.severity === 'High' ? "bg-orange-100 text-orange-600" :
283
+ alert.severity === 'Medium' ? "bg-yellow-100 text-yellow-600" :
284
+ "bg-blue-100 text-blue-600"
285
+ )}>
286
+ {alert.type === 'gr' ? <FileText className="h-4 w-4" /> :
287
+ alert.type === 'dam' ? <Database className="h-4 w-4" /> :
288
+ alert.type === 'flood' ? <Waves className="h-4 w-4" /> :
289
+ <AlertTriangle className="h-4 w-4" />}
290
+ </div>
291
+ <div className="flex-1 min-w-0">
292
+ <div className="flex items-center gap-2 flex-wrap">
293
+ <p className="text-sm font-medium">{alert.title}</p>
294
+ <Badge variant={
295
+ alert.severity === 'Critical' ? 'destructive' :
296
+ alert.severity === 'High' ? 'outline' :
297
+ alert.severity === 'Medium' ? 'secondary' :
298
+ 'outline'
299
+ } className={cn(
300
+ "text-[10px] h-5",
301
+ alert.severity === 'High' ? 'border-orange-500 text-orange-700' : ''
302
+ )}>
303
+ {alert.severity}
304
+ </Badge>
305
+ </div>
306
+ <p className="text-xs text-slate-500 mt-0.5">{alert.location}</p>
307
+ <p className="text-xs text-slate-600 mt-1 hidden sm:block">{alert.description}</p>
308
+ </div>
309
+ <div className="text-xs text-slate-400 whitespace-nowrap shrink-0">{alert.time}</div>
310
+ </div>
311
+ ))}
312
+ </div>
313
+ <Button variant="outline" className="w-full mt-4 sm:hidden text-xs h-10">
314
+ View All Alerts <ArrowRight className="ml-2 h-4 w-4" />
315
+ </Button>
316
+ </CardContent>
317
+ </Card>
318
+ </div>
319
+ );
320
+ }
src/pages/Faq.tsx ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useMemo } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { Card, CardContent, CardHeader, CardTitle } from '@/src/components/ui/Card';
4
+ import { Input } from '@/src/components/ui/Input';
5
+ import { Button } from '@/src/components/ui/Button';
6
+ import { Badge } from '@/src/components/ui/Badge';
7
+ import {
8
+ Search, MessageSquare, ChevronDown, ChevronUp,
9
+ Waves, Database, Droplets, IndianRupee, FileText,
10
+ HelpCircle, ArrowRight, BookOpen
11
+ } from 'lucide-react';
12
+ import { cn } from '@/src/utils/cn';
13
+ import { faqData, faqCategories, getFAQsByCategory, FAQItem } from '@/src/data/mockFAQs';
14
+
15
+ const categoryIcons: Record<string, React.ElementType> = {
16
+ 'Flood Management': Waves,
17
+ 'Dam Operations': Database,
18
+ 'Irrigation & Water Rights': Droplets,
19
+ 'Compensation & Relief': IndianRupee,
20
+ 'GRs & Acts': FileText,
21
+ };
22
+
23
+ export function Faq() {
24
+ const navigate = useNavigate();
25
+ const [searchQuery, setSearchQuery] = useState('');
26
+ const [selectedCategory, setSelectedCategory] = useState<string>('All');
27
+ const [expandedId, setExpandedId] = useState<string | null>(null);
28
+
29
+ const filteredFAQs = useMemo(() => {
30
+ let faqs = selectedCategory === 'All'
31
+ ? faqData
32
+ : getFAQsByCategory(selectedCategory);
33
+
34
+ if (searchQuery) {
35
+ const q = searchQuery.toLowerCase();
36
+ faqs = faqs.filter(faq =>
37
+ faq.question.toLowerCase().includes(q) ||
38
+ faq.answer.toLowerCase().includes(q) ||
39
+ faq.keywords.some(k => k.toLowerCase().includes(q))
40
+ );
41
+ }
42
+
43
+ return faqs;
44
+ }, [searchQuery, selectedCategory]);
45
+
46
+ const handleAskAI = (question: string) => {
47
+ navigate(`/ai-assistant?q=${encodeURIComponent(question)}`);
48
+ };
49
+
50
+ const toggleExpand = (id: string) => {
51
+ setExpandedId(expandedId === id ? null : id);
52
+ };
53
+
54
+ return (
55
+ <div className="space-y-6 max-w-4xl mx-auto">
56
+ {/* Header */}
57
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
58
+ <div>
59
+ <h1 className="text-2xl font-bold tracking-tight text-slate-900">Frequently Asked Questions</h1>
60
+ <p className="text-slate-500">Find answers to common WRD queries</p>
61
+ </div>
62
+ <Button onClick={() => navigate('/ai-assistant')} className="bg-indigo-600 hover:bg-indigo-700 text-white">
63
+ <MessageSquare className="mr-2 h-4 w-4" />
64
+ Ask GR Helper
65
+ </Button>
66
+ </div>
67
+
68
+ {/* Search */}
69
+ <Card className="border-none shadow-md bg-white/50 backdrop-blur-sm">
70
+ <CardContent className="p-4 sm:p-6">
71
+ <div className="relative">
72
+ <Search className="absolute left-3 top-3 h-5 w-5 text-slate-400" />
73
+ <Input
74
+ type="text"
75
+ placeholder="Search questions..."
76
+ className="pl-10 h-12 text-base bg-white"
77
+ value={searchQuery}
78
+ onChange={(e) => setSearchQuery(e.target.value)}
79
+ />
80
+ </div>
81
+ </CardContent>
82
+ </Card>
83
+
84
+ {/* Category Tabs */}
85
+ <div className="flex flex-wrap gap-2">
86
+ <Badge
87
+ variant={selectedCategory === 'All' ? 'default' : 'secondary'}
88
+ className="cursor-pointer px-4 py-2 text-sm"
89
+ onClick={() => setSelectedCategory('All')}
90
+ >
91
+ All ({faqData.length})
92
+ </Badge>
93
+ {faqCategories.map(cat => {
94
+ const count = getFAQsByCategory(cat.name).length;
95
+ const Icon = categoryIcons[cat.name] || HelpCircle;
96
+ return (
97
+ <Badge
98
+ key={cat.id}
99
+ variant={selectedCategory === cat.name ? 'default' : 'secondary'}
100
+ className="cursor-pointer px-4 py-2 text-sm flex items-center gap-1"
101
+ onClick={() => setSelectedCategory(cat.name)}
102
+ >
103
+ <Icon className="h-3 w-3" />
104
+ {cat.name} ({count})
105
+ </Badge>
106
+ );
107
+ })}
108
+ </div>
109
+
110
+ {/* FAQ List */}
111
+ <div className="space-y-3">
112
+ {filteredFAQs.map((faq) => {
113
+ const isExpanded = expandedId === faq.id;
114
+ const CategoryIcon = categoryIcons[faq.category] || HelpCircle;
115
+
116
+ return (
117
+ <Card
118
+ key={faq.id}
119
+ className={cn(
120
+ "overflow-hidden transition-all border-slate-200",
121
+ isExpanded ? "shadow-md" : "hover:shadow-sm"
122
+ )}
123
+ >
124
+ <div
125
+ className="p-4 cursor-pointer"
126
+ onClick={() => toggleExpand(faq.id)}
127
+ >
128
+ <div className="flex items-start gap-3">
129
+ <div className={cn(
130
+ "rounded-full p-2 shrink-0",
131
+ faq.category === 'Flood Management' ? "bg-blue-100 text-blue-600" :
132
+ faq.category === 'Dam Operations' ? "bg-red-100 text-red-600" :
133
+ faq.category === 'Irrigation & Water Rights' ? "bg-cyan-100 text-cyan-600" :
134
+ faq.category === 'Compensation & Relief' ? "bg-emerald-100 text-emerald-600" :
135
+ "bg-purple-100 text-purple-600"
136
+ )}>
137
+ <CategoryIcon className="h-4 w-4" />
138
+ </div>
139
+ <div className="flex-1 min-w-0">
140
+ <div className="flex items-center justify-between gap-2">
141
+ <h3 className="text-sm font-semibold text-slate-900 pr-4">
142
+ {faq.question}
143
+ </h3>
144
+ {isExpanded ? (
145
+ <ChevronUp className="h-5 w-5 text-slate-400 shrink-0" />
146
+ ) : (
147
+ <ChevronDown className="h-5 w-5 text-slate-400 shrink-0" />
148
+ )}
149
+ </div>
150
+ {!isExpanded && (
151
+ <p className="text-xs text-slate-500 mt-1 line-clamp-1">
152
+ Click to expand answer
153
+ </p>
154
+ )}
155
+ </div>
156
+ </div>
157
+ </div>
158
+
159
+ {isExpanded && (
160
+ <CardContent className="pt-0 pb-4 px-4">
161
+ <div className="pl-11">
162
+ {/* Answer */}
163
+ <div className="text-sm text-slate-700 whitespace-pre-line leading-relaxed">
164
+ {faq.answer}
165
+ </div>
166
+
167
+ {/* Related GRs */}
168
+ {faq.relatedGRs && faq.relatedGRs.length > 0 && (
169
+ <div className="mt-4 pt-4 border-t border-slate-100">
170
+ <p className="text-xs font-medium text-slate-500 mb-2">Related GRs:</p>
171
+ <div className="flex flex-wrap gap-2">
172
+ {faq.relatedGRs.map(grId => (
173
+ <Badge
174
+ key={grId}
175
+ variant="outline"
176
+ className="text-xs cursor-pointer hover:bg-slate-100"
177
+ onClick={() => navigate(`/gr-finder?search=${grId}`)}
178
+ >
179
+ {grId}
180
+ </Badge>
181
+ ))}
182
+ </div>
183
+ </div>
184
+ )}
185
+
186
+ {/* Keywords */}
187
+ <div className="mt-3 flex flex-wrap gap-1">
188
+ {faq.keywords.map(keyword => (
189
+ <span
190
+ key={keyword}
191
+ className="text-[10px] bg-slate-100 text-slate-600 px-2 py-0.5 rounded"
192
+ >
193
+ {keyword}
194
+ </span>
195
+ ))}
196
+ </div>
197
+
198
+ {/* Ask AI Button */}
199
+ <div className="mt-4 pt-4 border-t border-slate-100">
200
+ <Button
201
+ variant="secondary"
202
+ size="sm"
203
+ className="bg-indigo-50 text-indigo-700 hover:bg-indigo-100 border-indigo-100"
204
+ onClick={() => handleAskAI(faq.question)}
205
+ >
206
+ <MessageSquare className="mr-2 h-4 w-4" />
207
+ Ask AI about this
208
+ </Button>
209
+ </div>
210
+ </div>
211
+ </CardContent>
212
+ )}
213
+ </Card>
214
+ );
215
+ })}
216
+
217
+ {filteredFAQs.length === 0 && (
218
+ <div className="text-center py-12">
219
+ <HelpCircle className="mx-auto h-12 w-12 text-slate-300 mb-4" />
220
+ <h3 className="text-lg font-medium text-slate-900">No questions found</h3>
221
+ <p className="text-slate-500">Try adjusting your search or category filter.</p>
222
+ <Button
223
+ variant="outline"
224
+ className="mt-4"
225
+ onClick={() => {
226
+ setSearchQuery('');
227
+ setSelectedCategory('All');
228
+ }}
229
+ >
230
+ Clear filters
231
+ </Button>
232
+ </div>
233
+ )}
234
+ </div>
235
+
236
+ {/* Still have questions? */}
237
+ <Card className="bg-gradient-to-r from-indigo-50 to-blue-50 border-indigo-100">
238
+ <CardContent className="p-6">
239
+ <div className="flex items-start gap-4">
240
+ <div className="rounded-full bg-indigo-100 p-3">
241
+ <BookOpen className="h-6 w-6 text-indigo-600" />
242
+ </div>
243
+ <div className="flex-1">
244
+ <h3 className="text-lg font-semibold text-slate-900">Still have questions?</h3>
245
+ <p className="text-sm text-slate-600 mt-1">
246
+ Can't find what you're looking for? Our GR Helper AI assistant can help you find answers
247
+ related to Government Resolutions, Acts, and WRD policies.
248
+ </p>
249
+ <Button
250
+ className="mt-4 bg-indigo-600 hover:bg-indigo-700"
251
+ onClick={() => navigate('/ai-assistant')}
252
+ >
253
+ Open GR Helper <ArrowRight className="ml-2 h-4 w-4" />
254
+ </Button>
255
+ </div>
256
+ </div>
257
+ </CardContent>
258
+ </Card>
259
+
260
+ {/* Quick Links */}
261
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
262
+ <Card className="border-slate-200 hover:shadow-md transition-shadow cursor-pointer" onClick={() => navigate('/gr-finder')}>
263
+ <CardContent className="p-4 flex items-center gap-3">
264
+ <div className="rounded-full bg-blue-100 p-2">
265
+ <FileText className="h-5 w-5 text-blue-600" />
266
+ </div>
267
+ <div>
268
+ <p className="text-sm font-medium text-slate-900">Browse Government Resolutions</p>
269
+ <p className="text-xs text-slate-500">Search and filter GRs by category, year, department</p>
270
+ </div>
271
+ <ArrowRight className="h-5 w-5 text-slate-400 ml-auto" />
272
+ </CardContent>
273
+ </Card>
274
+
275
+ <Card className="border-slate-200 hover:shadow-md transition-shadow cursor-pointer" onClick={() => navigate('/map')}>
276
+ <CardContent className="p-4 flex items-center gap-3">
277
+ <div className="rounded-full bg-cyan-100 p-2">
278
+ <Waves className="h-5 w-5 text-cyan-600" />
279
+ </div>
280
+ <div>
281
+ <p className="text-sm font-medium text-slate-900">View Flood Prediction Map</p>
282
+ <p className="text-xs text-slate-500">Real-time flood status and predictions</p>
283
+ </div>
284
+ <ArrowRight className="h-5 w-5 text-slate-400 ml-auto" />
285
+ </CardContent>
286
+ </Card>
287
+ </div>
288
+ </div>
289
+ );
290
+ }
src/pages/GisMap.tsx ADDED
@@ -0,0 +1,969 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
2
+ import { MapContainer, TileLayer, Marker, Popup, Circle, useMap, GeoJSON, useMapEvents } from 'react-leaflet';
3
+ import 'leaflet/dist/leaflet.css';
4
+ import L from 'leaflet';
5
+ import { Card, CardContent, CardHeader, CardTitle } from '@/src/components/ui/Card';
6
+ import { Badge } from '@/src/components/ui/Badge';
7
+ import { Button } from '@/src/components/ui/Button';
8
+ import {
9
+ Layers, Play, Pause, AlertTriangle, Menu, X, Droplets,
10
+ MapPin, Waves, ArrowUpRight, Wind, Activity, Gauge,
11
+ ChevronRight, Info
12
+ } from 'lucide-react';
13
+ import { cn } from '@/src/utils/cn';
14
+
15
+ // Fix for default marker icons in react-leaflet
16
+ delete (L.Icon.Default.prototype as any)._getIconUrl;
17
+ L.Icon.Default.mergeOptions({
18
+ iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png',
19
+ iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png',
20
+ shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
21
+ });
22
+
23
+ const customMarkerIcon = new L.Icon({
24
+ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-blue.png',
25
+ shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
26
+ iconSize: [25, 41],
27
+ iconAnchor: [12, 41],
28
+ popupAnchor: [1, -34],
29
+ shadowSize: [41, 41]
30
+ });
31
+
32
+ const warningMarkerIcon = new L.Icon({
33
+ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-orange.png',
34
+ shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
35
+ iconSize: [25, 41],
36
+ iconAnchor: [12, 41],
37
+ popupAnchor: [1, -34],
38
+ shadowSize: [41, 41]
39
+ });
40
+
41
+ const criticalMarkerIcon = new L.Icon({
42
+ iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
43
+ shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png',
44
+ iconSize: [25, 41],
45
+ iconAnchor: [12, 41],
46
+ popupAnchor: [1, -34],
47
+ shadowSize: [41, 41]
48
+ });
49
+
50
+ // Maharashtra major dams with realistic data
51
+ const dams = [
52
+ { id: 1, name: 'Koyna Dam', lat: 17.4014, lng: 73.7458, level: 92, capacity: '2.8 TMC', inflow: '45,000 cusecs', outflow: '32,000 cusecs', status: 'Critical', district: 'Satara' },
53
+ { id: 2, name: 'Jayakwadi Dam', lat: 19.4833, lng: 75.3167, level: 78, capacity: '2.9 TMC', inflow: '5,000 cusecs', outflow: '3,000 cusecs', status: 'Normal', district: 'Aurangabad' },
54
+ { id: 3, name: 'Ujani Dam', lat: 18.0736, lng: 75.1164, level: 85, capacity: '3.3 TMC', inflow: '15,000 cusecs', outflow: '12,000 cusecs', status: 'Warning', district: 'Solapur' },
55
+ { id: 4, name: 'Panshet Dam', lat: 18.3858, lng: 73.6158, level: 65, capacity: '0.3 TMC', inflow: '2,000 cusecs', outflow: '0 cusecs', status: 'Normal', district: 'Pune' },
56
+ { id: 5, name: 'Radhanagari Dam', lat: 16.4100, lng: 73.9900, level: 95, capacity: '0.2 TMC', inflow: '25,000 cusecs', outflow: '18,000 cusecs', status: 'Critical', district: 'Kolhapur' },
57
+ { id: 6, name: 'Ghatghar Dam', lat: 19.5670, lng: 73.4840, level: 88, capacity: '0.5 TMC', inflow: '8,000 cusecs', outflow: '5,000 cusecs', status: 'Warning', district: 'Thane' },
58
+ { id: 7, name: 'Bhandardara Dam', lat: 19.5500, lng: 73.7600, level: 72, capacity: '0.3 TMC', inflow: '3,000 cusecs', outflow: '1,500 cusecs', status: 'Normal', district: 'Ahmednagar' },
59
+ ];
60
+
61
+ // Flood prediction zones (Pur Sadrushya Paristhiti)
62
+ const floodZones = [
63
+ { id: 1, lat: 16.6965, lng: 74.2433, radius: 15000, risk: 'High', area: 'Kolhapur City & Surroundings', affectedPopulation: '500,000+', river: 'Panchaganga' },
64
+ { id: 2, lat: 16.8524, lng: 74.5815, radius: 12000, risk: 'Medium', area: 'Sangli City', affectedPopulation: '200,000+', river: 'Krishna' },
65
+ { id: 3, lat: 17.2777, lng: 74.1844, radius: 8000, risk: 'Low', area: 'Karad', affectedPopulation: '50,000+', river: 'Koyna' },
66
+ { id: 4, lat: 19.2000, lng: 74.7500, radius: 10000, risk: 'Medium', area: 'Beed District', affectedPopulation: '100,000+', river: 'Godavari' },
67
+ ];
68
+
69
+ // Maharashtra districts for boundary fetching
70
+ const maharashtraDistricts = [
71
+ 'Kolhapur', 'Sangli', 'Satara', 'Pune', 'Ahmednagar', 'Nashik', 'Aurangabad', 'Thane', 'Raigad', 'Ratnagiri'
72
+ ];
73
+
74
+ interface DistrictBoundary {
75
+ name: string;
76
+ geojson: any;
77
+ risk: 'High' | 'Medium' | 'Low' | 'None';
78
+ }
79
+
80
+ interface LiveCell {
81
+ id: string;
82
+ name: string;
83
+ lat: number;
84
+ lng: number;
85
+ intensity: number; // 0..1
86
+ type: 'rain' | 'flood';
87
+ }
88
+
89
+ // Rain drop particle for animation
90
+ interface RainDrop {
91
+ x: number;
92
+ y: number;
93
+ speed: number;
94
+ length: number;
95
+ opacity: number;
96
+ }
97
+
98
+ // Flood wave parameters
99
+ interface FloodWave {
100
+ offset: number;
101
+ amplitude: number;
102
+ frequency: number;
103
+ speed: number;
104
+ }
105
+
106
+ // Custom canvas layer for rain animation inside polygons
107
+ class RainCanvasLayer extends L.Layer {
108
+ private _canvas: HTMLCanvasElement | null = null;
109
+ private _ctx: CanvasRenderingContext2D | null = null;
110
+ private _rainDrops: RainDrop[] = [];
111
+ private _polygons: L.Polygon[] = [];
112
+ private _animationFrame: number = 0;
113
+ private _isAnimating: boolean = false;
114
+
115
+ constructor(polygons: L.Polygon[]) {
116
+ super();
117
+ this._polygons = polygons;
118
+ }
119
+
120
+ onAdd(map: L.Map) {
121
+ this._canvas = L.DomUtil.create('canvas', 'leaflet-rain-layer');
122
+ this._canvas.style.position = 'absolute';
123
+ this._canvas.style.pointerEvents = 'none';
124
+ map.getPanes().overlayPane.appendChild(this._canvas);
125
+ this._ctx = this._canvas.getContext('2d');
126
+
127
+ // Initialize rain drops
128
+ this._initRainDrops();
129
+
130
+ // Start animation
131
+ this._isAnimating = true;
132
+ this._animate();
133
+
134
+ map.on('moveend', this._updateCanvasSize, this);
135
+ map.on('zoomend', this._updateCanvasSize, this);
136
+ this._updateCanvasSize();
137
+
138
+ return this;
139
+ }
140
+
141
+ onRemove(map: L.Map) {
142
+ this._isAnimating = false;
143
+ if (this._animationFrame) {
144
+ cancelAnimationFrame(this._animationFrame);
145
+ }
146
+ if (this._canvas && this._canvas.parentNode) {
147
+ this._canvas.parentNode.removeChild(this._canvas);
148
+ }
149
+ map.off('moveend', this._updateCanvasSize, this);
150
+ map.off('zoomend', this._updateCanvasSize, this);
151
+ return this;
152
+ }
153
+
154
+ _initRainDrops() {
155
+ this._rainDrops = [];
156
+ for (let i = 0; i < 150; i++) {
157
+ this._rainDrops.push({
158
+ x: Math.random() * 2000,
159
+ y: Math.random() * 2000,
160
+ speed: 8 + Math.random() * 12,
161
+ length: 10 + Math.random() * 20,
162
+ opacity: 0.2 + Math.random() * 0.4
163
+ });
164
+ }
165
+ }
166
+
167
+ _updateCanvasSize() {
168
+ const map = this._map;
169
+ if (!map || !this._canvas) return;
170
+
171
+ const size = map.getSize();
172
+ const topLeft = map.containerPointToLayerPoint([0, 0]);
173
+
174
+ L.DomUtil.setPosition(this._canvas, topLeft);
175
+ this._canvas.width = size.x;
176
+ this._canvas.height = size.y;
177
+ }
178
+
179
+ _animate = () => {
180
+ if (!this._isAnimating || !this._ctx || !this._canvas || !this._map) return;
181
+
182
+ const ctx = this._ctx;
183
+ const canvas = this._canvas;
184
+ const map = this._map;
185
+
186
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
187
+
188
+ // Draw rain drops clipped to polygons
189
+ ctx.save();
190
+
191
+ // Create clipping path from polygons
192
+ ctx.beginPath();
193
+ for (const polygon of this._polygons) {
194
+ const latLngs = polygon.getLatLngs() as L.LatLng[][];
195
+ if (latLngs[0]) {
196
+ for (const ring of latLngs) {
197
+ const firstPoint = map.latLngToContainerPoint(ring[0]);
198
+ ctx.moveTo(firstPoint.x, firstPoint.y);
199
+ for (let i = 1; i < ring.length; i++) {
200
+ const point = map.latLngToContainerPoint(ring[i]);
201
+ ctx.lineTo(point.x, point.y);
202
+ }
203
+ ctx.closePath();
204
+ }
205
+ }
206
+ }
207
+ ctx.clip();
208
+
209
+ // Draw rain drops
210
+ for (const drop of this._rainDrops) {
211
+ const screenPoint = map.containerPointToLayerPoint([drop.x % canvas.width, drop.y % canvas.height]);
212
+
213
+ ctx.beginPath();
214
+ ctx.strokeStyle = `rgba(100, 180, 255, ${drop.opacity * 0.6})`;
215
+ ctx.lineWidth = 1.5;
216
+ ctx.moveTo(screenPoint.x, screenPoint.y);
217
+ ctx.lineTo(screenPoint.x, screenPoint.y + drop.length);
218
+ ctx.stroke();
219
+
220
+ // Update position
221
+ drop.y += drop.speed;
222
+ if (drop.y > canvas.height + drop.length) {
223
+ drop.y = -drop.length;
224
+ drop.x = Math.random() * canvas.width;
225
+ }
226
+ }
227
+
228
+ ctx.restore();
229
+
230
+ this._animationFrame = requestAnimationFrame(this._animate);
231
+ }
232
+ }
233
+
234
+ // Custom canvas layer for flood wave animation inside polygons
235
+ class FloodWaveCanvasLayer extends L.Layer {
236
+ private _canvas: HTMLCanvasElement | null = null;
237
+ private _ctx: CanvasRenderingContext2D | null = null;
238
+ private _polygons: L.Polygon[] = [];
239
+ private _animationFrame: number = 0;
240
+ private _isAnimating: boolean = false;
241
+ private _time: number = 0;
242
+
243
+ constructor(polygons: L.Polygon[]) {
244
+ super();
245
+ this._polygons = polygons;
246
+ }
247
+
248
+ onAdd(map: L.Map) {
249
+ this._canvas = L.DomUtil.create('canvas', 'leaflet-flood-layer');
250
+ this._canvas.style.position = 'absolute';
251
+ this._canvas.style.pointerEvents = 'none';
252
+ map.getPanes().overlayPane.appendChild(this._canvas);
253
+ this._ctx = this._canvas.getContext('2d');
254
+
255
+ this._isAnimating = true;
256
+ this._animate();
257
+
258
+ map.on('moveend', this._updateCanvasSize, this);
259
+ map.on('zoomend', this._updateCanvasSize, this);
260
+ this._updateCanvasSize();
261
+
262
+ return this;
263
+ }
264
+
265
+ onRemove(map: L.Map) {
266
+ this._isAnimating = false;
267
+ if (this._animationFrame) {
268
+ cancelAnimationFrame(this._animationFrame);
269
+ }
270
+ if (this._canvas && this._canvas.parentNode) {
271
+ this._canvas.parentNode.removeChild(this._canvas);
272
+ }
273
+ map.off('moveend', this._updateCanvasSize, this);
274
+ map.off('zoomend', this._updateCanvasSize, this);
275
+ return this;
276
+ }
277
+
278
+ _updateCanvasSize() {
279
+ const map = this._map;
280
+ if (!map || !this._canvas) return;
281
+
282
+ const size = map.getSize();
283
+ const topLeft = map.containerPointToLayerPoint([0, 0]);
284
+
285
+ L.DomUtil.setPosition(this._canvas, topLeft);
286
+ this._canvas.width = size.x;
287
+ this._canvas.height = size.y;
288
+ }
289
+
290
+ _animate = () => {
291
+ if (!this._isAnimating || !this._ctx || !this._canvas || !this._map) return;
292
+
293
+ const ctx = this._ctx;
294
+ const canvas = this._canvas;
295
+ const map = this._map;
296
+
297
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
298
+
299
+ this._time += 0.03;
300
+
301
+ ctx.save();
302
+
303
+ // Create clipping path from polygons
304
+ ctx.beginPath();
305
+ for (const polygon of this._polygons) {
306
+ const latLngs = polygon.getLatLngs() as L.LatLng[][];
307
+ if (latLngs[0]) {
308
+ for (const ring of latLngs) {
309
+ const firstPoint = map.latLngToContainerPoint(ring[0]);
310
+ ctx.moveTo(firstPoint.x, firstPoint.y);
311
+ for (let i = 1; i < ring.length; i++) {
312
+ const point = map.latLngToContainerPoint(ring[i]);
313
+ ctx.lineTo(point.x, point.y);
314
+ }
315
+ ctx.closePath();
316
+ }
317
+ }
318
+ }
319
+ ctx.clip();
320
+
321
+ // Draw flood wave layers with low opacity
322
+ const waveCount = 5;
323
+ for (let w = 0; w < waveCount; w++) {
324
+ const waveOffset = (this._time + w * 0.4) % 1;
325
+ const baseOpacity = 0.08 - w * 0.012;
326
+
327
+ ctx.beginPath();
328
+
329
+ // Create wave pattern across the clipped area
330
+ for (let x = 0; x < canvas.width; x += 3) {
331
+ const waveY = canvas.height * 0.3 + Math.sin((x * 0.02) + this._time * 2 + w) * 15 + waveOffset * canvas.height * 0.5;
332
+
333
+ if (x === 0) {
334
+ ctx.moveTo(x, waveY);
335
+ } else {
336
+ ctx.lineTo(x, waveY);
337
+ }
338
+ }
339
+
340
+ // Complete the wave shape
341
+ ctx.lineTo(canvas.width, canvas.height);
342
+ ctx.lineTo(0, canvas.height);
343
+ ctx.closePath();
344
+
345
+ // Gradient fill for water effect
346
+ const gradient = ctx.createLinearGradient(0, canvas.height * 0.3, 0, canvas.height);
347
+ gradient.addColorStop(0, `rgba(34, 197, 94, ${baseOpacity})`);
348
+ gradient.addColorStop(0.5, `rgba(14, 165, 233, ${baseOpacity * 0.8})`);
349
+ gradient.addColorStop(1, `rgba(59, 130, 246, ${baseOpacity * 0.5})`);
350
+
351
+ ctx.fillStyle = gradient;
352
+ ctx.fill();
353
+ }
354
+
355
+ // Add ripple circles
356
+ for (let i = 0; i < 8; i++) {
357
+ const rippleX = (Math.sin(this._time * 0.5 + i * 1.3) + 1) * canvas.width * 0.5;
358
+ const rippleY = (Math.cos(this._time * 0.3 + i * 1.7) + 1) * canvas.height * 0.5;
359
+ const rippleRadius = 20 + Math.sin(this._time * 2 + i) * 10;
360
+ const rippleOpacity = 0.05 + Math.sin(this._time * 3 + i) * 0.03;
361
+
362
+ ctx.beginPath();
363
+ ctx.arc(rippleX, rippleY, rippleRadius, 0, Math.PI * 2);
364
+ ctx.strokeStyle = `rgba(96, 165, 250, ${rippleOpacity})`;
365
+ ctx.lineWidth = 2;
366
+ ctx.stroke();
367
+ }
368
+
369
+ ctx.restore();
370
+
371
+ this._animationFrame = requestAnimationFrame(this._animate);
372
+ }
373
+ }
374
+
375
+ // Component to handle map animations
376
+ function MapAnimator({ isPlaying, timeStep }: { isPlaying: boolean; timeStep: number }) {
377
+ const map = useMap();
378
+
379
+ useEffect(() => {
380
+ if (isPlaying) {
381
+ // Simulate flood spread animation
382
+ }
383
+ }, [isPlaying, timeStep, map]);
384
+
385
+ return null;
386
+ }
387
+
388
+ // Component to fit bounds to Maharashtra
389
+ function MaharashtraBounds() {
390
+ const map = useMap();
391
+
392
+ useEffect(() => {
393
+ map.fitBounds([
394
+ [15.6, 72.8],
395
+ [22.0, 80.0]
396
+ ], { padding: [20, 20] });
397
+ }, [map]);
398
+
399
+ return null;
400
+ }
401
+
402
+ // Component to manage live animation layers (rain + flood)
403
+ function LiveAnimationManager({
404
+ isActive,
405
+ polygons
406
+ }: {
407
+ isActive: boolean;
408
+ polygons: L.Polygon[];
409
+ }) {
410
+ const map = useMap();
411
+ const rainLayerRef = useRef<RainCanvasLayer | null>(null);
412
+ const floodLayerRef = useRef<FloodWaveCanvasLayer | null>(null);
413
+
414
+ useEffect(() => {
415
+ if (isActive && polygons.length > 0) {
416
+ // Add rain animation layer
417
+ if (!rainLayerRef.current) {
418
+ rainLayerRef.current = new RainCanvasLayer(polygons);
419
+ rainLayerRef.current.addTo(map);
420
+ }
421
+ // Add flood wave animation layer
422
+ if (!floodLayerRef.current) {
423
+ floodLayerRef.current = new FloodWaveCanvasLayer(polygons);
424
+ floodLayerRef.current.addTo(map);
425
+ }
426
+ } else {
427
+ // Remove animation layers when not active
428
+ if (rainLayerRef.current) {
429
+ map.removeLayer(rainLayerRef.current);
430
+ rainLayerRef.current = null;
431
+ }
432
+ if (floodLayerRef.current) {
433
+ map.removeLayer(floodLayerRef.current);
434
+ floodLayerRef.current = null;
435
+ }
436
+ }
437
+
438
+ return () => {
439
+ if (rainLayerRef.current) {
440
+ map.removeLayer(rainLayerRef.current);
441
+ rainLayerRef.current = null;
442
+ }
443
+ if (floodLayerRef.current) {
444
+ map.removeLayer(floodLayerRef.current);
445
+ floodLayerRef.current = null;
446
+ }
447
+ };
448
+ }, [isActive, polygons, map]);
449
+
450
+ return null;
451
+ }
452
+
453
+ export function GisMap() {
454
+ const [isPlaying, setIsPlaying] = useState(false);
455
+ const [timeStep, setTimeStep] = useState(0);
456
+ const [activeLayer, setActiveLayer] = useState<'current' | 'prediction'>('prediction');
457
+ const [showMobileControls, setShowMobileControls] = useState(false);
458
+ const [districtBoundaries, setDistrictBoundaries] = useState<DistrictBoundary[]>([]);
459
+ const [loadingBoundaries, setLoadingBoundaries] = useState(false);
460
+ const [selectedDam, setSelectedDam] = useState<number | null>(null);
461
+ const [showDistricts, setShowDistricts] = useState(true);
462
+ const [liveTick, setLiveTick] = useState(0);
463
+ const districtPolygonsRef = useRef<L.Polygon[]>([]);
464
+ const [polygonsReady, setPolygonsReady] = useState(false);
465
+
466
+ useEffect(() => {
467
+ const loadCachedBoundaries = async () => {
468
+ setLoadingBoundaries(true);
469
+ try {
470
+ const res = await fetch('/data/maharashtra-districts.geojson', {
471
+ headers: { 'Accept': 'application/json' }
472
+ });
473
+ const geo = await res.json();
474
+
475
+ const features: any[] = Array.isArray(geo?.features) ? geo.features : [];
476
+ const boundaries: DistrictBoundary[] = features.map((feature: any, idx: number) => {
477
+ const district = String(feature?.properties?.name || feature?.properties?.NAME || feature?.properties?.district || `District ${idx + 1}`);
478
+ const risk = floodZones.some(z => z.area.toLowerCase().includes(district.toLowerCase()))
479
+ ? 'High'
480
+ : ['Satara', 'Thane', 'Raigad', 'Ratnagiri'].some((d) => district.toLowerCase().includes(d.toLowerCase()))
481
+ ? 'Medium'
482
+ : 'Low';
483
+ return { name: district, geojson: feature, risk };
484
+ });
485
+
486
+ setDistrictBoundaries(boundaries);
487
+ } catch (e) {
488
+ console.error('Failed to load cached district boundaries:', e);
489
+ setDistrictBoundaries([]);
490
+ } finally {
491
+ setLoadingBoundaries(false);
492
+ }
493
+ };
494
+
495
+ loadCachedBoundaries();
496
+ }, []);
497
+
498
+ // Create polygon references for animation clipping
499
+ const onEachDistrictFeature = useCallback((feature: any, layer: L.Layer) => {
500
+ if (layer instanceof L.Polygon) {
501
+ districtPolygonsRef.current.push(layer);
502
+ setPolygonsReady(true);
503
+ }
504
+ }, []);
505
+
506
+ useEffect(() => {
507
+ let interval: NodeJS.Timeout;
508
+ if (activeLayer === 'current') {
509
+ interval = setInterval(() => setLiveTick((t) => t + 1), 900);
510
+ }
511
+ return () => clearInterval(interval);
512
+ }, [activeLayer]);
513
+
514
+ useEffect(() => {
515
+ let interval: NodeJS.Timeout;
516
+ if (isPlaying) {
517
+ interval = setInterval(() => {
518
+ setTimeStep((prev) => (prev >= 48 ? 0 : prev + 1));
519
+ }, 800);
520
+ }
521
+ return () => clearInterval(interval);
522
+ }, [isPlaying]);
523
+
524
+ const togglePlay = useCallback(() => setIsPlaying(!isPlaying), [isPlaying]);
525
+
526
+ const getDamIcon = (status: string) => {
527
+ switch (status) {
528
+ case 'Critical': return criticalMarkerIcon;
529
+ case 'Warning': return warningMarkerIcon;
530
+ default: return customMarkerIcon;
531
+ }
532
+ };
533
+
534
+ const districtStyle = (risk: string) => {
535
+ const colors: Record<string, { color: string; fillColor: string; fillOpacity: number }> = {
536
+ High: { color: '#dc2626', fillColor: '#dc2626', fillOpacity: 0.15 },
537
+ Medium: { color: '#ea580c', fillColor: '#ea580c', fillOpacity: 0.1 },
538
+ Low: { color: '#ca8a04', fillColor: '#ca8a04', fillOpacity: 0.05 },
539
+ None: { color: '#64748b', fillColor: '#64748b', fillOpacity: 0.02 }
540
+ };
541
+ return colors[risk] || colors.None;
542
+ };
543
+
544
+ const liveCells: LiveCell[] = [
545
+ { id: 'konkan-rain', name: 'Konkan Heavy Rain', lat: 17.2, lng: 73.3, intensity: 0.9, type: 'rain' },
546
+ { id: 'pune-rain', name: 'Pune Thunderstorm', lat: 18.52, lng: 73.86, intensity: 0.6, type: 'rain' },
547
+ { id: 'kolhapur-flood', name: 'Kolhapur Flood Pulse', lat: 16.70, lng: 74.24, intensity: 0.85, type: 'flood' },
548
+ { id: 'sangli-flood', name: 'Sangli River Rise', lat: 16.85, lng: 74.58, intensity: 0.55, type: 'flood' },
549
+ ];
550
+
551
+ return (
552
+ <div className="flex flex-col h-full w-full min-h-0">
553
+ {/* Header */}
554
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2 px-4 py-3 bg-white border-b border-slate-200 shrink-0">
555
+ <div>
556
+ <h1 className="text-lg lg:text-2xl font-bold tracking-tight text-slate-900 flex items-center gap-2">
557
+ <Droplets className="h-5 w-5 lg:h-6 lg:w-6 text-blue-600" />
558
+ Flood Prediction GIS
559
+ </h1>
560
+ <p className="text-xs lg:text-sm text-slate-500">Pur Sadrushya Paristhiti Analysis & Live Monitoring</p>
561
+ </div>
562
+ <div className="flex items-center gap-2">
563
+ <div className="flex bg-slate-100 rounded-lg p-1">
564
+ <Button
565
+ variant={activeLayer === 'current' ? 'default' : 'ghost'}
566
+ onClick={() => setActiveLayer('current')}
567
+ size="sm"
568
+ className="text-xs h-7 lg:h-8"
569
+ >
570
+ Live Data
571
+ </Button>
572
+ <Button
573
+ variant={activeLayer === 'prediction' ? 'default' : 'ghost'}
574
+ onClick={() => setActiveLayer('prediction')}
575
+ size="sm"
576
+ className="text-xs h-7 lg:h-8"
577
+ >
578
+ +48h Prediction
579
+ </Button>
580
+ </div>
581
+ <Button
582
+ variant="outline"
583
+ size="icon"
584
+ className="lg:hidden h-8 w-8"
585
+ onClick={() => setShowMobileControls(true)}
586
+ >
587
+ <Menu className="h-4 w-4" />
588
+ </Button>
589
+ </div>
590
+ </div>
591
+
592
+ {/* Main Content */}
593
+ <div className="flex-1 flex flex-col lg:grid lg:grid-cols-4 min-h-0">
594
+ {/* Map */}
595
+ <Card className="lg:col-span-3 relative overflow-hidden border-0 rounded-none lg:rounded-lg lg:border lg:m-2">
596
+ {/* Time Control Overlay */}
597
+ <div className="absolute top-3 left-3 right-3 lg:right-auto lg:min-w-[200px] z-[1000] bg-white/95 backdrop-blur-sm p-2 rounded-lg shadow-md flex items-center justify-between">
598
+ <div className="flex items-center gap-2">
599
+ <span className="text-sm font-semibold text-slate-700">
600
+ +{timeStep}h Forecast
601
+ </span>
602
+ {isPlaying && (
603
+ <span className="text-xs text-slate-500 animate-pulse">Live</span>
604
+ )}
605
+ </div>
606
+ <div className="flex items-center gap-1">
607
+ <Button size="icon" variant="ghost" onClick={togglePlay} className="h-8 w-8">
608
+ {isPlaying ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
609
+ </Button>
610
+ </div>
611
+ </div>
612
+
613
+ {/* Legend Overlay */}
614
+ <div className="absolute bottom-3 left-3 z-[1000] bg-white/95 backdrop-blur-sm p-2 rounded-lg shadow-md hidden sm:block">
615
+ <div className="text-xs font-medium text-slate-700 mb-1">Risk Zones</div>
616
+ <div className="space-y-1">
617
+ <div className="flex items-center gap-2 text-xs">
618
+ <span className="w-3 h-3 rounded-full bg-red-500/60"></span>
619
+ <span>High Risk</span>
620
+ </div>
621
+ <div className="flex items-center gap-2 text-xs">
622
+ <span className="w-3 h-3 rounded-full bg-orange-500/40"></span>
623
+ <span>Medium Risk</span>
624
+ </div>
625
+ <div className="flex items-center gap-2 text-xs">
626
+ <span className="w-3 h-3 rounded-full bg-yellow-500/20"></span>
627
+ <span>Low Risk</span>
628
+ </div>
629
+ </div>
630
+ </div>
631
+
632
+ <MapContainer
633
+ center={[18.5204, 73.8567]}
634
+ zoom={7}
635
+ className="h-full w-full"
636
+ style={{ zIndex: 0 }}
637
+ >
638
+ <TileLayer
639
+ attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
640
+ url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
641
+ />
642
+
643
+ <MaharashtraBounds />
644
+ <MapAnimator isPlaying={isPlaying} timeStep={timeStep} />
645
+
646
+ {/* Live Animation Layers - Rain drops and flood waves inside district boundaries */}
647
+ <LiveAnimationManager
648
+ isActive={activeLayer === 'current' && showDistricts}
649
+ polygons={districtPolygonsRef.current}
650
+ />
651
+
652
+ {/* District Boundaries */}
653
+ {showDistricts && districtBoundaries.map((district) => (
654
+ <GeoJSON
655
+ key={district.name}
656
+ data={district.geojson}
657
+ style={() => districtStyle(district.risk)}
658
+ onEachFeature={onEachDistrictFeature}
659
+ >
660
+ <Popup>
661
+ <div className="p-1">
662
+ <h3 className="font-bold text-sm">{district.name} District</h3>
663
+ <p className="text-xs mt-1">
664
+ Flood Risk: <span className={cn(
665
+ "font-semibold",
666
+ district.risk === 'High' ? "text-red-600" :
667
+ district.risk === 'Medium' ? "text-orange-600" :
668
+ "text-yellow-600"
669
+ )}>{district.risk}</span>
670
+ </p>
671
+ </div>
672
+ </Popup>
673
+ </GeoJSON>
674
+ ))}
675
+
676
+ {/* Dams */}
677
+ {dams.map(dam => (
678
+ <Marker
679
+ key={dam.id}
680
+ position={[dam.lat, dam.lng]}
681
+ icon={getDamIcon(dam.status)}
682
+ eventHandlers={{
683
+ click: () => setSelectedDam(dam.id)
684
+ }}
685
+ >
686
+ <Popup>
687
+ <div className="p-2 min-w-[200px]">
688
+ <div className="flex items-center justify-between mb-2">
689
+ <h3 className="font-bold text-sm">{dam.name}</h3>
690
+ <Badge variant={dam.status === 'Critical' ? 'destructive' : dam.status === 'Warning' ? 'outline' : 'secondary'} className={cn(
691
+ "text-xs",
692
+ dam.status === 'Warning' ? 'border-orange-500 text-orange-700 bg-orange-50' : ''
693
+ )}>
694
+ {dam.status}
695
+ </Badge>
696
+ </div>
697
+ <div className="space-y-1.5 text-xs">
698
+ <div className="flex justify-between">
699
+ <span className="text-slate-500">District:</span>
700
+ <span className="font-medium">{dam.district}</span>
701
+ </div>
702
+ <div className="flex justify-between">
703
+ <span className="text-slate-500">Storage:</span>
704
+ <span className={cn(
705
+ "font-medium",
706
+ dam.level > 90 ? "text-red-600" : dam.level > 75 ? "text-orange-600" : "text-green-600"
707
+ )}>{dam.level}% ({dam.capacity})</span>
708
+ </div>
709
+ <div className="flex justify-between">
710
+ <span className="text-slate-500">Inflow:</span>
711
+ <span className="font-medium text-blue-600">{dam.inflow}</span>
712
+ </div>
713
+ <div className="flex justify-between">
714
+ <span className="text-slate-500">Outflow:</span>
715
+ <span className="font-medium text-cyan-600">{dam.outflow}</span>
716
+ </div>
717
+ </div>
718
+ </div>
719
+ </Popup>
720
+ </Marker>
721
+ ))}
722
+
723
+ {/* Flood Risk Zones */}
724
+ {activeLayer === 'prediction' && floodZones.map(zone => {
725
+ const currentRadius = isPlaying ? zone.radius * (1 + (timeStep * 0.015)) : zone.radius;
726
+ const opacity = zone.risk === 'High' ? 0.5 : zone.risk === 'Medium' ? 0.35 : 0.2;
727
+ const color = zone.risk === 'High' ? '#dc2626' : zone.risk === 'Medium' ? '#ea580c' : '#ca8a04';
728
+
729
+ return (
730
+ <Circle
731
+ key={zone.id}
732
+ center={[zone.lat, zone.lng]}
733
+ radius={currentRadius}
734
+ pathOptions={{
735
+ color: color,
736
+ fillColor: color,
737
+ fillOpacity: isPlaying ? opacity * (0.8 + Math.sin(timeStep * 0.2) * 0.2) : opacity,
738
+ weight: 2
739
+ }}
740
+ >
741
+ <Popup>
742
+ <div className="p-2 min-w-[180px]">
743
+ <div className="flex items-center gap-2 mb-2">
744
+ <AlertTriangle className={cn(
745
+ "h-4 w-4",
746
+ zone.risk === 'High' ? "text-red-500" :
747
+ zone.risk === 'Medium' ? "text-orange-500" :
748
+ "text-yellow-500"
749
+ )} />
750
+ <h3 className="font-bold text-sm">{zone.area}</h3>
751
+ </div>
752
+ <div className="space-y-1 text-xs">
753
+ <p><span className="text-slate-500">Risk Level:</span> <span className="font-semibold">{zone.risk}</span></p>
754
+ <p><span className="text-slate-500">River:</span> <span className="font-medium">{zone.river}</span></p>
755
+ <p><span className="text-slate-500">Population at Risk:</span> <span className="font-medium">{zone.affectedPopulation}</span></p>
756
+ <p><span className="text-slate-500">Impact Radius:</span> <span className="font-medium">{Math.round(currentRadius / 1000)} km</span></p>
757
+ </div>
758
+ </div>
759
+ </Popup>
760
+ </Circle>
761
+ );
762
+ })}
763
+
764
+ {/* Live layer: Rainfall/Flood indicator circles with pulsing opacity */}
765
+ {activeLayer === 'current' && liveCells.map((cell) => {
766
+ const phase = (liveTick % 20) / 20;
767
+ const pulse = 0.65 + Math.sin((phase * 2 * Math.PI) + (cell.lat + cell.lng)) * 0.25;
768
+ const intensity = Math.max(0.15, Math.min(1, cell.intensity * pulse));
769
+ const baseRadius = cell.type === 'rain' ? 12000 : 15000;
770
+ const radius = baseRadius * (0.75 + phase * 0.5);
771
+ const color = cell.type === 'rain' ? '#3b82f6' : '#06b6d4';
772
+ const fill = cell.type === 'rain' ? '#60a5fa' : '#22d3ee';
773
+
774
+ return (
775
+ <Circle
776
+ key={cell.id}
777
+ center={[cell.lat, cell.lng]}
778
+ radius={radius}
779
+ pathOptions={{
780
+ color,
781
+ fillColor: fill,
782
+ fillOpacity: 0.06 + intensity * 0.12,
783
+ weight: 1,
784
+ dashArray: cell.type === 'rain' ? '4 8' : undefined,
785
+ }}
786
+ >
787
+ <Popup>
788
+ <div className="p-2 min-w-[180px]">
789
+ <div className="flex items-center justify-between">
790
+ <h3 className="font-bold text-sm">{cell.name}</h3>
791
+ <Badge className={cn(
792
+ 'text-xs',
793
+ cell.type === 'rain' ? 'bg-blue-600' : 'bg-cyan-600'
794
+ )}>
795
+ {cell.type === 'rain' ? 'Rain' : 'Flood'}
796
+ </Badge>
797
+ </div>
798
+ <p className="text-xs text-slate-600 mt-1">
799
+ Intensity: <span className="font-semibold">{Math.round(intensity * 100)}%</span>
800
+ </p>
801
+ <p className="text-[11px] text-slate-500 mt-1">
802
+ Animation visible inside district boundaries.
803
+ </p>
804
+ </div>
805
+ </Popup>
806
+ </Circle>
807
+ );
808
+ })}
809
+ </MapContainer>
810
+ </Card>
811
+
812
+ {/* Sidebar - Desktop */}
813
+ <div className="hidden lg:block space-y-3 p-2 overflow-y-auto">
814
+ <Card>
815
+ <CardHeader className="pb-2">
816
+ <CardTitle className="text-sm flex items-center">
817
+ <AlertTriangle className="h-4 w-4 mr-2 text-red-500" />
818
+ Critical Alert Areas
819
+ </CardTitle>
820
+ </CardHeader>
821
+ <CardContent className="space-y-3">
822
+ <div className="p-3 bg-red-50 border border-red-100 rounded-md">
823
+ <div className="flex items-center justify-between mb-1">
824
+ <h4 className="text-sm font-semibold text-red-900">Kolhapur City</h4>
825
+ <Badge variant="destructive" className="text-xs">Critical</Badge>
826
+ </div>
827
+ <p className="text-xs text-red-700">Panchaganga river flowing above danger mark. Evacuation recommended for low-lying areas.</p>
828
+ <p className="text-xs text-red-600 mt-1 font-medium">500,000+ people at risk</p>
829
+ </div>
830
+ <div className="p-3 bg-orange-50 border border-orange-100 rounded-md">
831
+ <div className="flex items-center justify-between mb-1">
832
+ <h4 className="text-sm font-semibold text-orange-900">Sangli District</h4>
833
+ <Badge variant="outline" className="text-xs border-orange-500 text-orange-700">High</Badge>
834
+ </div>
835
+ <p className="text-xs text-orange-700">Water level rising steadily. Keep emergency response teams on standby.</p>
836
+ </div>
837
+ <div className="p-3 bg-yellow-50 border border-yellow-100 rounded-md">
838
+ <h4 className="text-sm font-semibold text-yellow-900">Satara Region</h4>
839
+ <p className="text-xs text-yellow-700 mt-1">Koyna dam at 92% capacity. Monitor discharge rates.</p>
840
+ </div>
841
+ </CardContent>
842
+ </Card>
843
+
844
+ <Card>
845
+ <CardHeader className="pb-2">
846
+ <CardTitle className="text-sm flex items-center">
847
+ <Layers className="h-4 w-4 mr-2 text-blue-500" />
848
+ Dam Status Overview
849
+ </CardTitle>
850
+ </CardHeader>
851
+ <CardContent className="space-y-2">
852
+ {dams.slice(0, 5).map(dam => (
853
+ <div
854
+ key={dam.id}
855
+ className={cn(
856
+ "flex justify-between items-center text-sm p-2 rounded-md cursor-pointer transition-colors",
857
+ selectedDam === dam.id ? "bg-slate-100" : "hover:bg-slate-50"
858
+ )}
859
+ onClick={() => setSelectedDam(dam.id)}
860
+ >
861
+ <div>
862
+ <span className="font-medium text-slate-700 block">{dam.name}</span>
863
+ <span className="text-xs text-slate-500">{dam.district}</span>
864
+ </div>
865
+ <div className="flex items-center gap-2">
866
+ <span className="text-xs text-slate-500">{dam.level}%</span>
867
+ <Badge
868
+ variant={dam.status === 'Critical' ? 'destructive' : dam.status === 'Warning' ? 'outline' : 'secondary'}
869
+ className={cn(
870
+ "text-xs",
871
+ dam.status === 'Warning' ? 'border-orange-500 text-orange-700 bg-orange-50' : ''
872
+ )}
873
+ >
874
+ {dam.status}
875
+ </Badge>
876
+ </div>
877
+ </div>
878
+ ))}
879
+ </CardContent>
880
+ </Card>
881
+
882
+ <Card>
883
+ <CardHeader className="pb-2">
884
+ <CardTitle className="text-sm">District Boundaries</CardTitle>
885
+ </CardHeader>
886
+ <CardContent>
887
+ <div className="flex items-center justify-between">
888
+ <span className="text-xs text-slate-600">Show district boundaries</span>
889
+ <Button
890
+ variant={showDistricts ? "default" : "outline"}
891
+ size="sm"
892
+ className="h-7 text-xs"
893
+ onClick={() => setShowDistricts(!showDistricts)}
894
+ >
895
+ {showDistricts ? 'On' : 'Off'}
896
+ </Button>
897
+ </div>
898
+ {loadingBoundaries && (
899
+ <p className="text-xs text-slate-500 mt-2">Loading cached boundaries...</p>
900
+ )}
901
+ </CardContent>
902
+ </Card>
903
+ </div>
904
+ </div>
905
+
906
+ {/* Mobile Controls Overlay */}
907
+ {showMobileControls && (
908
+ <div className="fixed inset-0 z-[1001] lg:hidden">
909
+ <div className="absolute inset-0 bg-black/50 backdrop-blur-sm" onClick={() => setShowMobileControls(false)} />
910
+ <div className="absolute bottom-0 left-0 right-0 bg-white rounded-t-2xl p-4 max-h-[70vh] overflow-y-auto animate-in slide-in-from-bottom duration-200">
911
+ <div className="flex items-center justify-between mb-4">
912
+ <h2 className="text-lg font-semibold">Controls & Alerts</h2>
913
+ <Button variant="ghost" size="icon" onClick={() => setShowMobileControls(false)}>
914
+ <X className="h-5 w-5" />
915
+ </Button>
916
+ </div>
917
+
918
+ {/* Mobile: Critical Areas */}
919
+ <div className="space-y-3 mb-4">
920
+ <h3 className="text-sm font-semibold flex items-center">
921
+ <AlertTriangle className="h-4 w-4 mr-2 text-red-500" />
922
+ Critical Alert Areas
923
+ </h3>
924
+ <div className="p-3 bg-red-50 border border-red-100 rounded-md">
925
+ <div className="flex items-center justify-between mb-1">
926
+ <h4 className="text-sm font-semibold text-red-900">Kolhapur City</h4>
927
+ <Badge variant="destructive" className="text-xs">Critical</Badge>
928
+ </div>
929
+ <p className="text-xs text-red-700">Panchaganga river above danger mark. Evacuation recommended.</p>
930
+ </div>
931
+ <div className="p-3 bg-orange-50 border border-orange-100 rounded-md">
932
+ <div className="flex items-center justify-between mb-1">
933
+ <h4 className="text-sm font-semibold text-orange-900">Sangli District</h4>
934
+ <Badge variant="outline" className="text-xs border-orange-500 text-orange-700">High</Badge>
935
+ </div>
936
+ <p className="text-xs text-orange-700">Water level rising steadily. Emergency teams on standby.</p>
937
+ </div>
938
+ </div>
939
+
940
+ {/* Mobile: Dam Status */}
941
+ <div className="space-y-2">
942
+ <h3 className="text-sm font-semibold flex items-center">
943
+ <Layers className="h-4 w-4 mr-2 text-blue-500" />
944
+ Dam Status
945
+ </h3>
946
+ {dams.slice(0, 5).map(dam => (
947
+ <div key={dam.id} className="flex justify-between items-center text-sm p-2 bg-slate-50 rounded-md">
948
+ <span className="font-medium text-slate-700">{dam.name}</span>
949
+ <div className="flex items-center gap-2">
950
+ <span className="text-xs text-slate-500">{dam.level}%</span>
951
+ <Badge
952
+ variant={dam.status === 'Critical' ? 'destructive' : dam.status === 'Warning' ? 'outline' : 'secondary'}
953
+ className={cn(
954
+ "text-xs",
955
+ dam.status === 'Warning' ? 'border-orange-500 text-orange-700 bg-orange-50' : ''
956
+ )}
957
+ >
958
+ {dam.status}
959
+ </Badge>
960
+ </div>
961
+ </div>
962
+ ))}
963
+ </div>
964
+ </div>
965
+ </div>
966
+ )}
967
+ </div>
968
+ );
969
+ }
src/pages/GrFinder.tsx ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useMemo } from 'react';
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { Card, CardContent, CardHeader, CardTitle } from '@/src/components/ui/Card';
4
+ import { Input } from '@/src/components/ui/Input';
5
+ import { Button } from '@/src/components/ui/Button';
6
+ import { Badge } from '@/src/components/ui/Badge';
7
+ import {
8
+ Search, Filter, FileText, Calendar, Building, ChevronRight,
9
+ MessageSquare, Download, Eye, Clock, TrendingUp, Star
10
+ } from 'lucide-react';
11
+ import { cn } from '@/src/utils/cn';
12
+ import { mockGRs, grCategories, grYears, grDepartments, GRDocument } from '@/src/data/mockGRs';
13
+
14
+ export function GrFinder() {
15
+ const navigate = useNavigate();
16
+ const [searchQuery, setSearchQuery] = useState('');
17
+ const [selectedCategory, setSelectedCategory] = useState<string>('All');
18
+ const [selectedYear, setSelectedYear] = useState<string>('All');
19
+ const [selectedDepartment, setSelectedDepartment] = useState<string>('All');
20
+ const [showFilters, setShowFilters] = useState(false);
21
+
22
+ const filteredGRs = useMemo(() => {
23
+ return mockGRs.filter(gr => {
24
+ const matchesSearch = searchQuery === '' ||
25
+ gr.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
26
+ gr.id.toLowerCase().includes(searchQuery.toLowerCase()) ||
27
+ gr.keywords.some(k => k.toLowerCase().includes(searchQuery.toLowerCase()));
28
+ const matchesCategory = selectedCategory === 'All' || gr.category === selectedCategory;
29
+ const matchesYear = selectedYear === 'All' || gr.year.toString() === selectedYear;
30
+ const matchesDepartment = selectedDepartment === 'All' || gr.department === selectedDepartment;
31
+ return matchesSearch && matchesCategory && matchesYear && matchesDepartment;
32
+ });
33
+ }, [searchQuery, selectedCategory, selectedYear, selectedDepartment]);
34
+
35
+ const popularGRs = mockGRs.filter(gr => gr.popularQuery);
36
+
37
+ const handleAskAI = (gr: GRDocument) => {
38
+ navigate(`/ai-assistant?q=${encodeURIComponent(`Explain ${gr.id}: ${gr.title}`)}`);
39
+ };
40
+
41
+ const getStatusColor = (status: string) => {
42
+ switch (status) {
43
+ case 'Active': return 'bg-green-100 text-green-700 border-green-200';
44
+ case 'Amended': return 'bg-yellow-100 text-yellow-700 border-yellow-200';
45
+ case 'Superseded': return 'bg-red-100 text-red-700 border-red-200';
46
+ default: return 'bg-slate-100 text-slate-700 border-slate-200';
47
+ }
48
+ };
49
+
50
+ const getCategoryColor = (category: string) => {
51
+ const colors: Record<string, string> = {
52
+ 'Flood Management': 'bg-blue-100 text-blue-700',
53
+ 'Dam Safety': 'bg-red-100 text-red-700',
54
+ 'Irrigation': 'bg-cyan-100 text-cyan-700',
55
+ 'Compensation': 'bg-emerald-100 text-emerald-700',
56
+ 'Water Policy': 'bg-purple-100 text-purple-700',
57
+ 'Administrative': 'bg-orange-100 text-orange-700',
58
+ };
59
+ return colors[category] || 'bg-slate-100 text-slate-700';
60
+ };
61
+
62
+ return (
63
+ <div className="space-y-6 max-w-6xl mx-auto">
64
+ {/* Header */}
65
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
66
+ <div>
67
+ <h1 className="text-2xl font-bold tracking-tight text-slate-900">GR Finder</h1>
68
+ <p className="text-slate-500">Search Government Resolutions, Acts, and Circulars</p>
69
+ </div>
70
+ <Button onClick={() => navigate('/ai-assistant')} className="bg-indigo-600 hover:bg-indigo-700 text-white">
71
+ <MessageSquare className="mr-2 h-4 w-4" />
72
+ Ask GR Helper
73
+ </Button>
74
+ </div>
75
+
76
+ {/* Search Card */}
77
+ <Card className="border-none shadow-md bg-white/50 backdrop-blur-sm">
78
+ <CardContent className="p-4 sm:p-6">
79
+ <div className="flex flex-col sm:flex-row gap-4">
80
+ <div className="relative flex-1">
81
+ <Search className="absolute left-3 top-3 h-5 w-5 text-slate-400" />
82
+ <Input
83
+ type="text"
84
+ placeholder="Search by GR number, title, or keywords..."
85
+ className="pl-10 h-12 text-base bg-white"
86
+ value={searchQuery}
87
+ onChange={(e) => setSearchQuery(e.target.value)}
88
+ />
89
+ </div>
90
+ <Button
91
+ variant="outline"
92
+ className="h-12 px-6 bg-white shrink-0"
93
+ onClick={() => setShowFilters(!showFilters)}
94
+ >
95
+ <Filter className="mr-2 h-4 w-4" />
96
+ Filters
97
+ {(selectedCategory !== 'All' || selectedYear !== 'All' || selectedDepartment !== 'All') && (
98
+ <span className="ml-2 bg-indigo-100 text-indigo-700 rounded-full px-2 py-0.5 text-xs">
99
+ Active
100
+ </span>
101
+ )}
102
+ </Button>
103
+ </div>
104
+
105
+ {/* Expanded Filters */}
106
+ {showFilters && (
107
+ <div className="mt-4 pt-4 border-t border-slate-200 grid grid-cols-1 sm:grid-cols-3 gap-4">
108
+ <div>
109
+ <label className="text-xs font-medium text-slate-500 mb-1 block">Category</label>
110
+ <select
111
+ className="w-full h-10 rounded-md border border-slate-200 bg-white px-3 text-sm"
112
+ value={selectedCategory}
113
+ onChange={(e) => setSelectedCategory(e.target.value)}
114
+ >
115
+ <option value="All">All Categories</option>
116
+ {grCategories.map(cat => (
117
+ <option key={cat} value={cat}>{cat}</option>
118
+ ))}
119
+ </select>
120
+ </div>
121
+ <div>
122
+ <label className="text-xs font-medium text-slate-500 mb-1 block">Year</label>
123
+ <select
124
+ className="w-full h-10 rounded-md border border-slate-200 bg-white px-3 text-sm"
125
+ value={selectedYear}
126
+ onChange={(e) => setSelectedYear(e.target.value)}
127
+ >
128
+ <option value="All">All Years</option>
129
+ {grYears.map(year => (
130
+ <option key={year} value={year}>{year}</option>
131
+ ))}
132
+ </select>
133
+ </div>
134
+ <div>
135
+ <label className="text-xs font-medium text-slate-500 mb-1 block">Department</label>
136
+ <select
137
+ className="w-full h-10 rounded-md border border-slate-200 bg-white px-3 text-sm"
138
+ value={selectedDepartment}
139
+ onChange={(e) => setSelectedDepartment(e.target.value)}
140
+ >
141
+ <option value="All">All Departments</option>
142
+ {grDepartments.map(dept => (
143
+ <option key={dept} value={dept}>{dept}</option>
144
+ ))}
145
+ </select>
146
+ </div>
147
+ </div>
148
+ )}
149
+
150
+ {/* Quick Category Filters */}
151
+ <div className="flex flex-wrap gap-2 mt-4">
152
+ <Badge
153
+ variant={selectedCategory === 'All' ? 'default' : 'secondary'}
154
+ className="cursor-pointer px-3 py-1 text-sm"
155
+ onClick={() => setSelectedCategory('All')}
156
+ >
157
+ All
158
+ </Badge>
159
+ {grCategories.slice(0, 4).map(cat => (
160
+ <Badge
161
+ key={cat}
162
+ variant={selectedCategory === cat ? 'default' : 'secondary'}
163
+ className="cursor-pointer px-3 py-1 text-sm"
164
+ onClick={() => setSelectedCategory(cat)}
165
+ >
166
+ {cat}
167
+ </Badge>
168
+ ))}
169
+ </div>
170
+ </CardContent>
171
+ </Card>
172
+
173
+ {/* Popular GRs Section (shown when no search) */}
174
+ {searchQuery === '' && selectedCategory === 'All' && (
175
+ <Card className="border-none shadow-md bg-gradient-to-r from-indigo-50 to-blue-50">
176
+ <CardHeader className="pb-2">
177
+ <CardTitle className="text-base flex items-center gap-2">
178
+ <Star className="h-4 w-4 text-yellow-500" />
179
+ Popular GRs
180
+ </CardTitle>
181
+ </CardHeader>
182
+ <CardContent>
183
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
184
+ {popularGRs.slice(0, 4).map(gr => (
185
+ <div
186
+ key={gr.id}
187
+ className="bg-white rounded-lg p-3 border border-slate-200 hover:shadow-md transition-shadow cursor-pointer"
188
+ onClick={() => {
189
+ setSearchQuery(gr.id);
190
+ }}
191
+ >
192
+ <div className="flex items-start justify-between gap-2">
193
+ <div className="flex-1 min-w-0">
194
+ <p className="text-xs font-mono text-indigo-600">{gr.id}</p>
195
+ <p className="text-sm font-medium text-slate-900 truncate">{gr.title}</p>
196
+ </div>
197
+ <Badge className={cn("text-[10px] shrink-0", getCategoryColor(gr.category))}>
198
+ {gr.category}
199
+ </Badge>
200
+ </div>
201
+ </div>
202
+ ))}
203
+ </div>
204
+ </CardContent>
205
+ </Card>
206
+ )}
207
+
208
+ {/* Results Count */}
209
+ <div className="flex items-center justify-between">
210
+ <p className="text-sm text-slate-500">
211
+ Found <span className="font-semibold text-slate-700">{filteredGRs.length}</span> Government Resolutions
212
+ </p>
213
+ {(selectedCategory !== 'All' || selectedYear !== 'All' || selectedDepartment !== 'All' || searchQuery) && (
214
+ <Button
215
+ variant="ghost"
216
+ size="sm"
217
+ className="text-xs"
218
+ onClick={() => {
219
+ setSearchQuery('');
220
+ setSelectedCategory('All');
221
+ setSelectedYear('All');
222
+ setSelectedDepartment('All');
223
+ }}
224
+ >
225
+ Clear filters
226
+ </Button>
227
+ )}
228
+ </div>
229
+
230
+ {/* Results List */}
231
+ <div className="space-y-4">
232
+ {filteredGRs.map((gr) => (
233
+ <Card key={gr.id} className="overflow-hidden transition-all hover:shadow-md border-slate-200">
234
+ <CardContent className="p-0">
235
+ <div className="flex flex-col sm:flex-row">
236
+ <div className="p-6 flex-1">
237
+ <div className="flex items-start justify-between gap-2 mb-2">
238
+ <div className="flex items-center gap-2 flex-wrap">
239
+ <Badge className={cn("text-[10px]", getStatusColor(gr.status))}>
240
+ {gr.status}
241
+ </Badge>
242
+ <Badge className={cn("text-[10px]", getCategoryColor(gr.category))}>
243
+ {gr.category}
244
+ </Badge>
245
+ <span className="text-xs font-mono text-slate-600">{gr.id}</span>
246
+ </div>
247
+ {gr.popularQuery && (
248
+ <Star className="h-4 w-4 text-yellow-500 fill-yellow-500" />
249
+ )}
250
+ </div>
251
+
252
+ <h3 className="text-lg font-semibold text-slate-900 mb-2 leading-tight">
253
+ {gr.title}
254
+ </h3>
255
+
256
+ <p className="text-sm text-slate-600 mb-4 line-clamp-2">
257
+ {gr.summary}
258
+ </p>
259
+
260
+ <div className="flex flex-wrap items-center gap-4 text-xs text-slate-500">
261
+ <div className="flex items-center">
262
+ <Building className="mr-1 h-3.5 w-3.5" />
263
+ {gr.department}
264
+ </div>
265
+ <div className="flex items-center">
266
+ <Calendar className="mr-1 h-3.5 w-3.5" />
267
+ {gr.date}
268
+ </div>
269
+ {gr.relatedGRs && gr.relatedGRs.length > 0 && (
270
+ <div className="flex items-center">
271
+ <FileText className="mr-1 h-3.5 w-3.5" />
272
+ {gr.relatedGRs.length} Related
273
+ </div>
274
+ )}
275
+ </div>
276
+
277
+ {/* Keywords */}
278
+ <div className="flex flex-wrap gap-1 mt-3">
279
+ {gr.keywords.slice(0, 4).map(keyword => (
280
+ <span
281
+ key={keyword}
282
+ className="text-[10px] bg-slate-100 text-slate-600 px-2 py-0.5 rounded"
283
+ >
284
+ {keyword}
285
+ </span>
286
+ ))}
287
+ </div>
288
+ </div>
289
+
290
+ <div className="bg-slate-50 border-t sm:border-t-0 sm:border-l border-slate-100 p-4 sm:p-6 flex sm:flex-col items-center justify-center gap-3 sm:w-48">
291
+ <Button variant="outline" size="sm" className="w-full justify-start bg-white">
292
+ <Eye className="mr-2 h-4 w-4" /> View
293
+ </Button>
294
+ <Button variant="outline" size="sm" className="w-full justify-start bg-white">
295
+ <Download className="mr-2 h-4 w-4" /> Download
296
+ </Button>
297
+ <Button
298
+ variant="secondary"
299
+ size="sm"
300
+ className="w-full justify-start bg-indigo-50 text-indigo-700 hover:bg-indigo-100 border-indigo-100"
301
+ onClick={() => handleAskAI(gr)}
302
+ >
303
+ <MessageSquare className="mr-2 h-4 w-4" /> Ask AI
304
+ </Button>
305
+ </div>
306
+ </div>
307
+ </CardContent>
308
+ </Card>
309
+ ))}
310
+
311
+ {filteredGRs.length === 0 && (
312
+ <div className="text-center py-12">
313
+ <FileText className="mx-auto h-12 w-12 text-slate-300 mb-4" />
314
+ <h3 className="text-lg font-medium text-slate-900">No GRs found</h3>
315
+ <p className="text-slate-500">Try adjusting your search or filters.</p>
316
+ <Button
317
+ variant="outline"
318
+ className="mt-4"
319
+ onClick={() => {
320
+ setSearchQuery('');
321
+ setSelectedCategory('All');
322
+ setSelectedYear('All');
323
+ setSelectedDepartment('All');
324
+ }}
325
+ >
326
+ Clear all filters
327
+ </Button>
328
+ </div>
329
+ )}
330
+ </div>
331
+
332
+ {/* Help Section */}
333
+ <Card className="bg-slate-50 border-slate-200">
334
+ <CardContent className="p-4">
335
+ <div className="flex items-start gap-3">
336
+ <MessageSquare className="h-5 w-5 text-indigo-600 mt-0.5" />
337
+ <div>
338
+ <p className="text-sm font-medium text-slate-900">Can't find what you're looking for?</p>
339
+ <p className="text-sm text-slate-500 mt-1">
340
+ Try asking the GR Helper AI assistant for personalized help with your query.
341
+ </p>
342
+ <Button
343
+ variant="link"
344
+ className="px-0 text-indigo-600 mt-1"
345
+ onClick={() => navigate('/ai-assistant')}
346
+ >
347
+ Open GR Helper <ChevronRight className="ml-1 h-4 w-4" />
348
+ </Button>
349
+ </div>
350
+ </div>
351
+ </CardContent>
352
+ </Card>
353
+ </div>
354
+ );
355
+ }
src/pages/Repository.tsx ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { Card, CardContent } from '@/src/components/ui/Card';
3
+ import { Input } from '@/src/components/ui/Input';
4
+ import { Button } from '@/src/components/ui/Button';
5
+ import { Badge } from '@/src/components/ui/Badge';
6
+ import {
7
+ Search,
8
+ Filter,
9
+ FileText,
10
+ Download,
11
+ Eye,
12
+ MessageSquare,
13
+ Calendar,
14
+ Building
15
+ } from 'lucide-react';
16
+ import { useNavigate } from 'react-router-dom';
17
+
18
+ const documents = [
19
+ {
20
+ id: 'GR-2023-01',
21
+ title: 'Guidelines for Flood Management and Evacuation Procedures',
22
+ type: 'GR',
23
+ department: 'Disaster Management',
24
+ date: '2023-06-15',
25
+ summary: 'Comprehensive guidelines for district administrations regarding flood preparedness, evacuation protocols, and relief camp management.'
26
+ },
27
+ {
28
+ id: 'ACT-1976',
29
+ title: 'Maharashtra Irrigation Act, 1976',
30
+ type: 'Act',
31
+ department: 'Water Resources',
32
+ date: '1976-08-05',
33
+ summary: 'An Act to unify and amend the law relating to irrigation in the State of Maharashtra.'
34
+ },
35
+ {
36
+ id: 'CIR-2024-45',
37
+ title: 'Standard Operating Procedure for Dam Gate Operations during Monsoon',
38
+ type: 'Circular',
39
+ department: 'Water Resources',
40
+ date: '2024-05-10',
41
+ summary: 'Updated SOPs for dam engineers regarding phased water discharge and coordination with downstream authorities.'
42
+ },
43
+ {
44
+ id: 'GR-2022-89',
45
+ title: 'Compensation Policy for Crop Damage due to Floods',
46
+ type: 'GR',
47
+ department: 'Revenue & Forest',
48
+ date: '2022-09-22',
49
+ summary: 'Revised compensation rates and assessment methodology for agricultural losses caused by natural calamities including floods.'
50
+ },
51
+ {
52
+ id: 'ACT-2005',
53
+ title: 'Maharashtra Water Resources Regulatory Authority Act, 2005',
54
+ type: 'Act',
55
+ department: 'Water Resources',
56
+ date: '2005-05-04',
57
+ summary: 'Act to provide for the establishment of the Maharashtra Water Resources Regulatory Authority to regulate water resources within the State.'
58
+ },
59
+ ];
60
+
61
+ export function Repository() {
62
+ const [searchQuery, setSearchQuery] = useState('');
63
+ const [activeFilter, setActiveFilter] = useState('All');
64
+ const navigate = useNavigate();
65
+
66
+ const filteredDocs = documents.filter(doc => {
67
+ const matchesSearch = doc.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
68
+ doc.id.toLowerCase().includes(searchQuery.toLowerCase());
69
+ const matchesFilter = activeFilter === 'All' || doc.type === activeFilter;
70
+ return matchesSearch && matchesFilter;
71
+ });
72
+
73
+ const handleAskAI = (docTitle: string) => {
74
+ navigate(`/ai-assistant?q=Summarize the key points of ${encodeURIComponent(docTitle)}`);
75
+ };
76
+
77
+ return (
78
+ <div className="space-y-6 max-w-6xl mx-auto">
79
+ <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
80
+ <div>
81
+ <h1 className="text-2xl font-bold tracking-tight text-slate-900">Centralized Repository</h1>
82
+ <p className="text-slate-500">Search and access GRs, Acts, and Circulars</p>
83
+ </div>
84
+ <Button onClick={() => navigate('/ai-assistant')} className="bg-indigo-600 hover:bg-indigo-700 text-white">
85
+ <MessageSquare className="mr-2 h-4 w-4" />
86
+ Ask AI Assistant
87
+ </Button>
88
+ </div>
89
+
90
+ <Card className="border-none shadow-md bg-white/50 backdrop-blur-sm">
91
+ <CardContent className="p-4 sm:p-6">
92
+ <div className="flex flex-col sm:flex-row gap-4">
93
+ <div className="relative flex-1">
94
+ <Search className="absolute left-3 top-3 h-5 w-5 text-slate-400" />
95
+ <Input
96
+ type="text"
97
+ placeholder="Search by title, GR number, or keywords..."
98
+ className="pl-10 h-12 text-base bg-white"
99
+ value={searchQuery}
100
+ onChange={(e) => setSearchQuery(e.target.value)}
101
+ />
102
+ </div>
103
+ <Button variant="outline" className="h-12 px-6 bg-white shrink-0">
104
+ <Filter className="mr-2 h-4 w-4" />
105
+ Advanced Filters
106
+ </Button>
107
+ </div>
108
+
109
+ <div className="flex flex-wrap gap-2 mt-4">
110
+ {['All', 'GR', 'Act', 'Circular'].map(filter => (
111
+ <Badge
112
+ key={filter}
113
+ variant={activeFilter === filter ? 'default' : 'secondary'}
114
+ className="cursor-pointer px-3 py-1 text-sm"
115
+ onClick={() => setActiveFilter(filter)}
116
+ >
117
+ {filter}
118
+ </Badge>
119
+ ))}
120
+ </div>
121
+ </CardContent>
122
+ </Card>
123
+
124
+ <div className="space-y-4">
125
+ {filteredDocs.map((doc) => (
126
+ <Card key={doc.id} className="overflow-hidden transition-all hover:shadow-md border-slate-200">
127
+ <CardContent className="p-0">
128
+ <div className="flex flex-col sm:flex-row">
129
+ <div className="p-6 flex-1">
130
+ <div className="flex items-start justify-between">
131
+ <div>
132
+ <div className="flex items-center space-x-2 mb-2">
133
+ <Badge variant={
134
+ doc.type === 'GR' ? 'default' :
135
+ doc.type === 'Act' ? 'destructive' : 'secondary'
136
+ }>
137
+ {doc.type}
138
+ </Badge>
139
+ <span className="text-xs font-medium text-slate-500">{doc.id}</span>
140
+ </div>
141
+ <h3 className="text-lg font-semibold text-slate-900 mb-2 leading-tight">
142
+ {doc.title}
143
+ </h3>
144
+ <p className="text-sm text-slate-600 mb-4 line-clamp-2">
145
+ {doc.summary}
146
+ </p>
147
+ </div>
148
+ </div>
149
+
150
+ <div className="flex flex-wrap items-center gap-4 text-xs text-slate-500">
151
+ <div className="flex items-center">
152
+ <Building className="mr-1 h-3.5 w-3.5" />
153
+ {doc.department}
154
+ </div>
155
+ <div className="flex items-center">
156
+ <Calendar className="mr-1 h-3.5 w-3.5" />
157
+ {doc.date}
158
+ </div>
159
+ </div>
160
+ </div>
161
+
162
+ <div className="bg-slate-50 border-t sm:border-t-0 sm:border-l border-slate-100 p-4 sm:p-6 flex sm:flex-col items-center justify-center gap-3 sm:w-48">
163
+ <Button variant="outline" size="sm" className="w-full justify-start bg-white">
164
+ <Eye className="mr-2 h-4 w-4" /> View
165
+ </Button>
166
+ <Button variant="outline" size="sm" className="w-full justify-start bg-white">
167
+ <Download className="mr-2 h-4 w-4" /> Download
168
+ </Button>
169
+ <Button
170
+ variant="secondary"
171
+ size="sm"
172
+ className="w-full justify-start bg-indigo-50 text-indigo-700 hover:bg-indigo-100 border-indigo-100"
173
+ onClick={() => handleAskAI(doc.title)}
174
+ >
175
+ <MessageSquare className="mr-2 h-4 w-4" /> Ask AI
176
+ </Button>
177
+ </div>
178
+ </div>
179
+ </CardContent>
180
+ </Card>
181
+ ))}
182
+ {filteredDocs.length === 0 && (
183
+ <div className="text-center py-12">
184
+ <FileText className="mx-auto h-12 w-12 text-slate-300 mb-4" />
185
+ <h3 className="text-lg font-medium text-slate-900">No documents found</h3>
186
+ <p className="text-slate-500">Try adjusting your search or filters.</p>
187
+ </div>
188
+ )}
189
+ </div>
190
+ </div>
191
+ );
192
+ }
src/utils/cn.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
tsconfig.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "experimentalDecorators": true,
5
+ "useDefineForClassFields": false,
6
+ "module": "ESNext",
7
+ "lib": [
8
+ "ES2022",
9
+ "DOM",
10
+ "DOM.Iterable"
11
+ ],
12
+ "skipLibCheck": true,
13
+ "moduleResolution": "bundler",
14
+ "isolatedModules": true,
15
+ "moduleDetection": "force",
16
+ "allowJs": true,
17
+ "jsx": "react-jsx",
18
+ "paths": {
19
+ "@/*": [
20
+ "./*"
21
+ ]
22
+ },
23
+ "allowImportingTsExtensions": true,
24
+ "noEmit": true
25
+ }
26
+ }
vite.config.ts ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import path from 'path';
2
+ import { defineConfig, loadEnv } from 'vite';
3
+ import react from '@vitejs/plugin-react';
4
+
5
+ export default defineConfig(({ mode }) => {
6
+ const env = loadEnv(mode, '.', '');
7
+ return {
8
+ server: {
9
+ port: 3000,
10
+ host: '0.0.0.0',
11
+ },
12
+ preview: {
13
+ host: true, // Listen on all addresses
14
+ port: 7860,
15
+ strictPort: true,
16
+ allowedHosts: ['devarshia5-wrd-drishti.hf.space'] // Add this line
17
+ },
18
+ plugins: [react()],
19
+ define: {
20
+ 'process.env.API_KEY': JSON.stringify(env.GEMINI_API_KEY),
21
+ 'process.env.GEMINI_API_KEY': JSON.stringify(env.GEMINI_API_KEY)
22
+ },
23
+ resolve: {
24
+ alias: {
25
+ '@': path.resolve(__dirname, '.'),
26
+ }
27
+ }
28
+ };
29
+ });