177 lines
6.7 KiB
Vue
177 lines
6.7 KiB
Vue
<script setup lang="ts">
|
|
import AppSidebar from '@/components/AppSidebar.vue'
|
|
import SiteHeader from '@/components/SiteHeader.vue'
|
|
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar'
|
|
|
|
import { ChevronDown, Plus, X } from '@lucide/vue'
|
|
import { computed, ref } from 'vue'
|
|
import { toast } from 'vue-sonner'
|
|
|
|
import DataTable from '@/components/data-table/DataTable.vue'
|
|
import DeleteDialog from '@/components/DeleteDialog.vue'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu'
|
|
import { Input } from '@/components/ui/input'
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from '@/components/ui/select'
|
|
import { type User, getColumns } from './columns'
|
|
|
|
const router = useRouter()
|
|
const config = useRuntimeConfig()
|
|
|
|
const statusFilter = ref('')
|
|
|
|
const { data: users, status, refresh } = useFetch<User[]>(
|
|
() => `${config.public.apiBase}/users${statusFilter.value ? `?status=${statusFilter.value}` : ''}`,
|
|
{ watch: [statusFilter] },
|
|
)
|
|
|
|
const tableRef = ref<InstanceType<typeof DataTable>>()
|
|
|
|
const deleteOpen = ref(false)
|
|
const deleteLoading = ref(false)
|
|
const selectedUser = ref<User | null>(null)
|
|
|
|
function handleEdit(user: User) {
|
|
router.push(`/admin/users/${user.id}/edit`)
|
|
}
|
|
|
|
function handleDelete(user: User) {
|
|
selectedUser.value = user
|
|
deleteOpen.value = true
|
|
}
|
|
|
|
const columns = computed(() => getColumns({ onEdit: handleEdit, onDelete: handleDelete }))
|
|
|
|
async function handleConfirmDelete() {
|
|
if (!selectedUser.value) return
|
|
|
|
deleteLoading.value = true
|
|
try {
|
|
await $fetch(`${config.public.apiBase}/users/${selectedUser.value.id}`, {
|
|
method: 'DELETE',
|
|
})
|
|
toast.success('Pengguna berhasil dihapus')
|
|
deleteOpen.value = false
|
|
selectedUser.value = null
|
|
await refresh()
|
|
} catch (e: any) {
|
|
toast.error(e?.data?.message ?? 'Gagal menghapus pengguna')
|
|
} finally {
|
|
deleteLoading.value = false
|
|
}
|
|
}
|
|
|
|
function hasActiveFilter() {
|
|
return !!statusFilter.value
|
|
}
|
|
|
|
function resetFilters() {
|
|
statusFilter.value = ''
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<SidebarProvider :style="{
|
|
'--sidebar-width': 'calc(var(--spacing) * 72)',
|
|
'--header-height': 'calc(var(--spacing) * 12)',
|
|
}">
|
|
<AppSidebar variant="inset" />
|
|
<SidebarInset>
|
|
<SiteHeader />
|
|
<div class="flex flex-1 flex-col">
|
|
<div class="@container/main flex flex-1 flex-col gap-2">
|
|
<div class="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
|
<div class="px-4 lg:px-6">
|
|
<div class="flex items-center justify-between">
|
|
<h1 class="text-2xl font-bold">Pengguna</h1>
|
|
<Button size="sm" @click="router.push('/admin/users/create')">
|
|
<Plus class="h-4 w-4 mr-1" />
|
|
Tambah
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div class="px-4 lg:px-6">
|
|
<DataTable ref="tableRef" :columns="columns" :data="users ?? []" :loading="status === 'pending'">
|
|
<template #filters="{ table }">
|
|
<div class="flex items-center gap-2 py-4">
|
|
<Input class="max-w-sm" placeholder="Cari email atau nama..."
|
|
:model-value="(table.getColumn('search')?.getFilterValue() as string) ?? ''"
|
|
@update:model-value="table.getColumn('search')?.setFilterValue($event)" />
|
|
<Select :model-value="statusFilter || 'all'"
|
|
@update:model-value="statusFilter = $event === 'all' ? '' : $event">
|
|
<SelectTrigger class="hidden h-8 w-auto md:flex">
|
|
<SelectValue placeholder="Semua Status" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="all">Semua Status</SelectItem>
|
|
<SelectItem value="ACTIVE">Aktif</SelectItem>
|
|
<SelectItem value="ONBOARDING">Onboarding</SelectItem>
|
|
<SelectItem value="INACTIVE">Nonaktif</SelectItem>
|
|
<SelectItem value="SUSPENDED">Ditangguhkan</SelectItem>
|
|
<SelectItem value="DISABLED">Dinonaktifkan</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<Button v-if="hasActiveFilter()" variant="ghost" size="sm" class="h-8"
|
|
@click="resetFilters()">
|
|
<X class="h-4 w-4 mr-1" />
|
|
Reset
|
|
</Button>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger as-child>
|
|
<Button variant="outline" class="ml-auto md:hidden">
|
|
Filter
|
|
<ChevronDown class="ml-2 h-4 w-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuLabel>Status</DropdownMenuLabel>
|
|
<div class="px-2 pb-2">
|
|
<Select :model-value="statusFilter || 'all'"
|
|
@update:model-value="statusFilter = $event === 'all' ? '' : $event">
|
|
<SelectTrigger class="h-8 w-37.5">
|
|
<SelectValue placeholder="Semua Status" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="all">Semua Status</SelectItem>
|
|
<SelectItem value="ACTIVE">Aktif</SelectItem>
|
|
<SelectItem value="ONBOARDING">Onboarding</SelectItem>
|
|
<SelectItem value="INACTIVE">Nonaktif</SelectItem>
|
|
<SelectItem value="SUSPENDED">Ditangguhkan</SelectItem>
|
|
<SelectItem value="DISABLED">Dinonaktifkan</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem :disabled="!hasActiveFilter()" @click="resetFilters()">
|
|
Reset Filter
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
</template>
|
|
</DataTable>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</SidebarInset>
|
|
|
|
<DeleteDialog v-model:open="deleteOpen" title="Hapus Pengguna"
|
|
:item-name="selectedUser?.email ?? selectedUser?.username" :loading="deleteLoading"
|
|
@confirm="handleConfirmDelete" />
|
|
</SidebarProvider>
|
|
</template>
|