7 Commits

Author SHA1 Message Date
e82443ee03 chore: bump version to 0.1.17 2026-05-26 16:28:44 +08:00
501fbde118 fix: perbaiki layout tabel dan accordion di layar sempit
Co-authored-by: amaliadwiy <amaliadwiy@users.noreply.github.com>
2026-05-26 15:11:25 +08:00
fe4ddf686e fix: perbaiki layout tabel User Management agar tidak overflow di layar sempit 2026-05-26 15:00:47 +08:00
fe83fd6025 fix: perbaiki layout accordion header Bug Reports agar badge status selalu terlihat 2026-05-26 14:50:47 +08:00
457f36be06 fix: perbaiki layout filter Activity Logs agar tidak overflow 2026-05-26 14:43:59 +08:00
5002fd1519 fix: perbaiki layout table Recent Error Reports di dashboard
Kolom App, Version, Reported, dan Status tidak lagi wrap atau terpotong.
Tambah horizontal scroll pada container dan minWidth pada table.
2026-05-26 14:37:28 +08:00
8aaec351cf Merge pull request 'amalia/25-mei-26' (#26) from amalia/25-mei-26 into main
Reviewed-on: #26
2026-05-25 17:33:49 +08:00
8 changed files with 142 additions and 134 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "bun-react-template",
"version": "0.1.16",
"version": "0.1.17",
"private": true,
"type": "module",
"scripts": {

View File

@@ -8,7 +8,6 @@ import {
Group,
Loader,
Paper,
ScrollArea,
SimpleGrid,
Stack,
Table,
@@ -74,7 +73,7 @@ export const ErrorDataTable = forwardRef<ErrorDataTableHandle, ErrorDataTablePro
return (
<>
<Paper withBorder radius="2xl" className="glass" style={{ overflow: 'hidden' }}>
<Paper withBorder radius="2xl" className="glass" style={{ overflowX: 'auto' }}>
<Box p="lg" style={{ borderBottom: '1px solid rgba(255,255,255,0.08)' }}>
<Group justify="space-between">
<Group gap="sm">
@@ -101,15 +100,15 @@ export const ErrorDataTable = forwardRef<ErrorDataTableHandle, ErrorDataTablePro
</Group>
</Box>
<ScrollArea>
<Table.ScrollContainer minWidth={520}>
<Table verticalSpacing="sm" highlightOnHover className="data-table">
<Table.Thead>
<Table.Tr>
<Table.Th px="lg">Error Description</Table.Th>
<Table.Th>Reporter</Table.Th>
<Table.Th>Version</Table.Th>
<Table.Th>Reported</Table.Th>
<Table.Th pr="lg">Status</Table.Th>
<Table.Th style={{ whiteSpace: 'nowrap' }}>Reporter</Table.Th>
<Table.Th style={{ whiteSpace: 'nowrap' }}>Version</Table.Th>
<Table.Th style={{ whiteSpace: 'nowrap' }}>Reported</Table.Th>
<Table.Th pr="lg" style={{ whiteSpace: 'nowrap' }}>Status</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
@@ -149,8 +148,8 @@ export const ErrorDataTable = forwardRef<ErrorDataTableHandle, ErrorDataTablePro
v{error.affectedVersion || 'N/A'}
</Badge>
</Table.Td>
<Table.Td>
<Group gap={4}>
<Table.Td style={{ whiteSpace: 'nowrap' }}>
<Group gap={4} wrap="nowrap">
<TbHistory size={12} color="gray" />
<Text size="xs" c="dimmed">
{dayjs(error.createdAt).format('D MMM YYYY, HH:mm')}
@@ -170,7 +169,7 @@ export const ErrorDataTable = forwardRef<ErrorDataTableHandle, ErrorDataTablePro
))}
</Table.Tbody>
</Table>
</ScrollArea>
</Table.ScrollContainer>
</Paper>
<Drawer

View File

@@ -463,27 +463,29 @@ function AppErrorsPage() {
}}
>
<Accordion.Control>
<Group wrap="nowrap">
<Group wrap="nowrap" style={{ minWidth: 0 }}>
<ThemeIcon
color={STATUS_COLOR[bug.status] ?? 'gray'}
variant="light"
size="lg"
radius="md"
style={{ flexShrink: 0 }}
>
<TbAlertTriangle size={20} />
</ThemeIcon>
<Box style={{ flex: 1 }}>
<Group justify="space-between">
<Text size="sm" fw={600} lineClamp={1}>{bug.description}</Text>
<Box style={{ flex: 1, minWidth: 0 }}>
<Group wrap="nowrap" gap="xs">
<Text size="sm" fw={600} lineClamp={1} style={{ flex: 1, minWidth: 0 }}>{bug.description}</Text>
<Badge
color={STATUS_COLOR[bug.status] ?? 'gray'}
variant="dot"
size="sm"
style={{ flexShrink: 0 }}
>
{STATUS_LABEL[bug.status] ?? bug.status}
</Badge>
</Group>
<Text size="xs" c="dimmed">
<Text size="xs" c="dimmed" lineClamp={1}>
{dayjs(bug.createdAt).format('D MMM YYYY, HH:mm')} · {bug.appId?.toUpperCase()} · v{bug.affectedVersion}
</Text>
</Box>

