import { AreaChart } from '@mantine/charts' import { Box, Button, Card, Group, Modal, Paper, SegmentedControl, SimpleGrid, Stack, Text, Textarea, TextInput, ThemeIcon, Title } from '@mantine/core' import { useDisclosure } from '@mantine/hooks' import { notifications } from '@mantine/notifications' import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router' import { useState } from 'react' import { TbArrowLeft, TbBuildingCommunity, TbCalendar, TbCalendarEvent, TbChartBar, TbEdit, TbHome2, TbLayoutKanban, TbMapPin, TbPower, TbUser, TbUsers, TbUsersGroup, TbWifi } from 'react-icons/tb' import useSWR from 'swr' import { API_URLS } from '../config/api' import { useSession } from '../hooks/useAuth' const fetcher = (url: string) => fetch(url).then((res) => res.json()) export const Route = createFileRoute('/apps/$appId/villages/$villageId')({ component: VillageDetailPage, }) // ── Mock Data ──────────────────────────────────────────────────────────────── // Mock data removed as it is replaced by API calls // Remove chart data generators as they are replaced by API calls // ── Helpers ─────────────────────────────────────────────────────────────────── // ── Activity Chart ──────────────────────────────────────────────────────────── type ChartPeriod = 'daily' | 'monthly' | 'yearly' function ActivityChart({ villageId }: { villageId: string }) { const [period, setPeriod] = useState('daily') const { data: response, isLoading } = useSWR( API_URLS.graphLogVillages(villageId, period), fetcher ) const labels: Record = { daily: 'Daily (last 14 days)', monthly: 'Monthly (this year)', yearly: 'Yearly', } const rawData: any[] = Array.isArray(response?.data) ? response.data : [] // Normalize: map any field names from external API → { label, activity } const data = rawData.map((item) => { const label = item.label const activity = item.aktivitas return { label: String(label), activity: Number(activity) } }) return ( Village Activity Log {labels[period]} setPeriod(v as ChartPeriod)} size="xs" radius="md" data={[ { value: 'daily', label: 'Daily' }, { value: 'monthly', label: 'Monthly' }, { value: 'yearly', label: 'Yearly' }, ]} /> {isLoading ? ( Loading chart data... ) : ( )} ) } // ── Main Page ───────────────────────────────────────────────────────────────── function VillageDetailPage() { const { appId, villageId } = useParams({ from: '/apps/$appId/villages/$villageId' }) const navigate = useNavigate() const { data: session } = useSession() const isDeveloper = session?.user?.role === 'DEVELOPER' const { data: infoRes, isLoading: infoLoading, mutate } = useSWR(API_URLS.infoVillages(villageId), fetcher) const { data: gridRes, isLoading: gridLoading } = useSWR(API_URLS.gridVillages(villageId), fetcher) const [confirmModalOpened, { open: openConfirmModal, close: closeConfirmModal }] = useDisclosure(false) const [editModalOpened, { open: openEditModal, close: closeEditModal }] = useDisclosure(false) const [isUpdating, setIsUpdating] = useState(false) const [isEditing, setIsEditing] = useState(false) const [editForm, setEditForm] = useState({ name: '', desc: '' }) const village = infoRes?.data const stats = gridRes?.data const openEdit = () => { setEditForm({ name: village?.name || '', desc: village?.desc || '' }) openEditModal() } const handleEditVillage = async () => { if (!village) return if (!editForm.name.trim() || !editForm.desc.trim()) { notifications.show({ title: 'Validation Error', message: 'All fields are required.', color: 'red' }) return } setIsEditing(true) try { const res = await fetch(API_URLS.editVillages(), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: village.id, name: editForm.name, desc: editForm.desc }) }) if (res.ok) { await fetch(API_URLS.createLog(), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'UPDATE', message: `Data desa (${appId}) diperbarui: ${editForm.name}-${village.id}` }) }).catch(console.error) notifications.show({ title: 'Success', message: 'Village data has been updated successfully.', color: 'teal' }) mutate() closeEditModal() } else { notifications.show({ title: 'Error', message: 'Failed to update village data.', color: 'red' }) } } catch (error) { notifications.show({ title: 'Error', message: 'A network error occurred.', color: 'red' }) } finally { setIsEditing(false) } } const handleConfirmToggle = async () => { if (!village) return setIsUpdating(true) try { const res = await fetch(API_URLS.updateStatusVillages(), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: village.id, active: !village.isActive }) }) if (res.ok) { await fetch(API_URLS.createLog(), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'UPDATE', message: `Status desa (${appId}) diperbarui (${!village.isActive ? 'activated' : 'deactivated'}): ${village.name}-${village.id}` }) }).catch(console.error) notifications.show({ title: 'Success', message: `Village status has been ${!village.isActive ? 'activated' : 'deactivated'}.`, color: 'teal' }) mutate() closeConfirmModal() } else { notifications.show({ title: 'Error', message: 'Failed to update village status.', color: 'red' }) } } catch (error) { notifications.show({ title: 'Error', message: 'A network error occurred.', color: 'red' }) } finally { setIsUpdating(false) } } const goBack = () => navigate({ to: '/apps/$appId/villages', params: { appId } }) if (infoLoading || gridLoading) { return ( Loading village data... ) } if (!village) { return ( Village not found Village ID "{villageId}" is not registered in the system. ) } return ( {/* ── Back Button ── */} {/* Action Buttons */} {/* ── Header Banner ── */} {/* Decorative blobs */} {village.name} Location data not available Village Head: {village.perbekel} {/* } > {cfg.label} */} {/* Last Sync block */} {/* Last Sync */} {village.isActive ? 'ACTIVE' : 'NON-ACTIVE'} {/* ── Stats Cards ── */} {[ { icon: TbUsers, label: 'Total Users', active: stats?.user?.active, nonActive: stats?.user?.nonActive, color: 'blue' }, { icon: TbUsersGroup, label: 'Total Groups', active: stats?.group?.active, nonActive: stats?.group?.nonActive, color: 'violet' }, { icon: TbLayoutKanban, label: 'Total Divisions', active: stats?.division?.active, nonActive: stats?.division?.nonActive, color: 'teal' }, { icon: TbCalendarEvent, label: 'Total Activities', active: stats?.project?.active, nonActive: stats?.project?.nonActive, color: 'orange' }, ].map((s) => ( NON-ACTIVE {s.nonActive?.toLocaleString('id-ID') || 0} {s.label} {s.active?.toLocaleString('id-ID') || 0} ))} {/* ── Chart + Info Panels ── */} {/* Left (3/4): Activity Chart */} {/* Right (1/4): Informasi Sistem */} System Information {[ { label: 'Date Created', value: village.createdAt }, { label: 'Created By', value: '-' }, { label: 'Last Updated', value: village.updatedAt }, ].map((item, idx, arr) => ( {item.label} {item.value} ))} {/* ── Confirmation Modal ── */} Confirm Status Change} radius="xl" centered > Are you sure you want to {village.isActive ? 'deactivate' : 'activate'} village {village.name}? {/* ── Edit Village Modal ── */} Edit Village Details} radius="xl" size="md" > setEditForm(prev => ({ ...prev, name: e.currentTarget.value }))} />