diff --git a/package-lock.json b/package-lock.json
index 59dc246..502c97e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,10 +9,14 @@
"version": "0.0.0",
"dependencies": {
"@radix-ui/react-avatar": "^1.1.9",
+ "@radix-ui/react-checkbox": "^1.3.1",
"@radix-ui/react-dialog": "^1.1.13",
+ "@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-select": "^2.2.4",
+ "@radix-ui/react-separator": "^1.1.6",
"@radix-ui/react-slot": "^1.2.2",
+ "@radix-ui/react-tabs": "^1.1.11",
"@radix-ui/react-toast": "^1.2.13",
"@tailwindcss/vite": "^4.1.6",
"axios": "^1.9.0",
@@ -20,9 +24,11 @@
"clsx": "^2.1.1",
"framer-motion": "^12.11.0",
"lucide-react": "^0.510.0",
+ "next-themes": "^0.4.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.6.0",
+ "sonner": "^2.0.3",
"tailwind-merge": "^3.3.0"
},
"devDependencies": {
@@ -1097,6 +1103,36 @@
}
}
},
+ "node_modules/@radix-ui/react-checkbox": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.1.tgz",
+ "integrity": "sha512-xTaLKAO+XXMPK/BpVTSaAAhlefmvMSACjIhK9mGsImvX2ljcTDm8VGR1CuS1uYcNdR5J+oiOhoJZc5un6bh3VQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.2",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-collection": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.6.tgz",
@@ -1225,6 +1261,35 @@
}
}
},
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.14",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.14.tgz",
+ "integrity": "sha512-lzuyNjoWOoaMFE/VC5FnAAYM16JmQA8ZmucOXtlhm2kKR5TSU95YLAueQ4JYuRmUJmBvSqXaVFGIfuukybwZJQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-menu": "2.1.14",
+ "@radix-ui/react-primitive": "2.1.2",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-focus-guards": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.2.tgz",
@@ -1302,6 +1367,46 @@
}
}
},
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.14",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.14.tgz",
+ "integrity": "sha512-0zSiBAIFq9GSKoSH5PdEaQeRB3RnEGxC+H2P0egtnKoKKLNBH8VBHyVO6/jskhjAezhOIplyRUj7U2lds9A+Yg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-collection": "1.1.6",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.9",
+ "@radix-ui/react-focus-guards": "1.1.2",
+ "@radix-ui/react-focus-scope": "1.1.6",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.6",
+ "@radix-ui/react-portal": "1.1.8",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.2",
+ "@radix-ui/react-roving-focus": "1.1.9",
+ "@radix-ui/react-slot": "1.2.2",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-popper": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.6.tgz",
@@ -1401,6 +1506,37 @@
}
}
},
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.9.tgz",
+ "integrity": "sha512-ZzrIFnMYHHCNqSNCsuN6l7wlewBEq0O0BCSBkabJMFXVO51LRUTq71gLP1UxFvmrXElqmPjA5VX7IqC9VpazAQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-collection": "1.1.6",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.2",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-select": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.4.tgz",
@@ -1443,6 +1579,29 @@
}
}
},
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.6.tgz",
+ "integrity": "sha512-Izof3lPpbCfTM7WDta+LRkz31jem890VjEvpVRoWQNKpDUMMVffuyq854XPGP1KYGWWmjmYvHvPFeocWhFCy1w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-slot": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz",
@@ -1460,6 +1619,36 @@
}
}
},
+ "node_modules/@radix-ui/react-tabs": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.11.tgz",
+ "integrity": "sha512-4FiKSVoXqPP/KfzlB7lwwqoFV6EPwkrrqGp9cUYXjwDYHhvpnqq79P+EPHKcdoTE7Rl8w/+6s9rTlsfXHES9GA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.2",
+ "@radix-ui/react-roving-focus": "1.1.9",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-toast": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.13.tgz",
@@ -4420,6 +4609,16 @@
"node": ">= 0.6"
}
},
+ "node_modules/next-themes": {
+ "version": "0.4.6",
+ "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz",
+ "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/node-releases": {
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
@@ -5148,6 +5347,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/sonner": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz",
+ "integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
diff --git a/package.json b/package.json
index 5a12e20..7e296b6 100644
--- a/package.json
+++ b/package.json
@@ -11,10 +11,14 @@
},
"dependencies": {
"@radix-ui/react-avatar": "^1.1.9",
+ "@radix-ui/react-checkbox": "^1.3.1",
"@radix-ui/react-dialog": "^1.1.13",
+ "@radix-ui/react-dropdown-menu": "^2.1.14",
"@radix-ui/react-label": "^2.1.6",
"@radix-ui/react-select": "^2.2.4",
+ "@radix-ui/react-separator": "^1.1.6",
"@radix-ui/react-slot": "^1.2.2",
+ "@radix-ui/react-tabs": "^1.1.11",
"@radix-ui/react-toast": "^1.2.13",
"@tailwindcss/vite": "^4.1.6",
"axios": "^1.9.0",
@@ -22,9 +26,11 @@
"clsx": "^2.1.1",
"framer-motion": "^12.11.0",
"lucide-react": "^0.510.0",
+ "next-themes": "^0.4.6",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.6.0",
+ "sonner": "^2.0.3",
"tailwind-merge": "^3.3.0"
},
"devDependencies": {
diff --git a/src/App.tsx b/src/App.tsx
index 732be04..b910afb 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,11 +2,13 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import MigrantProfilePage from "./pages/MigrantProfilePage";
import NotFoundPage from "./pages/NotFoundPage";
import "./App.css";
-import LoginPage from "./components/LoginPage";
-import Migrants from "./components/Migrants";
+import LoginPage from "./components/admin/LoginPage";
+import Migrants from "./components/admin/Migrants";
import ProfileSettings from "./components/ui/ProfileSettings";
-import AdminDashboardPage from "./pages/AdminDashboardPage";
+import AdminDashboardPage from "./components/admin/AdminDashboard";
import HomePage from "./pages/HomePage";
+import RegisterPage from "./components/admin/Register";
+import AddMigrantPage from "./components/admin/AddMigrant";
function App() {
return (
@@ -15,7 +17,9 @@ function App() {
} />
} />
} />
+ } />
} />
+ } />
} />
} />
} />
diff --git a/src/components/AdminDashboard.tsx b/src/components/AdminDashboard.tsx
deleted file mode 100644
index 61aae16..0000000
--- a/src/components/AdminDashboard.tsx
+++ /dev/null
@@ -1,360 +0,0 @@
-"use client"
-
-import { useEffect, useState } from "react"
-import { useNavigate } from "react-router-dom"
-import { motion } from "framer-motion"
-import {
- Users,
- Database,
- FileText,
- Settings,
- BarChart2,
- Calendar,
- Clock,
- PlusCircle,
- Search,
- User,
- Bell,
- Home,
-} from "lucide-react"
-import { Link } from "react-router-dom"
-
-
-export default function AdminDashboard() {
- const [isFirstLoad, setIsFirstLoad] = useState(true)
- const [isProfileDropdownOpen, setIsProfileDropdownOpen] = useState(false)
- const navigate = useNavigate()
-
- useEffect(() => {
- // Check if user is authenticated
- const token = localStorage.getItem("adminToken")
- if (!token) {
- navigate("/admin")
- return
- }
-
- // Check if we're navigating from another admin page
- const hasVisitedAdmin = localStorage.getItem("adminNavigation")
-
- // Skip loading if we're navigating from another admin page
- if (hasVisitedAdmin) {
- setIsFirstLoad(false)
- } else {
- // Set flag to indicate we've visited an admin page
- localStorage.setItem("adminNavigation", "true")
-
- // Only show loading state on first load
- if (isFirstLoad) {
- // Simulate loading dashboard data
- const timer = setTimeout(() => {
- setIsFirstLoad(false)
-
- // Show welcome toast
-
- }, 500) // Reduced loading time for better UX
-
- return () => clearTimeout(timer)
- }
- }
- }, [isFirstLoad, navigate])
-
- const handleLogout = () => {
- localStorage.removeItem("adminToken")
- localStorage.removeItem("adminNavigation") // Clear navigation flag on logout
-
- navigate("/login")
- }
-
-
- // If it's the first load, show a loading state
- if (isFirstLoad) {
- return (
-
-
-
-
Loading dashboard...
-
-
- )
- }
-
- return (
-
- {/* Sidebar */}
-
-
-
Italian Migrants
-
Northern Territory DB
-
-
-
-
-
-
-
- {/* Main content */}
-
- {/* Header */}
-
-
-
Admin Portal
-
-
-
-
-
-
- {/* Notifications */}
-
-
- {/* Profile dropdown */}
-
-
-
- {isProfileDropdownOpen && (
-
- setIsProfileDropdownOpen(false)}
- >
- Your Profile
-
-
- Settings
-
-
-
- )}
-
-
-
-
-
- {/* Dashboard content */}
-
- {/* Stats cards */}
-
-
-
-
-
-
-
Total Records
-
1,248
-
-
-
-
-
-
+86 this month
-
-
-
-
-
-
-
3 require attention
-
-
-
- {/* Quick actions */}
-
-
-
Quick Actions
-
-
-
-
- Add New Migrant
-
-
-
-
-
-
- {/* Recent activity */}
-
-
-
Recent Activity
-
-
-
- {[
- { action: "Added new migrant", user: "Admin", time: "10 minutes ago", type: "add" },
- { action: "Updated record #1248", user: "Admin", time: "2 hours ago", type: "update" },
- { action: "Generated monthly report", user: "System", time: "Yesterday", type: "report" },
- { action: "Deleted duplicate record", user: "Admin", time: "2 days ago", type: "delete" },
- { action: "Imported 15 new records", user: "Admin", time: "1 week ago", type: "import" },
- ].map((activity, index) => (
-
-
- {activity.type === "add" ? (
-
- ) : activity.type === "update" ? (
-
- ) : activity.type === "delete" ? (
-
- ) : activity.type === "report" ? (
-
- ) : (
-
- )}
-
-
-
{activity.action}
-
- By {activity.user} • {activity.time}
-
-
-
- ))}
-
-
-
-
-
-
- )
-}
diff --git a/src/components/AdminDashboard/DashboardLayout.tsx b/src/components/AdminDashboard/DashboardLayout.tsx
deleted file mode 100644
index 57c6485..0000000
--- a/src/components/AdminDashboard/DashboardLayout.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import Sidebar from "./Sidebar"
-import Header from "./Header"
-import StatsCards from "./StatsCards"
-import QuickActions from "./QuickActions"
-import RecentActivity from "./RecentActivity"
-
-export default function DashboardLayout() {
- return (
-
- )
-}
\ No newline at end of file
diff --git a/src/components/AdminDashboard/Header.tsx b/src/components/AdminDashboard/Header.tsx
deleted file mode 100644
index 9050f1f..0000000
--- a/src/components/AdminDashboard/Header.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Search, Bell, ChevronDown } from "lucide-react"
-
-export default function Header() {
- return (
-
- )
-}
\ No newline at end of file
diff --git a/src/components/AdminDashboard/Sidebar.tsx b/src/components/AdminDashboard/Sidebar.tsx
deleted file mode 100644
index f946f1e..0000000
--- a/src/components/AdminDashboard/Sidebar.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { Link } from "react-router-dom"
-import { Home, User, FileText, Database, Settings } from "lucide-react"
-
-export default function Sidebar() {
- return (
-
-
-
Italian Migrants
-
Northern Territory DB
-
-
-
-
-
-
- )
- }
\ No newline at end of file
diff --git a/src/components/AdminDashboard/StatsCards.tsx b/src/components/AdminDashboard/StatsCards.tsx
deleted file mode 100644
index 71889a8..0000000
--- a/src/components/AdminDashboard/StatsCards.tsx
+++ /dev/null
@@ -1,65 +0,0 @@
-import { motion } from "framer-motion"
-import { Users, Database, Clock, Calendar } from "lucide-react"
-
-export default function StatsCards() {
- return (
-
-
-
-
-
-
-
Total Records
-
1,248
-
-
-
-
-
-
+86 this month
-
-
-
-
-
-
-
3 require attention
-
-
- )
-}
\ No newline at end of file
diff --git a/src/components/Migrants.tsx b/src/components/Migrants.tsx
deleted file mode 100644
index 59b765e..0000000
--- a/src/components/Migrants.tsx
+++ /dev/null
@@ -1,602 +0,0 @@
-"use client"
-
-import { useState, useEffect } from "react"
-import { useNavigate } from "react-router-dom"
-import { motion } from "framer-motion"
-import { Link } from "react-router-dom"
-import {
- Home,
- User,
- FileText,
- Database,
- Settings,
- Search,
- Filter,
- MoreHorizontal,
- Edit,
- Upload,
- Trash2,
- Plus,
- ChevronUp,
-} from "lucide-react"
-
-interface Migrant {
- id: number
- name: string
- birthDate: string
- arrivalDate: string
- origin: string
- status: "Verified" | "Incomplete" | "Pending"
- photos: number
-}
-
-export default function Migrants() {
- const [isFirstLoad, setIsFirstLoad] = useState(true)
- const [migrants, setMigrants] = useState([])
- const [selectedMigrants, setSelectedMigrants] = useState([])
- const [activeDropdown, setActiveDropdown] = useState(null)
- const [searchQuery, setSearchQuery] = useState("")
- const [arrivalDateMin, setArrivalDateMin] = useState("")
- const [arrivalDateMax, setArrivalDateMax] = useState("")
- const navigate = useNavigate()
-
- // Add a handleLogout function that clears the adminNavigation flag
- const handleLogout = () => {
- localStorage.removeItem("adminToken")
- localStorage.removeItem("adminNavigation") // Clear navigation flag on logout
-
-
- navigate("/login")
- }
-
- // Check authentication and load data
- useEffect(() => {
- const token = localStorage.getItem("adminToken")
- if (!token) {
- navigate("/login")
- return
- }
-
- // Check if we're navigating from another admin page
- const hasVisitedAdmin = localStorage.getItem("adminNavigation")
-
- // Skip loading if we're navigating from another admin page
- if (hasVisitedAdmin) {
- setIsFirstLoad(false)
-
- // Simulate API call to fetch migrants data without loading state
- setMigrants([
- {
- id: 3,
- name: "Antonio Esposito",
- birthDate: "2/18/1940",
- arrivalDate: "9/5/1962",
- origin: "Rome, Italy",
- status: "Verified",
- photos: 5,
- },
- {
- id: 8,
- name: "Giovanna Ferraro",
- birthDate: "3/11/1955",
- arrivalDate: "8/5/1978",
- origin: "Turin, Italy",
- status: "Incomplete",
- photos: 0,
- },
- {
- id: 5,
- name: "Giuseppe Colombo",
- birthDate: "4/7/1935",
- arrivalDate: "11/28/1955",
- origin: "Venice, Italy",
- status: "Verified",
- photos: 2,
- },
- {
- id: 4,
- name: "Lucia Romano",
- birthDate: "8/30/1958",
- arrivalDate: "1/10/1980",
- origin: "Milan, Italy",
- status: "Incomplete",
- photos: 0,
- },
- {
- id: 1,
- name: "Marco Rossi",
- birthDate: "5/12/1945",
- arrivalDate: "3/15/1968",
- origin: "Sicily, Italy",
- status: "Verified",
- photos: 3,
- },
- {
- id: 6,
- name: "Maria Ricci",
- birthDate: "12/15/1950",
- arrivalDate: "6/20/1972",
- origin: "Florence, Italy",
- status: "Pending",
- photos: 1,
- },
- {
- id: 7,
- name: "Paolo Marino",
- birthDate: "9/22/1943",
- arrivalDate: "4/17/1965",
- origin: "Palermo, Italy",
- status: "Verified",
- photos: 4,
- },
- ])
- } else {
- // Set flag to indicate we've visited an admin page
- localStorage.setItem("adminNavigation", "true")
-
- // Only show loading state on first load
- if (isFirstLoad) {
- // Simulate API call to fetch migrants data
- setTimeout(() => {
- setMigrants([
- {
- id: 3,
- name: "Antonio Esposito",
- birthDate: "2/18/1940",
- arrivalDate: "9/5/1962",
- origin: "Rome, Italy",
- status: "Verified",
- photos: 5,
- },
- {
- id: 8,
- name: "Giovanna Ferraro",
- birthDate: "3/11/1955",
- arrivalDate: "8/5/1978",
- origin: "Turin, Italy",
- status: "Incomplete",
- photos: 0,
- },
- {
- id: 5,
- name: "Giuseppe Colombo",
- birthDate: "4/7/1935",
- arrivalDate: "11/28/1955",
- origin: "Venice, Italy",
- status: "Verified",
- photos: 2,
- },
- {
- id: 4,
- name: "Lucia Romano",
- birthDate: "8/30/1958",
- arrivalDate: "1/10/1980",
- origin: "Milan, Italy",
- status: "Incomplete",
- photos: 0,
- },
- {
- id: 1,
- name: "Marco Rossi",
- birthDate: "5/12/1945",
- arrivalDate: "3/15/1968",
- origin: "Sicily, Italy",
- status: "Verified",
- photos: 3,
- },
- {
- id: 6,
- name: "Maria Ricci",
- birthDate: "12/15/1950",
- arrivalDate: "6/20/1972",
- origin: "Florence, Italy",
- status: "Pending",
- photos: 1,
- },
- {
- id: 7,
- name: "Paolo Marino",
- birthDate: "9/22/1943",
- arrivalDate: "4/17/1965",
- origin: "Palermo, Italy",
- status: "Verified",
- photos: 4,
- },
- ])
- setIsFirstLoad(false)
- }, 1000)
- }
- }
- }, [isFirstLoad, navigate])
-
- const handleSelectMigrant = (id: number) => {
- setSelectedMigrants((prev) => (prev.includes(id) ? prev.filter((migrantId) => migrantId !== id) : [...prev, id]))
- }
-
- const handleSelectAll = () => {
- if (selectedMigrants.length === migrants.length) {
- setSelectedMigrants([])
- } else {
- setSelectedMigrants(migrants.map((migrant) => migrant.id))
- }
- }
-
- const handleDropdownToggle = (id: number) => {
- setActiveDropdown(activeDropdown === id ? null : id)
- }
-
-
- const filteredMigrants = migrants.filter((migrant) => migrant.name.toLowerCase().includes(searchQuery.toLowerCase()))
-
- // If it's the first load, show a loading state
- if (isFirstLoad) {
- return (
-
-
-
-
Loading migrants data...
-
-
- )
- }
-
- return (
-
- {/* Sidebar */}
-
-
-
Italian Migrants
-
Northern Territory DB
-
-
-
-
- {/* Add this to the bottom user section in the sidebar */}
-
-
-
- {/* Main content */}
-
- {/* Header */}
-
-
-
Admin Portal
-
Northern Territory
-
-
-
- {/* Migrants Management content */}
-
-
-
-
-
Migrants Management
-
Manage and organize migrant records
-
-
-
-
- {/* Migrants Database */}
-
-
-
Migrants Database
-
-
- {/* Search and filters */}
-
-
-
-
-
-
- Show:
-
-
-
-
-
-
-
-
- {/* Table */}
-
-
- {/* Pagination */}
-
-
-
-
-
-
-
-
- Showing 1 to{" "}
- {filteredMigrants.length} of{" "}
- {filteredMigrants.length} results
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/src/components/admin/AddMigrant.tsx b/src/components/admin/AddMigrant.tsx
new file mode 100644
index 0000000..e5f26db
--- /dev/null
+++ b/src/components/admin/AddMigrant.tsx
@@ -0,0 +1,126 @@
+"use client"
+
+import { useState } from "react";
+import { ArrowLeft, Save } from "lucide-react";
+import { Link } from "react-router-dom";
+import apiService from "@/services/apiService";
+import { Button } from "@/components/ui/button";
+import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
+
+import Header from "../layout/Header";
+import Sidebar from "../layout/Sidebar";
+import { PersonalInfoTab } from "./migrant/PersonalInfoTab";
+import { MigrationDetailsTab } from "./migrant/MigrationDetailsTab";
+import { LocationsTab } from "./migrant/LocationsTab";
+import { InterneeDetailsTab } from "./migrant/InterneeDetailsTab";
+import { PhotosTab } from "./migrant/PhotosTab";
+import { NotesTab } from "./migrant/NotesTab";
+
+export default function AddUserPage() {
+ const [formData, setFormData] = useState({
+ surname: "",
+ christian_name: "",
+ full_name: "",
+ date_of_birth: "",
+ date_of_death: "",
+ place_of_birth: "",
+ home_at_death: "",
+ occupation: "",
+ names_of_parents: "",
+ names_of_children: "",
+ data_source: "",
+ reference: "",
+ cav: "",
+ id_card_no: "",
+ date_of_arrival_australia: "",
+ date_of_arrival_nt: "",
+ date_of_naturalisation: "",
+ corps_issued: "",
+ no_of_cert: "",
+ issued_at: "",
+ });
+
+ const handleInputChange = (e: React.ChangeEvent) => {
+ const { id, value } = e.target;
+ setFormData(prev => ({ ...prev, [id]: value }));
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ try {
+ const res = await apiService.createPerson(formData);
+ alert("Migrant created successfully!");
+ console.log(res);
+ } catch (err) {
+ alert("Failed to create migrant.");
+ console.error(err);
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ Add New Migrant
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/admin/AdminDashboard.tsx b/src/components/admin/AdminDashboard.tsx
new file mode 100644
index 0000000..63919d7
--- /dev/null
+++ b/src/components/admin/AdminDashboard.tsx
@@ -0,0 +1,172 @@
+import { useState } from "react";
+import {
+ BarChart3,
+ Calendar,
+ Clock,
+ Database,
+ FileText,
+ PlusCircle,
+ Search,
+ User,
+ Users,
+} from "lucide-react";
+import { Link } from "react-router-dom";
+
+import { Button } from "@/components/ui/button";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import {
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+} from "@/components/ui/tabs";
+
+import Header from "../layout/Header";
+import Sidebar from "../layout/Sidebar";
+import RecentActivityList from "../common/RecentActivity";
+import StatCard from "../common/StatCard";
+
+export default function DashboardPage() {
+ const [searchQuery, setSearchQuery] = useState("");
+
+ return (
+
+
+
+
+
+
+
+ Welcome, Admin
+
+
+ Here's an overview of your Italian Migrants Database
+
+
+
+
+ }
+ />
+ }
+ />
+ }
+ />
+ }
+ />
+
+
+
+
+
+ Quick Search
+ Find migrant records quickly
+
+
+
+
+ setSearchQuery(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ Migration Trends
+ Yearly migration patterns
+
+
+
+ {[35, 45, 20, 30, 75, 60, 40, 80, 90, 50].map((height, i) => (
+
+
+ {1950 + i * 5}: {height} migrants
+
+
+
{1950 + i * 5}
+
+ ))}
+
+
+
+
+
+
+
+
+
+ Recent Activity
+ Pending Reviews
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
No Pending Reviews
+
All migrant records have been reviewed.
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/LoginPage.tsx b/src/components/admin/LoginPage.tsx
similarity index 93%
rename from src/components/LoginPage.tsx
rename to src/components/admin/LoginPage.tsx
index 320d941..055406b 100644
--- a/src/components/LoginPage.tsx
+++ b/src/components/admin/LoginPage.tsx
@@ -7,6 +7,7 @@ import { motion } from "framer-motion"
import { useNavigate } from "react-router-dom"
import { Eye, EyeOff, Lock, Mail } from "lucide-react"
import { Link } from "react-router-dom";
+import apiService from "@/services/apiService"
export default function LoginPage() {
const [email, setEmail] = useState("")
@@ -23,19 +24,20 @@ export default function LoginPage() {
// Simulate API call for authentication
try {
- // In a real application, this would be an API call to your Laravel backend
- await new Promise((resolve) => setTimeout(resolve, 1500))
-
- // For demo purposes, hardcoded check
- if (email === "admin@example.com" && password === "password") {
- // Store token in localStorage or cookies
- localStorage.setItem("adminToken", "demo-token-12345")
- navigate("/admin")
+ const response = await apiService.login({
+ email,
+ password,
+ })
+ console.log("Response:", response.data)
+ alert("Login successful!")
+ navigate("/admin")
+ } catch (error: any) {
+ console.error("Error submitting form:", error)
+ if (error.response && error.response.data && error.response.data.message) {
+ setError(error.response.data.message)
} else {
- setError("Invalid email or password")
+ setError("Login failed. Please check your input and try again.")
}
- } catch (err) {
- setError("Authentication failed. Please try again.")
} finally {
setIsLoading(false)
}
diff --git a/src/components/admin/Migrants.tsx b/src/components/admin/Migrants.tsx
new file mode 100644
index 0000000..d4ef695
--- /dev/null
+++ b/src/components/admin/Migrants.tsx
@@ -0,0 +1,245 @@
+"use client"
+
+import { useState } from "react"
+import { ArrowUpDown, Download, Filter, MoreHorizontal, PlusCircle, Search, Trash2, Upload } from "lucide-react"
+import {Link} from "react-router-dom"
+
+import { Button } from "@/components/ui/button"
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
+import { Checkbox } from "@/components/ui/checkbox"
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
+import { Input } from "@/components/ui/input"
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
+
+import Header from "@/components/layout/Header"
+import Sidebar from "@/components/layout/Sidebar"
+
+// Sample data for migrants
+const migrants = [
+ {
+ id: 1,
+ name: "Marco Rossi",
+ birthDate: "1935-05-12",
+ birthPlace: "Rome, Italy",
+ arrivalDate: "1952-08-23",
+ occupation: "Carpenter",
+ hasPhotos: true,
+ },
+ {
+ id: 2,
+ name: "Sofia Bianchi",
+ birthDate: "1942-11-03",
+ birthPlace: "Naples, Italy",
+ arrivalDate: "1960-02-15",
+ occupation: "Seamstress",
+ hasPhotos: true,
+ },
+ {
+ id: 3,
+ name: "Antonio Esposito",
+ birthDate: "1928-07-22",
+ birthPlace: "Milan, Italy",
+ arrivalDate: "1950-10-05",
+ occupation: "Farmer",
+ hasPhotos: false,
+ },
+ {
+ id: 4,
+ name: "Lucia Romano",
+ birthDate: "1940-03-18",
+ birthPlace: "Florence, Italy",
+ arrivalDate: "1958-06-30",
+ occupation: "Teacher",
+ hasPhotos: true,
+ },
+ {
+ id: 5,
+ name: "Giuseppe Colombo",
+ birthDate: "1932-09-08",
+ birthPlace: "Venice, Italy",
+ arrivalDate: "1955-12-10",
+ occupation: "Fisherman",
+ hasPhotos: false,
+ },
+]
+
+export default function MigrantsPage() {
+ const [searchQuery, setSearchQuery] = useState("")
+ const [selectedMigrants, setSelectedMigrants] = useState([])
+
+ const toggleSelectAll = () => {
+ if (selectedMigrants.length === migrants.length) {
+ setSelectedMigrants([])
+ } else {
+ setSelectedMigrants(migrants.map((m) => m.id))
+ }
+ }
+
+ const toggleSelectMigrant = (id: number) => {
+ if (selectedMigrants.includes(id)) {
+ setSelectedMigrants(selectedMigrants.filter((m) => m !== id))
+ } else {
+ setSelectedMigrants([...selectedMigrants, id])
+ }
+ }
+
+
+
+ const filteredMigrants = migrants.filter(
+ (migrant) =>
+ migrant.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ migrant.birthPlace.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ migrant.occupation.toLowerCase().includes(searchQuery.toLowerCase()),
+ )
+
+ return (
+
+
+
+
+
+
+
Migrants Database
+
+
+
+
+
+
+
+ Search & Filter
+
+
+
+
+
+
+ setSearchQuery(e.target.value)}
+ />
+
+
+
+
+
+
+ {selectedMigrants.length > 0 && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ 0}
+ onCheckedChange={toggleSelectAll}
+ aria-label="Select all"
+ />
+
+
+
+
+
+
+
+ Birth Place
+
+
+
+ Occupation
+ Photos
+ Actions
+
+
+
+ {filteredMigrants.length === 0 ? (
+
+
+ No migrants found matching your search criteria.
+
+
+ ) : (
+ filteredMigrants.map((migrant) => (
+
+
+ toggleSelectMigrant(migrant.id)}
+ aria-label={`Select ${migrant.name}`}
+ />
+
+ {migrant.name}
+ {new Date(migrant.birthDate).toLocaleDateString()}
+ {migrant.birthPlace}
+ {new Date(migrant.arrivalDate).toLocaleDateString()}
+ {migrant.occupation}
+
+ {migrant.hasPhotos ? (
+
+ Yes
+
+ ) : (
+
+ No
+
+ )}
+
+
+
+
+
+
+
+
+ Edit
+
+ Delete
+
+
+
+
+ ))
+ )}
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/admin/Register.tsx b/src/components/admin/Register.tsx
new file mode 100644
index 0000000..217f399
--- /dev/null
+++ b/src/components/admin/Register.tsx
@@ -0,0 +1,231 @@
+"use client"
+
+import { useState } from "react"
+import { motion } from "framer-motion"
+import { Eye, EyeOff, Lock, Mail } from "lucide-react"
+import { Link, useNavigate } from "react-router-dom"
+import apiService from "@/services/apiService"
+
+export default function SimpleForm() {
+ const [error, setError] = useState("")
+ const [showPassword, setShowPassword] = useState(false)
+ const [isLoading, setIsLoading] = useState(false)
+
+ const [email, setEmail] = useState("")
+ const [password, setPassword] = useState("")
+ const [name, setName] = useState("")
+
+ const navigate = useNavigate()
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ setIsLoading(true)
+ setError("")
+
+ try {
+ const response = await apiService.register({
+ name,
+ email,
+ password,
+ })
+
+ console.log("Response:", response.data)
+ alert("Registration successful!")
+ setName("")
+ setEmail("")
+ setPassword("")
+ } catch (error: any) {
+ console.error("Error submitting form:", error)
+ if (error.response && error.response.data && error.response.data.message) {
+ setError(error.response.data.message)
+ } else {
+ setError("Registration failed. Please check your input and try again.")
+ }
+ } finally {
+ setIsLoading(false)
+ navigate("/login")
+ }
+ }
+
+ return (
+
+
+
+
+
+
+
+
+ Admin Access
+
+ Northern Territory Italian Migration History
+
+
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+
+
+
+
+ Return to public site
+
+
+
+
+
+
+ © {new Date().getFullYear()} Northern Territory Italian Migration History Project
+
+
+ )
+}
diff --git a/src/components/admin/migrant/InterneeDetailsTab.tsx b/src/components/admin/migrant/InterneeDetailsTab.tsx
new file mode 100644
index 0000000..4980fcb
--- /dev/null
+++ b/src/components/admin/migrant/InterneeDetailsTab.tsx
@@ -0,0 +1,41 @@
+// InterneeDetailsTab.jsx
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Textarea } from "@/components/ui/textarea";
+
+export function InterneeDetailsTab() {
+ return (
+
+
+ Internee Details
+ Information about internment (if applicable)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/admin/migrant/LocationsTab.tsx b/src/components/admin/migrant/LocationsTab.tsx
new file mode 100644
index 0000000..f3c93a9
--- /dev/null
+++ b/src/components/admin/migrant/LocationsTab.tsx
@@ -0,0 +1,35 @@
+// LocationsTab.jsx
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Checkbox } from "@/components/ui/checkbox";
+import { Label } from "@/components/ui/label";
+
+export function LocationsTab() {
+ return (
+
+
+ Location Information
+ Check all applicable locations
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/admin/migrant/MigrationDetailsTab.tsx b/src/components/admin/migrant/MigrationDetailsTab.tsx
new file mode 100644
index 0000000..96cc704
--- /dev/null
+++ b/src/components/admin/migrant/MigrationDetailsTab.tsx
@@ -0,0 +1,123 @@
+// MigrationDetailsTab.jsx
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Calendar } from "lucide-react";
+
+export function MigrationDetailsTab({ formData, handleInputChange }: {
+ formData: {
+ date_of_arrival_australia: string
+ date_of_arrival_nt: string
+ date_of_naturalisation: string
+ corps_issued: string
+ no_of_cert: string
+ issued_at: string
+ }
+ handleInputChange: (e: React.ChangeEvent) => void
+}) {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/admin/migrant/NotesTab.tsx b/src/components/admin/migrant/NotesTab.tsx
new file mode 100644
index 0000000..73a67d1
--- /dev/null
+++ b/src/components/admin/migrant/NotesTab.tsx
@@ -0,0 +1,25 @@
+// NotesTab.jsx
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Label } from "@/components/ui/label";
+import { Textarea } from "@/components/ui/textarea";
+
+export function NotesTab() {
+ return (
+
+
+ Additional Notes
+ Any other relevant information about this migrant
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/admin/migrant/PersonalInfoTab.tsx b/src/components/admin/migrant/PersonalInfoTab.tsx
new file mode 100644
index 0000000..49f5451
--- /dev/null
+++ b/src/components/admin/migrant/PersonalInfoTab.tsx
@@ -0,0 +1,198 @@
+// PersonalInfoTab.jsx
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Textarea } from "@/components/ui/textarea";
+import { Calendar } from "lucide-react";
+
+export function PersonalInfoTab({ formData, handleInputChange }: {
+ formData: {
+ surname: string
+ christian_name: string
+ full_name: string
+ date_of_birth: string
+ date_of_death: string
+ place_of_birth: string
+ home_at_death: string
+ occupation: string
+ names_of_parents: string
+ names_of_children: string
+ data_source: string
+ reference: string
+ cav: string
+ id_card_no: string
+ }
+ handleInputChange: (e: React.ChangeEvent) => void
+}) {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/admin/migrant/PhotosTab.tsx b/src/components/admin/migrant/PhotosTab.tsx
new file mode 100644
index 0000000..e13bcf3
--- /dev/null
+++ b/src/components/admin/migrant/PhotosTab.tsx
@@ -0,0 +1,107 @@
+// PhotosTab.jsx
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Label } from "@/components/ui/label";
+import { ImageIcon, Upload, X } from "lucide-react";
+import { useState } from "react";
+
+export function PhotosTab() {
+ const [photos, setPhotos] = useState([]);
+ const [mainPhotoIndex, setMainPhotoIndex] = useState(null);
+
+ // Simulate photo upload
+ const handlePhotoUpload = () => {
+ // In a real app, this would handle file upload
+ const newPhoto = `/placeholder.svg?height=200&width=200`;
+ setPhotos([...photos, newPhoto]);
+ if (mainPhotoIndex === null) {
+ setMainPhotoIndex(photos.length);
+ }
+ };
+
+ const removePhoto = (index: number) => {
+ const newPhotos = [...photos];
+ newPhotos.splice(index, 1);
+ setPhotos(newPhotos);
+
+ if (mainPhotoIndex === index) {
+ setMainPhotoIndex(newPhotos.length > 0 ? 0 : null);
+ } else if (mainPhotoIndex !== null && mainPhotoIndex > index) {
+ setMainPhotoIndex(mainPhotoIndex - 1);
+ }
+ };
+
+ const setAsMainPhoto = (index: number) => {
+ setMainPhotoIndex(index);
+ };
+
+ return (
+
+
+ Photos & Documents
+ Upload photos and documents related to this migrant
+
+
+
+
+
+
+ {photos.length > 0 ? (
+
+
+
+ {photos.map((photo, index) => (
+
+

+
+
+ {mainPhotoIndex === index ? (
+ Main Photo
+ ) : (
+
+ )}
+
+
+
+
+ ))}
+
+
+ ) : (
+
+
+
No photos uploaded
+
+ Upload photos of the migrant or related documents
+
+
+ )}
+
+
+ );
+}
diff --git a/src/components/LoadingSpinner.tsx b/src/components/common/LoadingSpinner.tsx
similarity index 100%
rename from src/components/LoadingSpinner.tsx
rename to src/components/common/LoadingSpinner.tsx
diff --git a/src/components/AdminDashboard/QuickActions.tsx b/src/components/common/QuickActions.tsx
similarity index 100%
rename from src/components/AdminDashboard/QuickActions.tsx
rename to src/components/common/QuickActions.tsx
diff --git a/src/components/AdminDashboard/RecentActivity.tsx b/src/components/common/RecentActivity.tsx
similarity index 100%
rename from src/components/AdminDashboard/RecentActivity.tsx
rename to src/components/common/RecentActivity.tsx
diff --git a/src/components/common/StatCard.tsx b/src/components/common/StatCard.tsx
new file mode 100644
index 0000000..23b9f44
--- /dev/null
+++ b/src/components/common/StatCard.tsx
@@ -0,0 +1,25 @@
+import type { ReactNode } from "react"
+
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
+
+interface StatCardProps {
+ title: string
+ value: string
+ description: string
+ icon: ReactNode
+}
+
+export default function StatCard({ title, value, description, icon }: StatCardProps) {
+ return (
+
+
+ {title}
+ {icon}
+
+
+ {value}
+ {description}
+
+
+ )
+}
diff --git a/src/components/common/recent-activity-list.tsx b/src/components/common/recent-activity-list.tsx
new file mode 100644
index 0000000..d787044
--- /dev/null
+++ b/src/components/common/recent-activity-list.tsx
@@ -0,0 +1,71 @@
+import { Edit, FileText, Plus, Trash2, Upload } from "lucide-react"
+
+// Sample activity data
+const activities = [
+ {
+ id: 1,
+ action: "added",
+ subject: "Marco Rossi",
+ timestamp: "2 hours ago",
+ icon: Plus,
+ iconColor: "text-green-600",
+ user: "Admin",
+ },
+ {
+ id: 2,
+ action: "updated",
+ subject: "Sofia Bianchi",
+ timestamp: "Yesterday",
+ icon: Edit,
+ iconColor: "text-blue-600",
+ user: "Admin",
+ },
+ {
+ id: 3,
+ action: "uploaded",
+ subject: "5 photos for Antonio Esposito",
+ timestamp: "2 days ago",
+ icon: Upload,
+ iconColor: "text-purple-600",
+ user: "Admin",
+ },
+ {
+ id: 4,
+ action: "deleted",
+ subject: "Duplicate record",
+ timestamp: "3 days ago",
+ icon: Trash2,
+ iconColor: "text-red-600",
+ user: "Admin",
+ },
+ {
+ id: 5,
+ action: "added",
+ subject: "Document for Lucia Romano",
+ timestamp: "1 week ago",
+ icon: FileText,
+ iconColor: "text-amber-600",
+ user: "Admin",
+ },
+]
+
+export default function RecentActivityList() {
+ return (
+
+ {activities.map((activity) => (
+
+
+
+
+ {activity.user} {activity.action}{" "}
+ {activity.subject}
+
+
{activity.timestamp}
+
+
+ ))}
+
+ )
+}
diff --git a/src/components/HeroSection.tsx b/src/components/home/HeroSection.tsx
similarity index 100%
rename from src/components/HeroSection.tsx
rename to src/components/home/HeroSection.tsx
diff --git a/src/components/HistoricalContext.tsx b/src/components/home/HistoricalContext.tsx
similarity index 98%
rename from src/components/HistoricalContext.tsx
rename to src/components/home/HistoricalContext.tsx
index c56fc6c..4133d75 100644
--- a/src/components/HistoricalContext.tsx
+++ b/src/components/home/HistoricalContext.tsx
@@ -1,4 +1,4 @@
-import { Card, CardContent } from "./ui/card";
+import { Card, CardContent } from "@/components/ui/card";
interface HistoricalContextProps {
year: number;
diff --git a/src/components/IntroSection.tsx b/src/components/home/IntroSection.tsx
similarity index 100%
rename from src/components/IntroSection.tsx
rename to src/components/home/IntroSection.tsx
diff --git a/src/components/MigrantProfileComponent.tsx b/src/components/home/MigrantProfileComponent.tsx
similarity index 97%
rename from src/components/MigrantProfileComponent.tsx
rename to src/components/home/MigrantProfileComponent.tsx
index bbf08af..ad76170 100644
--- a/src/components/MigrantProfileComponent.tsx
+++ b/src/components/home/MigrantProfileComponent.tsx
@@ -5,9 +5,9 @@ import { Link } from "react-router-dom";
import { ArrowLeft } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
-import PhotoGallery from "@/components/PhotoGallery";
-import RelatedMigrants from "@/components/RelatedMigrants";
-import HistoricalContext from "@/components/HistoricalContext";
+import PhotoGallery from "@/components/home/PhotoGallery";
+import RelatedMigrants from "@/components/home/RelatedMigrants";
+import HistoricalContext from "@/components/home/HistoricalContext";
import AnimatedImage from "@/components/ui/animated-image";
import type { MigrantProfile as MigrantProfileType } from "@/types/migrant";
diff --git a/src/components/PhotoGallery.tsx b/src/components/home/PhotoGallery.tsx
similarity index 100%
rename from src/components/PhotoGallery.tsx
rename to src/components/home/PhotoGallery.tsx
diff --git a/src/components/RelatedMigrants.tsx b/src/components/home/RelatedMigrants.tsx
similarity index 91%
rename from src/components/RelatedMigrants.tsx
rename to src/components/home/RelatedMigrants.tsx
index a8b8c46..7cd3118 100644
--- a/src/components/RelatedMigrants.tsx
+++ b/src/components/home/RelatedMigrants.tsx
@@ -1,8 +1,8 @@
import { Link } from "react-router-dom";
-import type { RelatedMigrant } from "../types/migrant";
-import { Card, CardContent } from "./ui/card";
+import type { RelatedMigrant } from "@/types/migrant";
+import { Card, CardContent } from "@/components/ui/card";
import { motion } from "framer-motion";
-import AnimatedImage from "./ui/animated-image";
+import AnimatedImage from "@/components/ui/animated-image";
interface RelatedMigrantsProps {
migrants: RelatedMigrant[];
diff --git a/src/components/SearchForm.tsx b/src/components/home/SearchForm.tsx
similarity index 100%
rename from src/components/SearchForm.tsx
rename to src/components/home/SearchForm.tsx
diff --git a/src/components/SearchResults.tsx b/src/components/home/SearchResults.tsx
similarity index 100%
rename from src/components/SearchResults.tsx
rename to src/components/home/SearchResults.tsx
diff --git a/src/components/SearchSection.tsx b/src/components/home/SearchSection.tsx
similarity index 96%
rename from src/components/SearchSection.tsx
rename to src/components/home/SearchSection.tsx
index 2281faf..2512ba9 100644
--- a/src/components/SearchSection.tsx
+++ b/src/components/home/SearchSection.tsx
@@ -3,8 +3,8 @@
import { useState } from "react";
import SearchForm from "./SearchForm";
import SearchResults from "./SearchResults";
-import type { SearchParams, SearchResult } from "../types/search";
-import apiService from "../services/apiService";
+import type { SearchParams, SearchResult } from "@/types/search";
+import apiService from "@/services/apiService";
import { Loader2 } from "lucide-react";
const SearchSection = () => {
diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx
new file mode 100644
index 0000000..dd85adb
--- /dev/null
+++ b/src/components/layout/Header.tsx
@@ -0,0 +1,40 @@
+import { Bell, HelpCircle, User } from "lucide-react"
+
+import { Button } from "@/components/ui/button"
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
+
+interface HeaderProps {
+ title: string
+}
+
+export default function Header({ title }: HeaderProps) {
+ return (
+
+ )
+}
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx
new file mode 100644
index 0000000..e9ee83c
--- /dev/null
+++ b/src/components/layout/Sidebar.tsx
@@ -0,0 +1,124 @@
+
+import { useState } from "react"
+import { BarChart3, FileText, Home, LogOut, Settings, Users } from "lucide-react"
+import { Link } from "react-router-dom"
+import { Button } from "@/components/ui/button"
+import { Separator } from "@/components/ui/separator"
+import apiService from "@/services/apiService"
+export default function Sidebar() {
+ const [collapsed, setCollapsed] = useState(false)
+
+ const handleLogout = async () => {
+ try {
+ await apiService.logout();
+ alert("Logged out successfully");
+ setTimeout(() => {
+ window.location.href = "/login";
+ }, 1000); // Delay so the alert shows
+ } catch (err) {
+ alert("Logout failed. Please try again.");
+ }
+ };
+
+ return (
+
+
+
+ {!collapsed && (
+
+
+ NT
+
+
Italian Migrants
+
+ )}
+ {collapsed && (
+
+ NT
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
new file mode 100644
index 0000000..defeb01
--- /dev/null
+++ b/src/components/ui/checkbox.tsx
@@ -0,0 +1,30 @@
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { CheckIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Checkbox({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+ )
+}
+
+export { Checkbox }
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..0d6741b
--- /dev/null
+++ b/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,255 @@
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function DropdownMenu({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuContent({
+ className,
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function DropdownMenuGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+ variant?: "default" | "destructive"
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuRadioGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function DropdownMenuSub({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+function DropdownMenuSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent,
+}
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
new file mode 100644
index 0000000..3cf4f89
--- /dev/null
+++ b/src/components/ui/separator.tsx
@@ -0,0 +1,26 @@
+import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator"
+
+import { cn } from "@/lib/utils"
+
+function Separator({
+ className,
+ orientation = "horizontal",
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Separator }
diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx
new file mode 100644
index 0000000..cd62aff
--- /dev/null
+++ b/src/components/ui/sonner.tsx
@@ -0,0 +1,23 @@
+import { useTheme } from "next-themes"
+import { Toaster as Sonner, ToasterProps } from "sonner"
+
+const Toaster = ({ ...props }: ToasterProps) => {
+ const { theme = "system" } = useTheme()
+
+ return (
+
+ )
+}
+
+export { Toaster }
diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx
new file mode 100644
index 0000000..5513a5c
--- /dev/null
+++ b/src/components/ui/table.tsx
@@ -0,0 +1,114 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Table({ className, ...props }: React.ComponentProps<"table">) {
+ return (
+
+ )
+}
+
+function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
+ return (
+
+ )
+}
+
+function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
+ return (
+
+ )
+}
+
+function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
+ return (
+ tr]:last:border-b-0",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
+ return (
+
+ )
+}
+
+function TableHead({ className, ...props }: React.ComponentProps<"th">) {
+ return (
+ [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableCell({ className, ...props }: React.ComponentProps<"td">) {
+ return (
+ | [role=checkbox]]:translate-y-[2px]",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function TableCaption({
+ className,
+ ...props
+}: React.ComponentProps<"caption">) {
+ return (
+
+ )
+}
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx
new file mode 100644
index 0000000..3d6f3ac
--- /dev/null
+++ b/src/components/ui/tabs.tsx
@@ -0,0 +1,64 @@
+import * as React from "react"
+import * as TabsPrimitive from "@radix-ui/react-tabs"
+
+import { cn } from "@/lib/utils"
+
+function Tabs({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsList({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsTrigger({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function TabsContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Tabs, TabsList, TabsTrigger, TabsContent }
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
new file mode 100644
index 0000000..7f21b5e
--- /dev/null
+++ b/src/components/ui/textarea.tsx
@@ -0,0 +1,18 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
+ return (
+
+ )
+}
+
+export { Textarea }
diff --git a/src/index.css b/src/index.css
index 5a7873a..f4c1e9b 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,2 +1,120 @@
@import "tailwindcss";
@import "tw-animate-css";
+
+@custom-variant dark (&:is(.dark *));
+
+@theme inline {
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --color-background: var(--background);
+ --color-foreground: var(--foreground);
+ --color-card: var(--card);
+ --color-card-foreground: var(--card-foreground);
+ --color-popover: var(--popover);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-primary: var(--primary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-secondary: var(--secondary);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-muted: var(--muted);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-accent: var(--accent);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-destructive: var(--destructive);
+ --color-border: var(--border);
+ --color-input: var(--input);
+ --color-ring: var(--ring);
+ --color-chart-1: var(--chart-1);
+ --color-chart-2: var(--chart-2);
+ --color-chart-3: var(--chart-3);
+ --color-chart-4: var(--chart-4);
+ --color-chart-5: var(--chart-5);
+ --color-sidebar: var(--sidebar);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-ring: var(--sidebar-ring);
+}
+
+:root {
+ --radius: 0.625rem;
+ --background: oklch(1 0 0);
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.205 0 0);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.922 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.556 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.556 0 0);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/src/main.tsx b/src/main.tsx
index 10ed13e..6f4ac9b 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,10 +1,10 @@
-import { StrictMode } from "react";
-import { createRoot } from "react-dom/client";
-import "./index.css";
-import App from "./App.tsx";
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import App from './App.tsx'
+import './index.css'
-createRoot(document.getElementById("root")!).render(
+createRoot(document.getElementById('root')!).render(
-
-);
+ ,
+)
diff --git a/src/pages/AdminDashboardPage.tsx b/src/pages/AdminDashboardPage.tsx
index 61d1b59..748b416 100644
--- a/src/pages/AdminDashboardPage.tsx
+++ b/src/pages/AdminDashboardPage.tsx
@@ -1,8 +1,8 @@
// src/pages/AdminDashboardPage.tsx
-import AdminDashboard from "@/components/AdminDashboard/DashboardLayout";
+import DashboardLayout from "@/components/admin/AdminDashboard";
const AdminDashboardPage = () => {
- return ;
+ return ;
};
export default AdminDashboardPage;
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx
index 508ab78..8f87244 100644
--- a/src/pages/HomePage.tsx
+++ b/src/pages/HomePage.tsx
@@ -1,6 +1,6 @@
-import HeroSection from "../components/HeroSection";
-import IntroSection from "../components/IntroSection";
-import SearchSection from "../components/SearchSection";
+import HeroSection from "../components/home/HeroSection";
+import IntroSection from "../components/home/IntroSection";
+import SearchSection from "../components/home/SearchSection";
const HomePage = () => {
return (
diff --git a/src/pages/MigrantProfilePage.tsx b/src/pages/MigrantProfilePage.tsx
index de33b41..060576f 100644
--- a/src/pages/MigrantProfilePage.tsx
+++ b/src/pages/MigrantProfilePage.tsx
@@ -2,33 +2,13 @@
import { useEffect, useState } from "react";
import { useParams, useNavigate } from "react-router-dom";
-import apiService from "../services/apiService";
-import type { MigrantProfile } from "../types/migrant";
-import MigrantProfileComponent from "../components/MigrantProfileComponent";
+import apiService from "@/services/apiService";
+import type { MigrantProfile } from "@/types/migrant";
+import MigrantProfileComponent from "@/components/home/MigrantProfileComponent";
import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert";
-import { AlertCircle, Loader2 } from "lucide-react";
+import { AlertCircle, Loader2 } from "lucide-react";
import { Button } from "@/components/ui/button";
-
-// Helper function to calculate age at migration
-const calculateAgeAtMigration = (birthDate: string | null | undefined, migrationDate: string | null | undefined): number => {
- if (!birthDate || !migrationDate) return 0;
-
- try {
- const normalizedBirthDate = birthDate.includes('.') ? birthDate.split('.')[0] + 'Z' : birthDate;
- const normalizedMigrationDate = migrationDate.includes('.') ? migrationDate.split('.')[0] + 'Z' : migrationDate;
-
- const birthYear = new Date(normalizedBirthDate).getFullYear();
- const migrationYear = new Date(normalizedMigrationDate).getFullYear();
-
- // Simple year difference calculation
- const age = migrationYear - birthYear;
-
- return age >= 0 ? age : 0;
- } catch (error) {
- console.error(`Error calculating age: birth=${birthDate}, migration=${migrationDate}`, error);
- return 0;
- }
-};
+import { calculateAgeAtMigration } from "@/utils/date";
const MigrantProfilePage = () => {
const { id } = useParams<{ id: string }>();
diff --git a/src/services/apiService.ts b/src/services/apiService.ts
index 07def72..11aeb2a 100644
--- a/src/services/apiService.ts
+++ b/src/services/apiService.ts
@@ -1,66 +1,130 @@
import axios from "axios";
-import type { SearchResult, SearchParams } from "../types/search";
+import type { SearchResult, SearchParams } from "@/types/search";
+import type { CreatePersonPayload, Person, PaginatedResponse } from "@/types/api";
-// Load base URL from environment
+// API base URL from environment variables
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
-// Create Axios instance
+// Axios instance with base config
const api = axios.create({
baseURL: API_BASE_URL,
headers: { "Content-Type": "application/json" },
});
-// Request interceptor for logging
-api.interceptors.request.use((request) => {
- const fullUrl = `${request.baseURL || ""}${request.url || ""}`;
- console.log("[API] Request URL:", fullUrl);
-
- if (request.params) {
- console.log("[API] Params:", request.params);
+// Attach token to each request if available
+api.interceptors.request.use((config) => {
+ const token = localStorage.getItem("token");
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
}
-
- if (request.url?.includes("/migrants/")) {
- const id = request.url.split("/").pop();
- console.log("[API] Fetching migrant by ID:", id);
- }
-
- return request;
+ return config;
});
+// Handle unauthorized responses globally
+api.interceptors.response.use(
+ (res) => res,
+ (err) => {
+ if (err.response?.status === 401) {
+ localStorage.removeItem("token");
+ localStorage.removeItem("user");
+
+ window.location.href = "/login";
+ }
+ return Promise.reject(err);
+ }
+);
+
class ApiService {
- /**
- * Fetch a single migrant record by ID
- */
async getRecordById(id: string): Promise {
- const res = await api.get(`/api/migrants/${id}`);
- return res.data.data;
+ const { data } = await api.get(`/api/migrants/${id}`);
+ return data.data;
}
- /**
- * Search for people based on filters.
- * Filters out empty and "all" values.
- */
async searchPeople(params: SearchParams): Promise {
- const filtered = Object.fromEntries(
- Object.entries(params)
- .filter(([_, value]) => value && value !== "all")
- .map(([key, value]) => [key, value.toString()])
+ const filteredParams = Object.fromEntries(
+ Object.entries(params).filter(
+ ([_, value]) => value && value !== "all"
+ )
);
- if (Object.keys(filtered).length === 0) return [];
+ if (Object.keys(filteredParams).length === 0) return [];
try {
- const res = await api.get("/api/persons/search", {
- params: { ...filtered, exactMatch: "true" },
+ const { data } = await api.get("/api/persons/search", {
+ params: { ...filteredParams, exactMatch: "true" },
});
- return res.data.success && res.data.data ? res.data.data.data : [];
- } catch (err) {
+ return data.success && data.data ? data.data.data : [];
+ } catch (error) {
const message =
- err instanceof Error ? err.message : "Unknown error during search";
- throw new Error(`[API] Failed to search people: ${message}`);
+ error instanceof Error ? error.message : "Unknown search error";
+ throw new Error(`[API] Search failed: ${message}`);
}
}
+
+ async register(params: { name: string; email: string; password: string }) {
+ const { data } = await api.post("/api/register", params);
+ return data;
+ }
+
+ async login(params: { email: string; password: string }) {
+ const { data } = await api.post("/api/login", params);
+ localStorage.setItem("token", data.token);
+ localStorage.setItem("user", JSON.stringify(data.user));
+ return data;
+ }
+
+ async logout() {
+ try {
+ const { data } = await api.post("/api/logout");
+ localStorage.removeItem("token");
+ localStorage.removeItem("user");
+ return data;
+ } catch (error) {
+ console.error("[logout] Logout failed:", error);
+ throw error;
+ }
+ }
+
+ async createPerson(payload: CreatePersonPayload): Promise {
+ const { data } = await api.post("/api/persons", payload);
+ return data.data;
+ }
+
+ // ✅ Updated to support pagination
+ async getMigrants(page: number = 1): Promise> {
+ const { data } = await api.get(`/api/persons?page=${page}`);
+
+ if (data.success && data.data) {
+ return {
+ data: data.data.data,
+ meta: data.data.meta,
+ links: data.data.links,
+ };
+ }
+
+ return {
+ data: [],
+ meta: {
+ total: 0,
+ count: 0,
+ per_page: 10,
+ current_page: 1,
+ last_page: 1,
+ },
+ links: {
+ first: null,
+ last: null,
+ prev: null,
+ next: null,
+ },
+ };
+ }
+
+ async deleteMigrant(id: string) {
+ const { data } = await api.delete(`/api/persons/${id}`);
+ return data;
+ }
}
export default new ApiService();
diff --git a/src/types/api.ts b/src/types/api.ts
new file mode 100644
index 0000000..2ed4f4f
--- /dev/null
+++ b/src/types/api.ts
@@ -0,0 +1,121 @@
+ export interface User {
+ id: string;
+ name: string;
+ email: string;
+ }
+ // types/person.ts
+
+export interface Migration {
+ migration_id?: number;
+ person_id?: number;
+ date_of_arrival_aus?: string;
+ date_of_arrival_nt?: string;
+ arrival_period?: string;
+ data_source?: string;
+ created_at?: string;
+ updated_at?: string;
+}
+
+export interface Naturalization {
+ naturalization_id?: number;
+ person_id?: number;
+ date_of_naturalisation?: string;
+ no_of_cert?: string;
+ issued_at?: string;
+ created_at?: string;
+ updated_at?: string;
+}
+
+export interface Residence {
+ residence_id?: number;
+ person_id?: number;
+ town_or_city?: string;
+ home_at_death?: string;
+ created_at?: string;
+ updated_at?: string;
+}
+
+export interface Family {
+ family_id?: number;
+ person_id?: number;
+ name?: string;
+ relationship?: string;
+ notes?: string;
+ created_at?: string;
+ updated_at?: string;
+}
+
+export interface Internment {
+ internment_id?: number;
+ person_id?: number;
+ corps_issued?: string;
+ interned_in?: string;
+ sent_to?: string;
+ internee_occupation?: string;
+ internee_address?: string;
+ cav?: string;
+ created_at?: string;
+ updated_at?: string;
+}
+
+export interface Person {
+ person_id?: number;
+ surname?: string;
+ christian_name?: string;
+ full_name?: string;
+ date_of_birth?: string;
+ place_of_birth?: string;
+ date_of_death?: string;
+ occupation?: string;
+ additional_notes?: string;
+ reference?: string;
+ id_card_no?: string;
+ created_at?: string;
+ updated_at?: string;
+
+ migration?: Migration | null;
+ naturalization?: Naturalization | null;
+ residence?: Residence | null;
+ family?: Family | null;
+ internment?: Internment | null;
+}
+
+export interface CreatePersonPayload {
+ surname: string;
+ christian_name: string;
+ full_name: string;
+ date_of_birth?: string;
+ place_of_birth?: string;
+ date_of_death?: string;
+ occupation?: string;
+ additional_notes?: string;
+ reference?: string;
+ id_card_no?: string;
+
+ // Optional nested objects
+ migration?: Migration;
+ naturalization?: Naturalization;
+ residence?: Residence;
+ family?: Family;
+ internment?: Internment;
+}
+export interface PaginationMeta {
+ total: number;
+ count: number;
+ per_page: number;
+ current_page: number;
+ last_page: number;
+}
+
+export interface PaginationLinks {
+ first: string | null;
+ last: string | null;
+ prev: string | null;
+ next: string | null;
+}
+
+export interface PaginatedResponse {
+ data: T[];
+ meta: PaginationMeta;
+ links: PaginationLinks;
+}
\ No newline at end of file
diff --git a/src/utils/date.ts b/src/utils/date.ts
new file mode 100644
index 0000000..43176a4
--- /dev/null
+++ b/src/utils/date.ts
@@ -0,0 +1,19 @@
+export const calculateAgeAtMigration = (birthDate: string | null | undefined, migrationDate: string | null | undefined): number => {
+ if (!birthDate || !migrationDate) return 0;
+
+ try {
+ const normalizedBirthDate = birthDate.includes('.') ? birthDate.split('.')[0] + 'Z' : birthDate;
+ const normalizedMigrationDate = migrationDate.includes('.') ? migrationDate.split('.')[0] + 'Z' : migrationDate;
+
+ const birthYear = new Date(normalizedBirthDate).getFullYear();
+ const migrationYear = new Date(normalizedMigrationDate).getFullYear();
+
+ // Simple year difference calculation
+ const age = migrationYear - birthYear;
+
+ return age >= 0 ? age : 0;
+ } catch (error) {
+ console.error(`Error calculating age: birth=${birthDate}, migration=${migrationDate}`, error);
+ return 0;
+ }
+ };
\ No newline at end of file
diff --git a/src/utils/formatters.ts b/src/utils/formatters.ts
deleted file mode 100644
index 55b5c54..0000000
--- a/src/utils/formatters.ts
+++ /dev/null
@@ -1,18 +0,0 @@
-export const formatDate = (dateString: string | undefined): string => {
- if (!dateString) return "Unknown"
-
- // This is a simple formatter, but you could use libraries like date-fns for more complex formatting
- return dateString
-}
-
-export const formatYear = (year: number | undefined): string => {
- if (!year) return "Unknown"
- return year.toString()
-}
-
-export const formatName = (firstName: string, middleName?: string, lastName?: string): string => {
- let fullName = firstName
- if (middleName) fullName += ` ${middleName}`
- if (lastName) fullName += ` ${lastName}`
- return fullName
-}
|