diff --git a/.gitignore b/.gitignore index 96bf95e01f2b9d39a46788132f7c868cba955e41..ebbcbb58098b733691240ee72a8374f59a2f06f5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,13 +5,31 @@ build/ .cache/ .vite/ -# Local env & secrets +# ─── Local env & secrets ──────────────────────── .env* !.env.example -# ─── Go backend (we’ll add later) ─────────────── -/backend/bin/ -/backend/coverage.out +# ─── Python / FastAPI backend ─────────────────── +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Virtual environments +.venv/ +venv/ + +# Distribution / packaging +*.egg-info/ +dist/ +build/ +*.whl + +# Testing / coverage +.coverage +.pytest_cache/ +htmlcov/ +.mypy_cache/ # ─── Docker ───────────────────────────────────── *.log @@ -20,8 +38,9 @@ docker-compose.override.yml # ─── OS / editor cruft ───────────────────────── .DS_Store Thumbs.db +*.swp .idea/ .vscode/ -*.swp +# ─── Prisma (if you’re using it) ──────────────── /generated/prisma diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cfbbdf5c73c89e2bf5a912c63ab215e439ff9d2d..2b6f079c008349a90185eeb1d1b054cbe20c0ab2 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,10 +1,15 @@ import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import { AlertContext, type AlertContextProps, type AlertParams, LanguageContext, type LanguageContextProps } from '@ifrc-go/ui/contexts'; +import { useCallback, useMemo, useState } from 'react'; +import { unique } from '@togglecorp/fujs'; import RootLayout from './layouts/RootLayout'; import UploadPage from './pages/UploadPage'; import AnalyticsPage from './pages/AnalyticsPage'; import ExplorePage from './pages/ExplorePage'; import HelpPage from './pages/HelpPage'; import MapDetailPage from './pages/MapDetailPage'; +import DemoPage from './pages/DemoPage'; +import DevPage from './pages/DevPage'; const router = createBrowserRouter([ { @@ -15,11 +20,88 @@ const router = createBrowserRouter([ { path: '/analytics', element: }, { path: '/explore', element: }, { path: '/help', element: }, + { path: '/demo', element: }, + { path: '/dev', element: }, { path: '/map/:mapId', element: }, ], }, ]); +function Application() { + // ALERTS + const [alerts, setAlerts] = useState([]); + + const addAlert = useCallback((alert: AlertParams) => { + setAlerts((prevAlerts) => unique( + [...prevAlerts, alert], + (a) => a.name, + ) ?? prevAlerts); + }, [setAlerts]); + + const removeAlert = useCallback((name: AlertParams['name']) => { + setAlerts((prevAlerts) => { + const i = prevAlerts.findIndex((a) => a.name === name); + if (i === -1) { + return prevAlerts; + } + + const newAlerts = [...prevAlerts]; + newAlerts.splice(i, 1); + + return newAlerts; + }); + }, [setAlerts]); + + const updateAlert = useCallback((name: AlertParams['name'], paramsWithoutName: Omit) => { + setAlerts((prevAlerts) => { + const i = prevAlerts.findIndex((a) => a.name === name); + if (i === -1) { + return prevAlerts; + } + + const newAlerts = [...prevAlerts]; + newAlerts[i] = { + ...newAlerts[i], + ...paramsWithoutName, + }; + + return newAlerts; + }); + }, [setAlerts]); + + const alertContextValue = useMemo( + () => ({ + alerts, + addAlert, + removeAlert, + updateAlert, + }), + [alerts, addAlert, removeAlert, updateAlert], + ); + + // LANGUAGE + const languageContextValue = useMemo( + () => ({ + languageNamespaceStatus: {}, + setLanguageNamespaceStatus: () => {}, + currentLanguage: 'en', + setCurrentLanguage: () => {}, + strings: {}, + setStrings: () => {}, + registerNamespace: () => {}, + }), + [], + ); + + return ( + + + + + + ); +} + export default function App() { - return ; + return ; } diff --git a/frontend/src/components/Card.tsx b/frontend/src/components/Card.tsx new file mode 100644 index 0000000000000000000000000000000000000000..7eb1d15c2e0ad9e7f659547d0d0d6b7ca3bd5d88 --- /dev/null +++ b/frontend/src/components/Card.tsx @@ -0,0 +1,34 @@ +// src/components/Card.tsx +import React from 'react' + +export interface CardProps { + /** extra Tailwind classes to apply to the wrapper */ + className?: string + /** contents of the card */ + children: React.ReactNode +} + +/** + * A simple white card with rounded corners, padding and soft shadow. + * + * Usage: + * import Card from '../components/Card' + * + * + *

