From f446aec7344e82bcb13d7c2956d2ec612e6e0d0a Mon Sep 17 00:00:00 2001 From: amal Date: Thu, 16 Apr 2026 09:52:17 +0800 Subject: [PATCH 1/3] upd: role akses --- src/frontend/components/DashboardCharts.tsx | 39 ++++---- src/frontend/routes/apps.$appId.index.tsx | 6 +- .../apps.$appId.villages.$villageId.tsx | 5 + src/frontend/routes/users.tsx | 93 ++++++++++--------- 4 files changed, 80 insertions(+), 63 deletions(-) diff --git a/src/frontend/components/DashboardCharts.tsx b/src/frontend/components/DashboardCharts.tsx index bd67156..6322ac2 100644 --- a/src/frontend/components/DashboardCharts.tsx +++ b/src/frontend/components/DashboardCharts.tsx @@ -1,15 +1,15 @@ -import { - Paper, - Stack, - Text, - Group, - ThemeIcon, - Box, +import { BarChart, LineChart } from '@mantine/charts' +import { Badge, + Box, + Group, + Paper, + Stack, + Text, + ThemeIcon, useMantineTheme } from '@mantine/core' -import { LineChart, BarChart } from '@mantine/charts' -import { TbTimeline, TbChartBar, TbArrowUpRight } from 'react-icons/tb' +import { TbArrowUpRight, TbChartBar, TbTimeline } from 'react-icons/tb' interface ChartProps { data?: any[] @@ -18,7 +18,7 @@ interface ChartProps { export function VillageActivityLineChart({ data = [], isLoading }: ChartProps) { const theme = useMantineTheme() - + return ( @@ -32,9 +32,14 @@ export function VillageActivityLineChart({ data = [], isLoading }: ChartProps) { Trend over the last 7 days - }> - {isLoading ? '...' : 'Live'} - + { + isLoading && ( + }> + ... + + ) + } + @@ -91,12 +96,12 @@ export function VillageComparisonBarChart({ data = [], isLoading }: ChartProps) tickLine="none" gridAxis="y" barProps={{ - radius: [8, 8, 4, 4], + radius: [8, 8, 4, 4], }} styles={{ - bar: { - fill: 'url(#barGradient)', - } + bar: { + fill: 'url(#barGradient)', + } }} > diff --git a/src/frontend/routes/apps.$appId.index.tsx b/src/frontend/routes/apps.$appId.index.tsx index 9ae228e..6e19ea0 100644 --- a/src/frontend/routes/apps.$appId.index.tsx +++ b/src/frontend/routes/apps.$appId.index.tsx @@ -2,6 +2,7 @@ import { useQuery } from '@tanstack/react-query' import { VillageActivityLineChart, VillageComparisonBarChart } from '@/frontend/components/DashboardCharts' import { ErrorDataTable } from '@/frontend/components/ErrorDataTable' import { SummaryCard } from '@/frontend/components/SummaryCard' +import { useSession } from '@/frontend/hooks/useAuth' import { Badge, Button, @@ -39,6 +40,8 @@ function AppOverviewPage() { const navigate = useNavigate() const isDesaPlus = appId === 'desa-plus' const [versionModalOpened, { open: openVersionModal, close: closeVersionModal }] = useDisclosure(false) + const { data: session } = useSession() + const isDeveloper = session?.user?.role === 'DEVELOPER' // Form State const [latestVersion, setLatestVersion] = useState('') @@ -177,7 +180,7 @@ function AppOverviewPage() { value={gridLoading ? '...' : (grid?.version?.mobile_latest_version || 'N/A')} icon={TbVersions} color="brand-blue" - onClick={openVersionModal} + onClick={isDeveloper ? openVersionModal : undefined} > @@ -220,6 +223,7 @@ function AppOverviewPage() { icon={TbAlertTriangle} color="red" isError={true} + onClick={() => navigate({ to: `/apps/${appId}/errors` })} /> diff --git a/src/frontend/routes/apps.$appId.villages.$villageId.tsx b/src/frontend/routes/apps.$appId.villages.$villageId.tsx index 70c1a0c..0304a1c 100644 --- a/src/frontend/routes/apps.$appId.villages.$villageId.tsx +++ b/src/frontend/routes/apps.$appId.villages.$villageId.tsx @@ -37,6 +37,7 @@ import { } 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()) @@ -149,6 +150,9 @@ 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) @@ -323,6 +327,7 @@ function VillageDetailPage() { onClick={openConfirmModal} radius="md" loading={isUpdating} + disabled={!isDeveloper} > {village.isActive ? 'Deactivate' : 'Active'} diff --git a/src/frontend/routes/users.tsx b/src/frontend/routes/users.tsx index 2ebb47b..27c7289 100644 --- a/src/frontend/routes/users.tsx +++ b/src/frontend/routes/users.tsx @@ -1,48 +1,47 @@ +import { DashboardLayout } from '@/frontend/components/DashboardLayout' +import { StatsCard } from '@/frontend/components/StatsCard' import { ActionIcon, + Avatar, Badge, Button, Card, Container, + Divider, Group, + List, + Modal, + Pagination, + Paper, + PasswordInput, + Select, + SimpleGrid, Stack, Table, + Tabs, Text, TextInput, - Title, - Paper, - Tabs, - Avatar, - SimpleGrid, ThemeIcon, - List, - Divider, - Pagination, - Modal, - Select, - PasswordInput, + Title, } from '@mantine/core' -import { createFileRoute } from '@tanstack/react-router' -import { useState, useEffect } from 'react' import { useDisclosure } from '@mantine/hooks' import { notifications } from '@mantine/notifications' -import { - TbPlus, - TbSearch, - TbPencil, - TbTrash, - TbUserCheck, - TbShieldCheck, +import { createFileRoute } from '@tanstack/react-router' +import { useEffect, useState } from 'react' +import { TbAccessPoint, TbCircleCheck, TbCircleX, - TbClock, - TbApps, + TbPencil, + TbPlus, + TbSearch, + TbShieldCheck, + TbTrash, + TbUserCheck } from 'react-icons/tb' -import { DashboardLayout } from '@/frontend/components/DashboardLayout' -import { StatsCard } from '@/frontend/components/StatsCard' import useSWR from 'swr' import { API_URLS } from '../config/api' +import { useSession } from '../hooks/useAuth' export const Route = createFileRoute('/users')({ component: UsersPage, @@ -59,13 +58,13 @@ const getRoleColor = (role: string) => { } const roles = [ - { - name: 'DEVELOPER', + { + name: 'DEVELOPER', color: 'red', permissions: ['Full Access', 'Error Feedback', 'Error Management', 'App Version Management', 'User Management'] }, - { - name: 'ADMIN', + { + name: 'ADMIN', color: 'orange', permissions: ['View All Apps', 'View Logs', 'Report Errors'] }, @@ -75,6 +74,8 @@ function UsersPage() { const [search, setSearch] = useState('') const [debouncedSearch, setDebouncedSearch] = useState('') const [page, setPage] = useState(1) + const { data: session } = useSession() + const isDeveloper = session?.user?.role === 'DEVELOPER' useEffect(() => { const timer = setTimeout(() => setDebouncedSearch(search), 300) @@ -244,15 +245,17 @@ function UsersPage() { setPage(1) }} /> - + {isDeveloper && ( + + )} @@ -302,12 +305,12 @@ function UsersPage() { - handleOpenEdit(user)}> - - - handleOpenDelete(user)}> - - + handleOpenEdit(user)}> + + + handleOpenDelete(user)}> + + @@ -340,14 +343,14 @@ function UsersPage() { - + {role.name.replace('_', ' ')} Core role for secure app management. - + Key Permissions Date: Thu, 16 Apr 2026 10:19:51 +0800 Subject: [PATCH 2/3] upd: grafik --- .../routes/apps.$appId.villages.$villageId.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/frontend/routes/apps.$appId.villages.$villageId.tsx b/src/frontend/routes/apps.$appId.villages.$villageId.tsx index 0304a1c..71e90e9 100644 --- a/src/frontend/routes/apps.$appId.villages.$villageId.tsx +++ b/src/frontend/routes/apps.$appId.villages.$villageId.tsx @@ -110,14 +110,20 @@ function ActivityChart({ villageId }: { villageId: string }) { h={280} data={data} dataKey="label" - series={[{ name: 'aktivitas', color: '#2563EB', label: 'Activity' }]} + series={[{ name: 'aktivitas', color: '#2563EB' }]} curveType="monotone" withTooltip withDots tickLine="none" gridAxis="x" tooltipAnimationDuration={150} + withPointLabels={false} + withXAxis={true} + withYAxis={true} fillOpacity={1} + tooltipProps={{ + allowEscapeViewBox: { x: true, y: false }, + }} areaProps={{ strokeWidth: 2.5, fill: 'url(#villageAreaGrad)', @@ -129,6 +135,14 @@ function ActivityChart({ villageId }: { villageId: string }) { strokeWidth: 2, stroke: '#2563EB', fill: 'white', + style: { cursor: 'pointer' }, + }} + activeDotProps={{ + r: 6, + strokeWidth: 2, + stroke: '#2563EB', + fill: '#2563EB', + children: null, }} > From e6dfac1ffe138fedb98cbc9dc530ab2db4be7901 Mon Sep 17 00:00:00 2001 From: amal Date: Thu, 16 Apr 2026 11:47:19 +0800 Subject: [PATCH 3/3] upd: fix graphic data --- src/frontend/components/DashboardCharts.tsx | 40 +++++++++++++++---- .../apps.$appId.villages.$villageId.tsx | 39 +++--------------- 2 files changed, 37 insertions(+), 42 deletions(-) diff --git a/src/frontend/components/DashboardCharts.tsx b/src/frontend/components/DashboardCharts.tsx index 6322ac2..b1a6604 100644 --- a/src/frontend/components/DashboardCharts.tsx +++ b/src/frontend/components/DashboardCharts.tsx @@ -53,6 +53,9 @@ export function VillageActivityLineChart({ data = [], isLoading }: ChartProps) { gridAxis="x" withTooltip tooltipAnimationDuration={200} + tooltipProps={{ + allowEscapeViewBox: { x: true, y: false }, + }} styles={{ root: { '.recharts-line-curve': { @@ -91,17 +94,38 @@ export function VillageComparisonBarChart({ data = [], isLoading }: ChartProps) h={300} data={data} dataKey="village" - series={[{ name: 'activity', color: 'indigo.6' }]} + series={[{ name: 'activity', color: 'blue.6' }]} // Menggunakan warna dari theme withTooltip - tickLine="none" - gridAxis="y" barProps={{ - radius: [8, 8, 4, 4], + radius: [8, 8, 0, 0], + fill: 'url(#barGradient)', // Menggunakan gradient yang Anda buat }} - styles={{ - bar: { - fill: 'url(#barGradient)', - } + tooltipProps={{ + cursor: { fill: '#373A40', opacity: 0.4 }, + allowEscapeViewBox: { x: false, y: false }, + content: ({ active, payload }) => { + if (active && payload && payload.length) { + return ( +
+
+ {payload[0].payload.village} +
+
+ Activity: {payload[0].value} +
+
+ ); + } + return null; + }, }} > diff --git a/src/frontend/routes/apps.$appId.villages.$villageId.tsx b/src/frontend/routes/apps.$appId.villages.$villageId.tsx index 71e90e9..0ed86c6 100644 --- a/src/frontend/routes/apps.$appId.villages.$villageId.tsx +++ b/src/frontend/routes/apps.$appId.villages.$villageId.tsx @@ -110,49 +110,20 @@ function ActivityChart({ villageId }: { villageId: string }) { h={280} data={data} dataKey="label" - series={[{ name: 'aktivitas', color: '#2563EB' }]} + series={[{ name: 'activity', color: '#2563EB' }]} curveType="monotone" - withTooltip - withDots - tickLine="none" - gridAxis="x" - tooltipAnimationDuration={150} + withTooltip={true} + withDots={true} withPointLabels={false} - withXAxis={true} - withYAxis={true} - fillOpacity={1} + tooltipAnimationDuration={150} tooltipProps={{ allowEscapeViewBox: { x: true, y: false }, }} - areaProps={{ - strokeWidth: 2.5, - fill: 'url(#villageAreaGrad)', - stroke: '#2563EB', - filter: 'drop-shadow(0 4px 12px rgba(37,99,235,0.3))', - }} - dotProps={{ - r: 4, - strokeWidth: 2, - stroke: '#2563EB', - fill: 'white', - style: { cursor: 'pointer' }, - }} activeDotProps={{ r: 6, strokeWidth: 2, - stroke: '#2563EB', - fill: '#2563EB', - children: null, }} - > - - - - - - - - + /> )}
)