upd: connected ke db

Deskripi:
- list error report general dan per apps
- update status
- update feedback

No Issues
This commit is contained in:
2026-04-14 12:05:34 +08:00
parent 14adaa8526
commit a0cafbf2e2
6 changed files with 963 additions and 180 deletions

View File

@@ -2,10 +2,12 @@ import { DashboardLayout } from '@/frontend/components/DashboardLayout'
import { API_URLS } from '@/frontend/config/api'
import {
Accordion,
Avatar,
Badge,
Box,
Button,
Code,
Collapse,
Container,
Group,
Image,
@@ -52,6 +54,12 @@ function ListErrorsPage() {
const [app, setApp] = useState('all')
const [status, setStatus] = useState('all')
const [showLogs, setShowLogs] = useState<Record<string, boolean>>({})
const toggleLogs = (bugId: string) => {
setShowLogs((prev) => ({ ...prev, [bugId]: !prev[bugId] }))
}
const { data, isLoading, refetch } = useQuery({
queryKey: ['bugs', { page, search, app, status }],
queryFn: () =>
@@ -63,7 +71,7 @@ function ListErrorsPage() {
const [isSubmitting, setIsSubmitting] = useState(false)
const [createForm, setCreateForm] = useState({
description: '',
app: 'desa_plus',
app: 'desa-plus',
status: 'OPEN',
source: 'USER',
affectedVersion: '',
@@ -73,6 +81,94 @@ function ListErrorsPage() {
imageUrl: '',
})
// Update Status Modal Logic
const [updateModalOpened, { open: openUpdateModal, close: closeUpdateModal }] = useDisclosure(false)
const [isUpdating, setIsUpdating] = useState(false)
const [selectedBugId, setSelectedBugId] = useState<string | null>(null)
const [updateForm, setUpdateForm] = useState({
status: '',
description: '',
})
// Feedback Modal Logic
const [feedbackModalOpened, { open: openFeedbackModal, close: closeFeedbackModal }] = useDisclosure(false)
const [isUpdatingFeedback, setIsUpdatingFeedback] = useState(false)
const [feedbackForm, setFeedbackForm] = useState({
feedBack: '',
})
const handleUpdateFeedback = async () => {
if (!selectedBugId || !feedbackForm.feedBack) return
setIsUpdatingFeedback(true)
try {
const res = await fetch(API_URLS.updateBugFeedback(selectedBugId), {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(feedbackForm),
})
if (res.ok) {
notifications.show({
title: 'Success',
message: 'Feedback has been updated.',
color: 'teal',
icon: <TbCircleCheck size={18} />,
})
refetch()
closeFeedbackModal()
setFeedbackForm({ feedBack: '' })
} else {
throw new Error('Failed to update feedback')
}
} catch (e) {
notifications.show({
title: 'Error',
message: 'Something went wrong.',
color: 'red',
icon: <TbCircleX size={18} />,
})
} finally {
setIsUpdatingFeedback(false)
}
}
const handleUpdateStatus = async () => {
if (!selectedBugId || !updateForm.status) return
setIsUpdating(true)
try {
const res = await fetch(API_URLS.updateBugStatus(selectedBugId), {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updateForm),
})
if (res.ok) {
notifications.show({
title: 'Success',
message: 'Status has been updated.',
color: 'teal',
icon: <TbCircleCheck size={18} />,
})
refetch()
closeUpdateModal()
setUpdateForm({ status: '', description: '' })
} else {
throw new Error('Failed to update status')
}
} catch (e) {
notifications.show({
title: 'Error',
message: 'Something went wrong.',
color: 'red',
icon: <TbCircleX size={18} />,
})
} finally {
setIsUpdating(false)
}
}
const handleCreateBug = async () => {
if (!createForm.description || !createForm.affectedVersion || !createForm.device || !createForm.os) {
notifications.show({
@@ -95,12 +191,12 @@ function ListErrorsPage() {
await fetch(API_URLS.createLog(), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'CREATE', message: `Report bug baru ditambahkan: ${createForm.description.substring(0, 50)}${createForm.description.length > 50 ? '...' : ''}` })
body: JSON.stringify({ type: 'CREATE', message: `Report error baru ditambahkan: ${createForm.description.substring(0, 50)}${createForm.description.length > 50 ? '...' : ''}` })
}).catch(console.error)
notifications.show({
title: 'Success',
message: 'Bug report has been created.',
message: 'Error report has been created.',
color: 'teal',
icon: <TbCircleCheck size={18} />,
})
@@ -108,7 +204,7 @@ function ListErrorsPage() {
close()
setCreateForm({
description: '',
app: 'desa_plus',
app: 'desa-plus',
status: 'OPEN',
source: 'USER',
affectedVersion: '',
@@ -118,7 +214,7 @@ function ListErrorsPage() {
imageUrl: '',
})
} else {
throw new Error('Failed to create bug report')
throw new Error('Failed to create error report')
}
} catch (e) {
notifications.show({
@@ -142,10 +238,10 @@ function ListErrorsPage() {
<Group justify="space-between" align="center">
<Stack gap={0}>
<Title order={2} className="gradient-text">
Bug Reports
Error Reports
</Title>
<Text size="sm" c="dimmed">
Centralized bug tracking and analysis for all applications.
Centralized error tracking and analysis for all applications.
</Text>
</Stack>
<Group>
@@ -155,7 +251,7 @@ function ListErrorsPage() {
leftSection={<TbPlus size={18} />}
onClick={open}
>
Report Bug
Report Error
</Button>
{/* <Button variant="light" color="red" leftSection={<TbBug size={16} />}>
Generate Report
@@ -163,10 +259,83 @@ function ListErrorsPage() {
</Group>
</Group>
<Modal
opened={updateModalOpened}
onClose={closeUpdateModal}
title={<Text fw={700} size="lg">Update Bug Status</Text>}
radius="xl"
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
>
<Stack gap="md">
<Select
label="New Status"
placeholder="Select status"
required
data={[
{ value: 'OPEN', label: 'Open' },
{ value: 'ON_HOLD', label: 'On Hold' },
{ value: 'IN_PROGRESS', label: 'In Progress' },
{ value: 'RESOLVED', label: 'Resolved' },
{ value: 'RELEASED', label: 'Released' },
{ value: 'CLOSED', label: 'Closed' },
]}
value={updateForm.status}
onChange={(val) => setUpdateForm({ ...updateForm, status: val || '' })}
/>
<Textarea
label="Update Note (Optional)"
placeholder="E.g. Fixed in commit xxxxx / Assigned to team"
minRows={3}
value={updateForm.description}
onChange={(e) => setUpdateForm({ ...updateForm, description: e.target.value })}
/>
<Button
fullWidth
mt="md"
variant="gradient"
gradient={{ from: '#2563EB', to: '#7C3AED', deg: 135 }}
loading={isUpdating}
onClick={handleUpdateStatus}
>
Save Changes
</Button>
</Stack>
</Modal>
<Modal
opened={feedbackModalOpened}
onClose={closeFeedbackModal}
title={<Text fw={700} size="lg">Developer Feedback</Text>}
radius="xl"
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
>
<Stack gap="md">
<Textarea
data-autofocus
label="Feedback / Note"
placeholder="Explain the issue, root cause, or resolution..."
required
minRows={4}
value={feedbackForm.feedBack}
onChange={(e) => setFeedbackForm({ ...feedbackForm, feedBack: e.target.value })}
/>
<Button
fullWidth
mt="md"
variant="gradient"
gradient={{ from: '#2563EB', to: '#7C3AED', deg: 135 }}
loading={isUpdatingFeedback}
onClick={handleUpdateFeedback}
>
Save Feedback
</Button>
</Stack>
</Modal>
<Modal
opened={opened}
onClose={close}
title={<Text fw={700} size="lg">Report New Bug</Text>}
title={<Text fw={700} size="lg">Report New Error</Text>}
radius="xl"
size="lg"
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
@@ -174,7 +343,7 @@ function ListErrorsPage() {
<Stack gap="md">
<Textarea
label="Description"
placeholder="What happened? Describe the bug in detail..."
placeholder="What happened? Describe the error in detail..."
required
minRows={3}
value={createForm.description}
@@ -264,7 +433,7 @@ function ListErrorsPage() {
loading={isSubmitting}
onClick={handleCreateBug}
>
Submit Bug Report
Submit Error Report
</Button>
</Stack>
</Modal>
@@ -292,7 +461,7 @@ function ListErrorsPage() {
<Select
placeholder="Status"
data={[
{ value: 'all', label: 'All Statuses' },
{ value: 'all', label: 'All Status' },
{ value: 'OPEN', label: 'Open' },
{ value: 'ON_HOLD', label: 'On Hold' },
{ value: 'IN_PROGRESS', label: 'In Progress' },
@@ -319,7 +488,7 @@ function ListErrorsPage() {
) : bugs.length === 0 ? (
<Paper p="xl" withBorder style={{ borderStyle: 'dashed', textAlign: 'center' }}>
<TbBug size={48} color="gray" style={{ marginBottom: 12, opacity: 0.5 }} />
<Text fw={600}>No bugs found</Text>
<Text fw={600}>No error reports found</Text>
<Text size="sm" c="dimmed">Try adjusting your filters or search terms.</Text>
</Paper>
) : (
@@ -398,6 +567,29 @@ function ListErrorsPage() {
</Box>
</SimpleGrid>
{/* Feedback & Reporter Info */}
{(bug.user || bug.feedBack) && (
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="lg">
{bug.user && (
<Box>
<Text size="xs" fw={700} c="dimmed" mb={4}>REPORTED BY</Text>
<Group gap="xs">
<Avatar src={bug.user.image} radius="xl" size="sm" color="blue">
{bug.user.name?.charAt(0).toUpperCase()}
</Avatar>
<Text size="sm">{bug.user.name}</Text>
</Group>
</Box>
)}
{bug.feedBack && (
<Box>
<Text size="xs" fw={700} c="dimmed" mb={4}>DEVELOPER FEEDBACK</Text>
<Text size="sm" style={{ whiteSpace: 'pre-wrap' }}>{bug.feedBack}</Text>
</Box>
)}
</SimpleGrid>
)}
{/* Stack Trace */}
{bug.stackTrace && (
<Box>
@@ -427,7 +619,7 @@ function ListErrorsPage() {
<SimpleGrid cols={{ base: 2, sm: 4 }} spacing="xs">
{bug.images.map((img: any) => (
<Paper key={img.id} withBorder radius="md" style={{ overflow: 'hidden' }}>
<Image src={img.imageUrl} alt="Bug screenshot" height={100} fit="cover" />
<Image src={img.imageUrl} alt="Error screenshot" height={100} fit="cover" />
</Paper>
))}
</SimpleGrid>
@@ -437,32 +629,52 @@ function ListErrorsPage() {
{/* Logs / History */}
{bug.logs && bug.logs.length > 0 && (
<Box>
<Group gap="xs" mb={12}>
<TbHistory size={16} color="gray" />
<Text size="xs" fw={700} c="dimmed">ACTIVITY LOG</Text>
<Group justify="space-between" mb={showLogs[bug.id] ? 12 : 0}>
<Group gap="xs">
<TbHistory size={16} color="gray" />
<Text size="xs" fw={700} c="dimmed">ACTIVITY LOG ({bug.logs.length})</Text>
</Group>
<Button
variant="subtle"
size="compact-xs"
color="gray"
onClick={() => toggleLogs(bug.id)}
>
{showLogs[bug.id] ? 'Hide' : 'Show'}
</Button>
</Group>
<Timeline active={bug.logs.length - 1} bulletSize={24} lineWidth={2}>
{bug.logs.map((log: any) => (
<Timeline.Item
key={log.id}
bullet={
<Badge size="xs" circle color={log.status === 'RESOLVED' ? 'teal' : 'blue'}> </Badge>
}
title={<Text size="sm" fw={600}>{log.status}</Text>}
>
<Text size="xs" c="dimmed" mb={4}>
{new Date(log.createdAt).toLocaleString()} by {log.user?.name || 'Unknown'}
</Text>
<Text size="sm">{log.description}</Text>
</Timeline.Item>
))}
</Timeline>
<Collapse in={showLogs[bug.id]}>
<Timeline active={bug.logs.length - 1} bulletSize={24} lineWidth={2} mt="md">
{bug.logs.map((log: any) => (
<Timeline.Item
key={log.id}
bullet={
<Badge size="xs" circle color={log.status === 'RESOLVED' ? 'teal' : 'blue'}> </Badge>
}
title={<Text size="sm" fw={600}>{log.status}</Text>}
>
<Text size="xs" c="dimmed" mb={4}>
{new Date(log.createdAt).toLocaleString()} by {log.user?.name || 'Unknown'}
</Text>
<Text size="sm">{log.description}</Text>
</Timeline.Item>
))}
</Timeline>
</Collapse>
</Box>
)}
<Group justify="flex-end" pt="sm">
<Button variant="light" size="compact-xs" color="blue">Assign Developer</Button>
<Button variant="light" size="compact-xs" color="teal">Update Status</Button>
<Button variant="light" size="compact-xs" color="blue" onClick={() => {
setSelectedBugId(bug.id)
setFeedbackForm({ feedBack: bug.feedBack || '' })
openFeedbackModal()
}}>Developer Feedback</Button>
<Button variant="light" size="compact-xs" color="teal" onClick={() => {
setSelectedBugId(bug.id)
setUpdateForm({ status: bug.status, description: '' })
openUpdateModal()
}}>Update Status</Button>
</Group>
</Stack>
</Accordion.Panel>