Title

+ *

Body content

+ *
+ */ +export default function Card({ children, className = '' }: CardProps) { + return ( +
+ {children} +
+ ) +} + diff --git a/frontend/src/components/HeaderNav.tsx b/frontend/src/components/HeaderNav.tsx index 95015504dd15c8695b8555f1962f1e8fb462f017..26e7c6c56ba515d95cd6376b4f849157740f935e 100644 --- a/frontend/src/components/HeaderNav.tsx +++ b/frontend/src/components/HeaderNav.tsx @@ -1,63 +1,74 @@ -import { NavLink, useLocation } from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; +import { Button, PageContainer } from "@ifrc-go/ui"; import { UploadCloudLineIcon, AnalysisIcon, SearchLineIcon, QuestionLineIcon, GoMainIcon, + SettingsIcon, } from "@ifrc-go/icons"; -/* Style helper for active vs. inactive nav links */ -const navLink = ({ isActive }: { isActive: boolean }) => - `flex items-center gap-1 px-4 sm:px-6 py-2 text-xs sm:text-sm transition-colors whitespace-nowrap mx-4 sm:mx-6 - ${isActive ? "text-ifrcRed font-semibold" : "text-gray-600 hover:text-ifrcRed"}`; - -/* Put page info in one list so it’s easy to extend */ +/* Put page info in one list so it's easy to extend */ const navItems = [ { to: "/upload", label: "Upload", Icon: UploadCloudLineIcon }, - { to: "/analytics", label: "Analytics", Icon: AnalysisIcon }, { to: "/explore", label: "Explore", Icon: SearchLineIcon }, + { to: "/analytics", label: "Analytics", Icon: AnalysisIcon }, + { to: "/dev", label: "Dev", Icon: SettingsIcon }, ]; export default function HeaderNav() { const location = useLocation(); - - const handleNavigation = (e: React.MouseEvent, to: string) => { - if (location.pathname === "/upload") { - const uploadPage = document.querySelector('[data-step="2"]'); - if (uploadPage) { - e.preventDefault(); - if (confirm("Changes will not be saved")) { - window.location.href = to; - } - } - } - }; + const navigate = useNavigate(); return ( -
-
- +
-
+ Help & Support + + + ); } diff --git a/frontend/src/index.css b/frontend/src/index.css index 582b8d4b951c2945da5e1a4ab18d8f1159b823fb..8ba88433850ef0176558f6479cae63617e1af0db 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -2,3 +2,44 @@ @tailwind base; @tailwind components; @tailwind utilities; + +* { + box-sizing: border-box; +} + +html { + @media screen { + margin: 0; + padding: 0; + scrollbar-gutter: stable; + } +} + +body { + line-height: var(--go-ui-line-height-md); + color: var(--go-ui-color-text); + font-family: var(--go-ui-font-family-sans-serif); + font-size: var(--go-ui-font-size-md); + font-weight: var(--go-ui-font-weight-normal); + + @media screen { + margin: 0; + background-color: var(--go-ui-color-background); + padding: 0; + } +} + +ul, ol, p { + margin: 0; +} + +@media print { + @page { + size: portrait A4; + margin: 10mm 10mm 16mm 10mm; + } + + body { + font-family: 'Open Sans', sans-serif; + } +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index bef5202a32cbd0632c43de40f6e908532903fd42..e963ab3df96d75536a1575e5119afb26e57b24e8 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,3 +1,4 @@ +import '@ifrc-go/ui/index.css'; import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' diff --git a/frontend/src/pages/AnalyticsPage.tsx b/frontend/src/pages/AnalyticsPage.tsx index 2953b5041e4cdf57c43d2b659371f9d213d676ee..31e3cc70cdde7fc09124e0d2f46314f1f5cfe592 100644 --- a/frontend/src/pages/AnalyticsPage.tsx +++ b/frontend/src/pages/AnalyticsPage.tsx @@ -1,9 +1,575 @@ -import { PageContainer, Heading } from '@ifrc-go/ui'; +// src/pages/AnalyticsPage.tsx + +import { + PageContainer, + PieChart, + KeyFigure, + Spinner, + Container, + ProgressBar, + SegmentInput, + Table, +} from '@ifrc-go/ui'; +import { + createStringColumn, + createNumberColumn, + numericIdSelector +} from '@ifrc-go/ui/utils'; +import { useState, useEffect, useMemo } from 'react'; +// icons not used on this page + +interface AnalyticsData { + totalCaptions: number; + sources: { [key: string]: number }; + types: { [key: string]: number }; + regions: { [key: string]: number }; + models: { + [key: string]: { + count: number; + avgAccuracy: number; + avgContext: number; + avgUsability: number; + totalScore: number; + }; + }; +} + +interface LookupData { + s_code?: string; + t_code?: string; + r_code?: string; + label: string; +} + +interface RegionData { + id: number; + name: string; + count: number; + percentage: number; +} + +interface TypeData { + id: number; + name: string; + count: number; + percentage: number; +} + +interface SourceData { + id: number; + name: string; + count: number; + percentage: number; +} + +interface ModelData { + id: number; + name: string; + count: number; + accuracy: number; + context: number; + usability: number; + totalScore: number; +} export default function AnalyticsPage() { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [view, setView] = useState<'general' | 'vlm'>('general'); + const [sourcesLookup, setSourcesLookup] = useState([]); + const [typesLookup, setTypesLookup] = useState([]); + const [regionsLookup, setRegionsLookup] = useState([]); + + // SegmentInput options for analytics view + const viewOptions = [ + { key: 'general' as const, label: 'General Analytics' }, + { key: 'vlm' as const, label: 'VLM Analytics' } + ]; + + useEffect(() => { + fetchAnalytics(); + fetchLookupData(); + }, []); + + async function fetchAnalytics() { + setLoading(true); + try { + const res = await fetch('/api/images/'); + const maps = await res.json(); + + const analytics: AnalyticsData = { + totalCaptions: maps.length, + sources: {}, + types: {}, + regions: {}, + models: {}, + }; + + maps.forEach((map: any) => { + if (map.source) analytics.sources[map.source] = (analytics.sources[map.source] || 0) + 1; + if (map.type) analytics.types[map.type] = (analytics.types[map.type] || 0) + 1; + if (map.countries) { + map.countries.forEach((c: any) => { + if (c.r_code) analytics.regions[c.r_code] = (analytics.regions[c.r_code] || 0) + 1; + }); + } + if (map.caption?.model) { + const m = map.caption.model; + const ctr = analytics.models[m] ||= { count: 0, avgAccuracy: 0, avgContext: 0, avgUsability: 0, totalScore: 0 }; + ctr.count++; + if (map.caption.accuracy != null) ctr.avgAccuracy += map.caption.accuracy; + if (map.caption.context != null) ctr.avgContext += map.caption.context; + if (map.caption.usability != null) ctr.avgUsability += map.caption.usability; + } + }); + + // Add all sources and types with 0 values for missing data + sourcesLookup.forEach(source => { + if (source.s_code && !analytics.sources[source.s_code]) { + analytics.sources[source.s_code] = 0; + } + }); + + typesLookup.forEach(type => { + if (type.t_code && !analytics.types[type.t_code]) { + analytics.types[type.t_code] = 0; + } + }); + + // Add all regions with 0 values for missing data + regionsLookup.forEach(region => { + if (region.r_code && !analytics.regions[region.r_code]) { + analytics.regions[region.r_code] = 0; + } + }); + + // Add all models with 0 values for missing data + const allModels = ['GPT-4', 'Claude', 'Gemini', 'Llama', 'Other']; + allModels.forEach(model => { + if (!analytics.models[model]) { + analytics.models[model] = { count: 0, avgAccuracy: 0, avgContext: 0, avgUsability: 0, totalScore: 0 }; + } + }); + + Object.values(analytics.models).forEach(m => { + if (m.count > 0) { + m.avgAccuracy = Math.round(m.avgAccuracy / m.count); + m.avgContext = Math.round(m.avgContext / m.count); + m.avgUsability = Math.round(m.avgUsability / m.count); + m.totalScore = Math.round((m.avgAccuracy + m.avgContext + m.avgUsability) / 3); + } + }); + + setData(analytics); + } catch (e) { + console.error(e); + setData(null); + } finally { + setLoading(false); + } + } + + async function fetchLookupData() { + try { + const [sourcesRes, typesRes, regionsRes] = await Promise.all([ + fetch('/api/sources'), + fetch('/api/types'), + fetch('/api/regions') + ]); + const sources = await sourcesRes.json(); + const types = await typesRes.json(); + const regions = await regionsRes.json(); + setSourcesLookup(sources); + setTypesLookup(types); + setRegionsLookup(regions); + } catch (e) { + console.error('Failed to fetch lookup data:', e); + } + } + + const getSourceLabel = (code: string) => { + const source = sourcesLookup.find(s => s.s_code === code); + return source ? source.label : code; + }; + + const getTypeLabel = (code: string) => { + const type = typesLookup.find(t => t.t_code === code); + return type ? type.label : code; + }; + + // const getRegionLabel = (code: string) => { + // const region = regionsLookup.find(r => r.r_code === code); + // return region ? region.label : code; + // }; + + // Transform regions data for IFRC Table - show all regions including 0 data + const regionsTableData = useMemo(() => { + if (!data || !regionsLookup.length) return []; + + // Create a map of all regions with their counts (0 if no data) + const allRegions = regionsLookup.reduce((acc, region) => { + if (region.r_code) { + acc[region.r_code] = { + name: region.label, + count: data.regions[region.r_code] || 0 + }; + } + return acc; + }, {} as Record); + + // Convert to array and sort by count descending + return Object.entries(allRegions) + .sort(([,a], [,b]) => b.count - a.count) + .map(([_, { name, count }], index) => ({ + id: index + 1, + name, + count, + percentage: data.totalCaptions > 0 ? Math.round((count / data.totalCaptions) * 100) : 0 + })); + }, [data, regionsLookup]); + + // Transform types data for IFRC Table + const typesTableData = useMemo(() => { + if (!data) return []; + + return Object.entries(data.types) + .sort(([,a], [,b]) => b - a) + .map(([typeKey, count], index) => ({ + id: index + 1, + name: getTypeLabel(typeKey), + count, + percentage: Math.round((count / data.totalCaptions) * 100) + })); + }, [data, typesLookup]); + + // Transform sources data for IFRC Table + const sourcesTableData = useMemo(() => { + if (!data) return []; + + return Object.entries(data.sources) + .sort(([,a], [,b]) => b - a) + .map(([sourceKey, count], index) => ({ + id: index + 1, + name: getSourceLabel(sourceKey), + count, + percentage: Math.round((count / data.totalCaptions) * 100) + })); + }, [data, sourcesLookup]); + + // Transform models data for IFRC Table + const modelsTableData = useMemo(() => { + if (!data) return []; + + return Object.entries(data.models) + .sort(([,a], [,b]) => b.totalScore - a.totalScore) + .map(([model, stats], index) => ({ + id: index + 1, + name: model, + count: stats.count, + accuracy: stats.avgAccuracy, + context: stats.avgContext, + usability: stats.avgUsability, + totalScore: stats.totalScore + })); + }, [data]); + + // Create columns for regions table + const regionsColumns = useMemo(() => [ + createStringColumn( + 'name', + 'Region', + (item) => item.name, + ), + createNumberColumn( + 'count', + 'Count', + (item) => item.count, + ), + createNumberColumn( + 'percentage', + '% of Total', + (item) => item.percentage, + { + suffix: '%', + maximumFractionDigits: 0, + }, + ), + ], []); + + // Create columns for types table + const typesColumns = useMemo(() => [ + createStringColumn( + 'name', + 'Type', + (item) => item.name, + ), + createNumberColumn( + 'count', + 'Count', + (item) => item.count, + ), + createNumberColumn( + 'percentage', + '% of Total', + (item) => item.percentage, + { + suffix: '%', + maximumFractionDigits: 0, + }, + ), + ], []); + + // Create columns for sources table + const sourcesColumns = useMemo(() => [ + createStringColumn( + 'name', + 'Source', + (item) => item.name, + ), + createNumberColumn( + 'count', + 'Count', + (item) => item.count, + ), + createNumberColumn( + 'percentage', + '% of Total', + (item) => item.percentage, + { + suffix: '%', + maximumFractionDigits: 0, + }, + ), + ], []); + + // Create columns for models table + const modelsColumns = useMemo(() => [ + createStringColumn( + 'name', + 'Model', + (item) => item.name, + ), + createNumberColumn( + 'count', + 'Count', + (item) => item.count, + ), + createNumberColumn( + 'accuracy', + 'Accuracy', + (item) => item.accuracy, + { + suffix: '%', + maximumFractionDigits: 0, + }, + ), + createNumberColumn( + 'context', + 'Context', + (item) => item.context, + { + suffix: '%', + maximumFractionDigits: 0, + }, + ), + createNumberColumn( + 'usability', + 'Usability', + (item) => item.usability, + { + suffix: '%', + maximumFractionDigits: 0, + }, + ), + createNumberColumn( + 'totalScore', + 'Total Score', + (item) => item.totalScore, + { + suffix: '%', + maximumFractionDigits: 0, + }, + ), + ], []); + + if (loading) { + return ( + +
+ +
+
+ ); + } + + if (!data) { + return ( + +
+
Failed to load analytics data. Please try again.
+
+
+ ); + } + + const sourcesChartData = Object.entries(data.sources).filter(([, value]) => value > 0).map(([name, value]) => ({ name, value })); + const typesChartData = Object.entries(data.types).filter(([, value]) => value > 0).map(([name, value]) => ({ name, value })); + const regionsChartData = Object.entries(data.regions).filter(([, value]) => value > 0).map(([name, value]) => ({ name, value })); + + // Official IFRC color palette for all pie charts - same order for all charts + const ifrcColors = [ + '#F5333F', // IFRC Primary Red (--go-ui-color-red-90) + '#F64752', // IFRC Red 80 + '#F75C65', // IFRC Red 70 + '#F87079', // IFRC Red 60 + '#F9858C', // IFRC Red 50 + '#FA999F', // IFRC Red 40 + '#FBADB2', // IFRC Red 30 + '#FCC2C5' // IFRC Red 20 + ]; + + return ( - - Analytics + + + {/* Tab selector */} +
+ { + if (value === 'general' || value === 'vlm') { + setView(value); + } + }} + options={viewOptions} + keySelector={(o) => o.key} + labelSelector={(o) => o.label} + /> +
+ + {view === 'general' ? ( +
+ {/* Summary Statistics */} + +
+ + +
+
+
+ Progress towards target + {Math.round((data.totalCaptions / 2000) * 100)}% +
+ +
+
+ + + {/* Regions Chart & Data */} + +
+
+ d.value} + labelSelector={d => d.name} + keySelector={d => d.name} + colors={ifrcColors} + showPercentageInLegend + /> +
+
+ + + + + + {/* Sources Chart & Data */} + +
+
+ d.value} + labelSelector={d => d.name} + keySelector={d => d.name} + colors={ifrcColors} + showPercentageInLegend + /> +
+
+
+ + + + + {/* Types Chart & Data */} + +
+
+ d.value} + labelSelector={d => d.name} + keySelector={d => d.name} + colors={ifrcColors} + showPercentageInLegend + /> +
+
+
+ + + + + ) : ( +
+ {/* Model Performance */} + +
+ + + )} + ); } diff --git a/frontend/src/pages/DemoPage.tsx b/frontend/src/pages/DemoPage.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ee5fb33eaa577a15b93f1311cf9a3f07e7e8ca1e --- /dev/null +++ b/frontend/src/pages/DemoPage.tsx @@ -0,0 +1,1079 @@ +import { useState } from 'react'; +import { + PageContainer, + Heading, + Button, + TextInput, + SelectInput, + MultiSelectInput, + SearchSelectInput, + SearchMultiSelectInput, + TextArea, + Checkbox, + Radio, + Switch, + DateInput, + NumberInput, + PasswordInput, + RawFileInput, + Container, + Alert, + Message, + Spinner, + ProgressBar, + StackedProgressBar, + KeyFigure, + PieChart, + BarChart, + TimeSeriesChart, + Table, + HeaderCell, + TableRow, + TableData, + Tabs, + Tab, + TabList, + TabPanel, + Chip, + Tooltip, + Modal, + Popup, + DropdownMenu, + IconButton, + ConfirmButton, + Breadcrumbs, + List, + Grid, + ExpandableContainer, + BlockLoading, + InputContainer, + InputLabel, + InputHint, + InputError, + InputSection, + BooleanInput, + BooleanOutput, + DateOutput, + DateRangeOutput, + NumberOutput, + TextOutput, + HtmlOutput, + DismissableTextOutput, + DismissableListOutput, + DismissableMultiListOutput, + Legend, + LegendItem, + ChartContainer, + ChartAxes, + InfoPopup, + Footer, + NavigationTabList, + Pager, + RawButton, + RawInput, + RawTextArea, + RawList, + SegmentInput, + SelectInputContainer, + ReducedListDisplay, + Image, + TopBanner, +} from '@ifrc-go/ui'; +import { + UploadCloudLineIcon, + ArrowRightLineIcon, + SearchLineIcon, + QuestionLineIcon, + GoMainIcon, + StarLineIcon, + DashboardIcon, + AnalysisIcon, + FilterLineIcon, + DropLineIcon, + CartIcon, + ChevronDownLineIcon, + ChevronUpLineIcon, + CloseLineIcon, + EditLineIcon, + DeleteBinLineIcon, + DownloadLineIcon, + ShareLineIcon, + SettingsLineIcon, + RulerLineIcon, + MagicLineIcon, + PantoneLineIcon, + MarkupLineIcon, + CalendarLineIcon, + LockLineIcon, + LocationIcon, + HeartLineIcon, + ThumbUpLineIcon, + ThumbDownLineIcon, + EyeLineIcon, + EyeOffLineIcon, + CheckLineIcon, + CropLineIcon, + AlertLineIcon, + InfoIcon, + AlarmWarningLineIcon, + SliceLineIcon, + ArrowLeftLineIcon, + ArrowDownLineIcon, + ArrowUpLineIcon, + MenuLineIcon, + MoreLineIcon, + RefreshLineIcon, + PaintLineIcon, + NotificationIcon, + HammerLineIcon, + ShapeLineIcon, + LinkLineIcon, + ExternalLinkLineIcon, + CopyLineIcon, +} from '@ifrc-go/icons'; + +export default function DemoPage() { + const [showModal, setShowModal] = useState(false); + const [showPopup, setShowPopup] = useState(false); + const [activeTab, setActiveTab] = useState('components'); + const [loading, setLoading] = useState(false); + const [textValue, setTextValue] = useState(''); + const [selectValue, setSelectValue] = useState(''); + const [multiSelectValue, setMultiSelectValue] = useState([]); + const [checkboxValue, setCheckboxValue] = useState(false); + const [radioValue, setRadioValue] = useState('option1'); + const [switchValue, setSwitchValue] = useState(false); + const [dateValue, setDateValue] = useState(''); + const [numberValue, setNumberValue] = useState(); + const [passwordValue, setPasswordValue] = useState(''); + const [booleanValue, setBooleanValue] = useState(false); + const [segmentValue, setSegmentValue] = useState('option1'); + + // Dummy data + const dummyOptions = [ + { key: 'option1', label: 'Option 1' }, + { key: 'option2', label: 'Option 2' }, + { key: 'option3', label: 'Option 3' }, + { key: 'option4', label: 'Option 4' }, + ]; + + const dummyCountries = [ + { c_code: 'US', label: 'United States', r_code: 'NAM' }, + { c_code: 'CA', label: 'Canada', r_code: 'NAM' }, + { c_code: 'MX', label: 'Mexico', r_code: 'NAM' }, + { c_code: 'BR', label: 'Brazil', r_code: 'SAM' }, + { c_code: 'AR', label: 'Argentina', r_code: 'SAM' }, + { c_code: 'UK', label: 'United Kingdom', r_code: 'EUR' }, + { c_code: 'DE', label: 'Germany', r_code: 'EUR' }, + { c_code: 'FR', label: 'France', r_code: 'EUR' }, + ]; + + const dummyTableData = [ + { id: 1, name: 'John Doe', age: 30, country: 'United States', status: 'Active' }, + { id: 2, name: 'Jane Smith', age: 25, country: 'Canada', status: 'Inactive' }, + { id: 3, name: 'Bob Johnson', age: 35, country: 'Mexico', status: 'Active' }, + { id: 4, name: 'Alice Brown', age: 28, country: 'Brazil', status: 'Active' }, + ]; + + const dummyChartData = [ + { name: 'Red Cross', value: 45 }, + { name: 'UNICEF', value: 30 }, + { name: 'WHO', value: 15 }, + { name: 'WFP', value: 10 }, + ]; + + const dummyTimeSeriesData = [ + { date: '2024-01', value: 100 }, + { date: '2024-02', value: 120 }, + { date: '2024-03', value: 110 }, + { date: '2024-04', value: 140 }, + { date: '2024-05', value: 130 }, + { date: '2024-06', value: 160 }, + ]; + + const dummyBarData = [ + { name: 'Q1', value: 100 }, + { name: 'Q2', value: 150 }, + { name: 'Q3', value: 120 }, + { name: 'Q4', value: 180 }, + ]; + + const handleLoading = () => { + setLoading(true); + setTimeout(() => setLoading(false), 2000); + }; + + const handleTextChange = (value: string | undefined, name: string) => { + setTextValue(value || ''); + }; + + const handlePasswordChange = (value: string | undefined, name: string) => { + setPasswordValue(value || ''); + }; + + const handleNumberChange = (value: number | undefined, name: string) => { + setNumberValue(value); + }; + + const handleDateChange = (value: string | undefined, name: string) => { + setDateValue(value || ''); + }; + + const handleSelectChange = (value: string | undefined, name: string) => { + setSelectValue(value || ''); + }; + + const handleMultiSelectChange = (value: string[], name: string) => { + setMultiSelectValue(value); + }; + + const handleCheckboxChange = (value: boolean, name: string) => { + setCheckboxValue(value); + }; + + const handleRadioChange = (value: string, name: string) => { + setRadioValue(value); + }; + + const handleSwitchChange = (value: boolean, name: string) => { + setSwitchValue(value); + }; + + const handleBooleanChange = (value: boolean, name: string) => { + setBooleanValue(value); + }; + + const handleSegmentChange = (value: string, name: string) => { + setSegmentValue(value); + }; + + return ( + +
+ {/* Header Section */} + +
+ {/* Navigation Tabs */} +
+

Navigation Tab List

+ + + + + + +
+ + {/* Top Banner */} +
+

Top Banner

+
+
+
+

Important Notice

+

This is a top banner component for important announcements.

+
+ +
+
+
+ + {/* Breadcrumbs */} +
+

Breadcrumbs

+ +
+
+
+ + {/* Basic Components */} + +
+ {/* Buttons */} +
+

Buttons

+
+ + + + + + alert('Confirmed!')}> + Confirm Button + + +
+
+ + {/* Icon Buttons */} +
+

Icon Buttons

+
+ + + + + + + + + + + + + + + + + + +
+
+ + {/* Chips */} +
+

Chips

+
+ + Primary Chip + + + Secondary Chip + + + Tertiary Chip + +
+
+ + {/* Tooltips */} +
+

Tooltips

+
+
+ +
+ This is a tooltip +
+
+
+ + + +
+ Another tooltip +
+
+
+
+
+
+ + {/* Form Elements */} + +
+ {/* Text Inputs */} +
+

Text Inputs

+
+ + Text Input + + This is a hint text + + + + Password Input + + + + + Number Input + + + + + Date Input + + + + + Text Area +