upd: api monitoring
Deskripsi : - api deactivate or active desa - api edit desa No Issues
This commit is contained in:
@@ -23,4 +23,6 @@ export const API_URLS = {
|
|||||||
listGroup: (id: string) => `${API_BASE_URL}/api/monitoring/list-group-villages?id=${id}`,
|
listGroup: (id: string) => `${API_BASE_URL}/api/monitoring/list-group-villages?id=${id}`,
|
||||||
listPosition: (id: string) => `${API_BASE_URL}/api/monitoring/list-position-villages?id=${id}`,
|
listPosition: (id: string) => `${API_BASE_URL}/api/monitoring/list-position-villages?id=${id}`,
|
||||||
editUser: () => `${API_BASE_URL}/api/monitoring/edit-user`,
|
editUser: () => `${API_BASE_URL}/api/monitoring/edit-user`,
|
||||||
|
updateStatusVillages: () => `${API_BASE_URL}/api/monitoring/update-status-villages`,
|
||||||
|
editVillages: () => `${API_BASE_URL}/api/monitoring/edit-villages`,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -639,8 +639,8 @@ function UsersIndexPage() {
|
|||||||
<Paper withBorder radius="2xl" className="glass" style={{ overflow: 'hidden' }}>
|
<Paper withBorder radius="2xl" className="glass" style={{ overflow: 'hidden' }}>
|
||||||
<ScrollArea h={isMobile ? undefined : 'auto'} offsetScrollbars>
|
<ScrollArea h={isMobile ? undefined : 'auto'} offsetScrollbars>
|
||||||
<Table
|
<Table
|
||||||
verticalSpacing="lg"
|
verticalSpacing="md"
|
||||||
horizontalSpacing="xl"
|
horizontalSpacing="md"
|
||||||
highlightOnHover
|
highlightOnHover
|
||||||
withColumnBorders={false}
|
withColumnBorders={false}
|
||||||
style={{
|
style={{
|
||||||
@@ -654,14 +654,13 @@ function UsersIndexPage() {
|
|||||||
<Table.Th style={{ border: 'none', width: isMobile ? undefined : '28%' }}>User & ID</Table.Th>
|
<Table.Th style={{ border: 'none', width: isMobile ? undefined : '28%' }}>User & ID</Table.Th>
|
||||||
<Table.Th style={{ border: 'none', width: isMobile ? undefined : '25%' }}>Contact Detail</Table.Th>
|
<Table.Th style={{ border: 'none', width: isMobile ? undefined : '25%' }}>Contact Detail</Table.Th>
|
||||||
<Table.Th style={{ border: 'none', width: isMobile ? undefined : '22%' }}>Organization</Table.Th>
|
<Table.Th style={{ border: 'none', width: isMobile ? undefined : '22%' }}>Organization</Table.Th>
|
||||||
<Table.Th style={{ border: 'none', width: isMobile ? undefined : '12%' }}>Role</Table.Th>
|
<Table.Th style={{ border: 'none', width: isMobile ? undefined : '20%' }}>Role</Table.Th>
|
||||||
<Table.Th style={{ border: 'none', width: isMobile ? undefined : '10%' }}>Status</Table.Th>
|
<Table.Th style={{ border: 'none', width: isMobile ? undefined : '10%' }}>Status</Table.Th>
|
||||||
<Table.Th style={{ border: 'none', width: isMobile ? undefined : '8%' }}>Actions</Table.Th>
|
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{users.map((user) => (
|
{users.map((user) => (
|
||||||
<Table.Tr key={user.id} style={{ borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
|
<Table.Tr key={user.id} style={{ borderBottom: '1px solid rgba(255,255,255,0.05)' }} onClick={()=>{handleEditOpen(user)}}>
|
||||||
<Table.Td>
|
<Table.Td>
|
||||||
<Group gap="md" wrap="nowrap">
|
<Group gap="md" wrap="nowrap">
|
||||||
<Avatar
|
<Avatar
|
||||||
@@ -745,17 +744,6 @@ function UsersIndexPage() {
|
|||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Table.Td>
|
</Table.Td>
|
||||||
<Table.Td>
|
|
||||||
<ActionIcon
|
|
||||||
variant="light"
|
|
||||||
color="brand-blue"
|
|
||||||
onClick={() => handleEditOpen(user)}
|
|
||||||
size="md"
|
|
||||||
radius="md"
|
|
||||||
>
|
|
||||||
<TbEdit size={18} />
|
|
||||||
</ActionIcon>
|
|
||||||
</Table.Td>
|
|
||||||
</Table.Tr>
|
</Table.Tr>
|
||||||
))}
|
))}
|
||||||
</Table.Tbody>
|
</Table.Tbody>
|
||||||
|
|||||||
@@ -4,14 +4,19 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
Group,
|
Group,
|
||||||
|
Modal,
|
||||||
Paper,
|
Paper,
|
||||||
SegmentedControl,
|
SegmentedControl,
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
|
Textarea,
|
||||||
|
TextInput,
|
||||||
ThemeIcon,
|
ThemeIcon,
|
||||||
Title
|
Title
|
||||||
} from '@mantine/core'
|
} from '@mantine/core'
|
||||||
|
import { useDisclosure } from '@mantine/hooks'
|
||||||
|
import { notifications } from '@mantine/notifications'
|
||||||
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
|
import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import {
|
import {
|
||||||
@@ -144,12 +149,120 @@ function VillageDetailPage() {
|
|||||||
const { appId, villageId } = useParams({ from: '/apps/$appId/villages/$villageId' })
|
const { appId, villageId } = useParams({ from: '/apps/$appId/villages/$villageId' })
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const { data: infoRes, isLoading: infoLoading } = useSWR(API_URLS.infoVillages(villageId), fetcher)
|
const { data: infoRes, isLoading: infoLoading, mutate } = useSWR(API_URLS.infoVillages(villageId), fetcher)
|
||||||
const { data: gridRes, isLoading: gridLoading } = useSWR(API_URLS.gridVillages(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 village = infoRes?.data
|
||||||
const stats = gridRes?.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) {
|
||||||
|
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) {
|
||||||
|
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 } })
|
const goBack = () => navigate({ to: '/apps/$appId/villages', params: { appId } })
|
||||||
|
|
||||||
if (infoLoading || gridLoading) {
|
if (infoLoading || gridLoading) {
|
||||||
@@ -195,8 +308,9 @@ function VillageDetailPage() {
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
color={village.isActive ? 'red' : 'green'}
|
color={village.isActive ? 'red' : 'green'}
|
||||||
leftSection={village.isActive ? <TbPower size={16} /> : <TbPower size={16} />}
|
leftSection={village.isActive ? <TbPower size={16} /> : <TbPower size={16} />}
|
||||||
onClick={() => alert(`Toggle status for ${village.name}`)}
|
onClick={openConfirmModal}
|
||||||
radius="md"
|
radius="md"
|
||||||
|
loading={isUpdating}
|
||||||
>
|
>
|
||||||
{village.isActive ? 'Deactivate' : 'Active'}
|
{village.isActive ? 'Deactivate' : 'Active'}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -204,7 +318,7 @@ function VillageDetailPage() {
|
|||||||
variant="light"
|
variant="light"
|
||||||
color="blue"
|
color="blue"
|
||||||
leftSection={<TbEdit size={16} />}
|
leftSection={<TbEdit size={16} />}
|
||||||
onClick={() => alert(`Edit settings for ${village.name}`)}
|
onClick={openEdit}
|
||||||
radius="md"
|
radius="md"
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
@@ -347,6 +461,75 @@ function VillageDetailPage() {
|
|||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* ── Confirmation Modal ── */}
|
||||||
|
<Modal
|
||||||
|
opened={confirmModalOpened}
|
||||||
|
onClose={closeConfirmModal}
|
||||||
|
title={<Text fw={700}>Confirm Status Change</Text>}
|
||||||
|
radius="xl"
|
||||||
|
centered
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Text size="sm">
|
||||||
|
Are you sure you want to <strong>{village.isActive ? 'deactivate' : 'activate'}</strong> village <strong>{village.name}</strong>?
|
||||||
|
</Text>
|
||||||
|
<Group justify="flex-end" gap="sm">
|
||||||
|
<Button variant="light" color="gray" onClick={closeConfirmModal} radius="md">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={village.isActive ? 'red' : 'green'}
|
||||||
|
onClick={handleConfirmToggle}
|
||||||
|
loading={isUpdating}
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
Confirm
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{/* ── Edit Village Modal ── */}
|
||||||
|
<Modal
|
||||||
|
opened={editModalOpened}
|
||||||
|
onClose={closeEditModal}
|
||||||
|
title={<Text fw={700}>Edit Village Details</Text>}
|
||||||
|
radius="xl"
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<TextInput
|
||||||
|
label="Village Name"
|
||||||
|
placeholder="Enter village name"
|
||||||
|
required
|
||||||
|
value={editForm.name}
|
||||||
|
onChange={(e) => setEditForm(prev => ({ ...prev, name: e.currentTarget.value }))}
|
||||||
|
/>
|
||||||
|
<Textarea
|
||||||
|
label="Description"
|
||||||
|
placeholder="Enter village description..."
|
||||||
|
minRows={3}
|
||||||
|
required
|
||||||
|
value={editForm.desc}
|
||||||
|
onChange={(e) => setEditForm(prev => ({ ...prev, desc: e.currentTarget.value }))}
|
||||||
|
/>
|
||||||
|
<Group justify="flex-end" gap="sm" mt="md">
|
||||||
|
<Button variant="light" color="gray" onClick={closeEditModal} radius="md">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="filled"
|
||||||
|
color="blue"
|
||||||
|
onClick={handleEditVillage}
|
||||||
|
loading={isEditing}
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
|
Save Changes
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
</Stack>
|
</Stack>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user