View File

@@ -4,6 +4,7 @@ import {
Box,
Button,
Card,
Grid,
Group,
Loader,
Modal,
@@ -218,34 +219,36 @@ function RecentVillageLogs({ villageId }: { villageId: string }) {
) : logs.length === 0 ? (
<Text size="sm" c="dimmed" ta="center" py="md">No recent activity.</Text>
) : (
<Table verticalSpacing="xs" className="data-table">
<Table.Thead>
<Table.Tr>
<Table.Th>Time</Table.Th>
<Table.Th>User</Table.Th>
<Table.Th>Action</Table.Th>
<Table.Th>Description</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{logs.map((log: any, i: number) => (
<Table.Tr key={i}>
<Table.Td>
<Text size="xs">{dayjs(log.timestamp).format('D MMM YYYY, HH:mm')}</Text>
</Table.Td>
<Table.Td>
<Text size="sm" fw={500}>{log.userName || 'Unknown'}</Text>
</Table.Td>
<Table.Td>
<Text size="xs">{log.action || '-'}</Text>
</Table.Td>
<Table.Td>
<Text size="xs" c="dimmed" lineClamp={1}>{log.desc || '-'}</Text>
</Table.Td>
<Table.ScrollContainer minWidth={380}>
<Table verticalSpacing="xs" className="data-table">
<Table.Thead>
<Table.Tr>
<Table.Th style={{ whiteSpace: 'nowrap' }}>Time</Table.Th>
<Table.Th>User</Table.Th>
<Table.Th style={{ whiteSpace: 'nowrap' }}>Action</Table.Th>
<Table.Th>Description</Table.Th>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Table.Thead>
<Table.Tbody>
{logs.map((log: any, i: number) => (
<Table.Tr key={i}>
<Table.Td style={{ whiteSpace: 'nowrap' }}>
<Text size="xs">{dayjs(log.timestamp).format('D MMM YYYY, HH:mm')}</Text>
</Table.Td>
<Table.Td>
<Text size="sm" fw={500}>{log.userName || 'Unknown'}</Text>
</Table.Td>
<Table.Td style={{ whiteSpace: 'nowrap' }}>
<Text size="xs">{log.action || '-'}</Text>
</Table.Td>
<Table.Td>
<Text size="xs" c="dimmed">{log.desc || '-'}</Text>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
</Table.ScrollContainer>
)}
</Paper>
)
@@ -561,47 +564,42 @@ function VillageDetailPage() {
<ActivityChart villageId={villageId} />
{/* ── Recent Logs + System Info ── */}
<Box
style={{
display: 'grid',
gridTemplateColumns: '2fr 1fr',
gap: '1rem',
alignItems: 'start',
}}
>
<Box style={{ minWidth: 0 }}>
<Grid gutter="md" align="flex-start">
<Grid.Col span={{ base: 12, md: 8 }}>
<RecentVillageLogs villageId={villageId} />
</Box>
</Grid.Col>
<Paper withBorder radius="xl" p="lg">
<Group gap="xs" mb="md">
<ThemeIcon size={28} radius="md" variant="light" color="teal">
<TbCalendar size={14} />
</ThemeIcon>
<Text fw={700} size="sm">System Information</Text>
</Group>
<Stack gap={0}>
{[
{ label: 'Date Created', value: village.createdAt },
{ label: 'Created By', value: '-' },
{ label: 'Last Updated', value: village.updatedAt },
].map((item, idx, arr) => (
<Group
key={item.label}
justify="space-between"
py="xs"
wrap="wrap"
style={{
borderBottom: idx < arr.length - 1 ? '1px solid var(--mantine-color-default-border)' : 'none',
}}
>
<Text size="xs" c="dimmed">{item.label}</Text>
<Text size="xs" fw={600} ta="right">{item.value}</Text>
</Group>
))}
</Stack>
</Paper>
</Box>
<Grid.Col span={{ base: 12, md: 4 }}>
<Paper withBorder radius="xl" p="lg">
<Group gap="xs" mb="md">
<ThemeIcon size={28} radius="md" variant="light" color="teal">
<TbCalendar size={14} />
</ThemeIcon>
<Text fw={700} size="sm">System Information</Text>
</Group>
<Stack gap={0}>
{[
{ label: 'Date Created', value: village.createdAt },
{ label: 'Created By', value: '-' },
{ label: 'Last Updated', value: village.updatedAt },
].map((item, idx, arr) => (
<Group
key={item.label}
justify="space-between"
py="xs"
wrap="wrap"
style={{
borderBottom: idx < arr.length - 1 ? '1px solid var(--mantine-color-default-border)' : 'none',
}}
>
<Text size="xs" c="dimmed">{item.label}</Text>
<Text size="xs" fw={600} ta="right">{item.value}</Text>
</Group>
))}
</Stack>
</Paper>
</Grid.Col>
</Grid>
{/* ── Confirmation Modal ── */}
<Modal

View File

@@ -700,27 +700,29 @@ function ListErrorsPage() {
}}
>
<Accordion.Control>
<Group wrap="nowrap">
<Group wrap="nowrap" style={{ minWidth: 0 }}>
<ThemeIcon
color={STATUS_COLOR[bug.status] ?? 'gray'}
variant="light"
size="lg"
radius="md"
style={{ flexShrink: 0 }}
>
<TbAlertTriangle size={20} />
</ThemeIcon>
<Box style={{ flex: 1 }}>
<Group justify="space-between">
<Text size="sm" fw={600} lineClamp={1}>{bug.description}</Text>
<Box style={{ flex: 1, minWidth: 0 }}>
<Group wrap="nowrap" gap="xs">
<Text size="sm" fw={600} lineClamp={1} style={{ flex: 1, minWidth: 0 }}>{bug.description}</Text>
<Badge
color={STATUS_COLOR[bug.status] ?? 'gray'}
variant="dot"
size="sm"
style={{ flexShrink: 0 }}
>
{STATUS_LABEL[bug.status] ?? bug.status}
</Badge>
</Group>
<Text size="xs" c="dimmed">
<Text size="xs" c="dimmed" lineClamp={1}>
{dayjs(bug.createdAt).format('D MMM YYYY, HH:mm')} · {bug.appId?.toUpperCase()} · v{bug.affectedVersion}
</Text>
</Box>

View File

@@ -198,15 +198,15 @@ function DashboardPage() {
</Button>
</Group>
<Paper withBorder radius="2xl" className="glass" p="md">
<Table className="data-table" verticalSpacing="sm">
<Paper withBorder radius="2xl" className="glass" p="md" style={{ overflowX: 'auto' }}>
<Table className="data-table" verticalSpacing="sm" style={{ minWidth: 560 }}>
<Table.Thead>
<Table.Tr>
<Table.Th>App</Table.Th>
<Table.Th style={{ whiteSpace: 'nowrap' }}>App</Table.Th>
<Table.Th>Error Message</Table.Th>
<Table.Th>Version</Table.Th>
<Table.Th>Reported</Table.Th>
<Table.Th>Status</Table.Th>
<Table.Th style={{ whiteSpace: 'nowrap' }}>Version</Table.Th>
<Table.Th style={{ whiteSpace: 'nowrap' }}>Reported</Table.Th>
<Table.Th style={{ whiteSpace: 'nowrap' }}>Status</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
@@ -227,7 +227,7 @@ function DashboardPage() {
</Table.Tr>
) : recentErrors.map((error: any) => (
<Table.Tr key={error.id}>
<Table.Td>
<Table.Td style={{ whiteSpace: 'nowrap' }}>
<Text fw={600} size="sm" tt="uppercase">{error.app}</Text>
</Table.Td>
<Table.Td style={{ maxWidth: 280 }}>
@@ -237,13 +237,13 @@ function DashboardPage() {
</Text>
</Tooltip>
</Table.Td>
<Table.Td>
<Table.Td style={{ whiteSpace: 'nowrap' }}>
<Badge variant="light" color="gray" size="sm">v{error.version}</Badge>
</Table.Td>
<Table.Td>
<Table.Td style={{ whiteSpace: 'nowrap' }}>
<Text size="xs" c="dimmed">{formatTimeAgo(error.time)}</Text>
</Table.Td>
<Table.Td>
<Table.Td style={{ whiteSpace: 'nowrap' }}>
<Badge
color={SEVERITY_COLOR[error.severity] ?? 'gray'}
variant="light"

View File

@@ -1,6 +1,7 @@
import {
ActionIcon,
Badge,
Box,
Container,
Group,
Loader,
@@ -100,39 +101,43 @@ function GlobalLogsPage() {
</Group>
<Paper withBorder radius="xl" p="md" className="glass">
<Group gap="sm" wrap="wrap" align="flex-end">
<Select
label="User"
placeholder="All users"
value={operatorId}
onChange={(v) => { setOperatorId(v ?? 'all'); setPage(1) }}
data={operatorOptions}
w={200}
clearable
size="sm"
/>
<DatePickerInput
type="range"
label="Date range"
placeholder="Pick a date range"
value={dateRange}
onChange={(v) => { setDateRange(v); setPage(1) }}
locale="id"
valueFormat="DD MMM YYYY"
clearable
w={280}
size="sm"
/>
<Stack gap="md">
<Group gap="sm" wrap="wrap" align="flex-end">
<Select
label="User"
placeholder="All users"
value={operatorId}
onChange={(v) => { setOperatorId(v ?? 'all'); setPage(1) }}
data={operatorOptions}
style={{ flex: 1, minWidth: 160 }}
clearable
size="sm"
/>
<DatePickerInput
type="range"
label="Date range"
placeholder="Pick a date range"
value={dateRange}
onChange={(v) => { setDateRange(v); setPage(1) }}
locale="id"
valueFormat="DD MMM YYYY"
clearable
style={{ flex: 2, minWidth: 220 }}
size="sm"
/>
</Group>
<Stack gap={4}>
<Text size="xs" fw={500} c="dimmed">Action type</Text>
<SegmentedControl
value={type}
onChange={(v) => { setType(v); setPage(1) }}
size="sm"
data={LOG_TYPES.map((t) => ({ label: LOG_TYPE_LABEL[t] ?? t, value: t }))}
/>
<Box style={{ overflowX: 'auto' }}>
<SegmentedControl
value={type}
onChange={(v) => { setType(v); setPage(1) }}
size="sm"
data={LOG_TYPES.map((t) => ({ label: LOG_TYPE_LABEL[t] ?? t, value: t }))}
/>
</Box>
</Stack>
</Group>
</Stack>
</Paper>
{isLoading && !data ? (

View File

@@ -309,14 +309,15 @@ function UsersPage() {
)}
</Group>
<Paper withBorder radius="2xl" className="glass" p={0} style={{ overflow: 'hidden' }}>
<Paper withBorder radius="2xl" className="glass" p={0} style={{ overflowX: 'auto' }}>
<Table.ScrollContainer minWidth={480}>
<Table className="data-table" verticalSpacing="md" highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>Name & Contact</Table.Th>
<Table.Th>Role</Table.Th>
<Table.Th>Joined</Table.Th>
<Table.Th>Actions</Table.Th>
<Table.Th style={{ whiteSpace: 'nowrap' }}>Role</Table.Th>
<Table.Th style={{ whiteSpace: 'nowrap' }}>Joined</Table.Th>
<Table.Th style={{ whiteSpace: 'nowrap' }}>Actions</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
@@ -341,8 +342,8 @@ function UsersPage() {
operators.map((user: any) => (
<Table.Tr key={user.id}>
<Table.Td style={{ opacity: user.active === false ? 0.45 : 1 }}>
<Group gap="sm">
<Box style={{ position: 'relative' }}>
<Group gap="sm" wrap="nowrap">
<Box style={{ position: 'relative', flexShrink: 0 }}>
<Avatar
size="sm"
radius="xl"
@@ -384,7 +385,7 @@ function UsersPage() {
{ROLE_LABEL[user.role] ?? user.role}
</Badge>
</Table.Td>
<Table.Td style={{ opacity: user.active === false ? 0.45 : 1 }}>
<Table.Td style={{ opacity: user.active === false ? 0.45 : 1, whiteSpace: 'nowrap' }}>
<Text size="xs" fw={500} c={user.active === false ? 'dimmed' : undefined}>
{new Date(user.createdAt).toLocaleDateString('en-GB', {
day: 'numeric',
@@ -440,6 +441,7 @@ function UsersPage() {
)}
</Table.Tbody>
</Table>
</Table.ScrollContainer>
</Paper>
{response?.totalPages > 1 && (