118 lines
3.7 KiB
Vue
118 lines
3.7 KiB
Vue
<script setup lang="ts">
|
|
import type { TableColumn } from "@nuxt/ui";
|
|
import type { Project } from "@workspace/schema";
|
|
|
|
definePageMeta({
|
|
title: "Projects",
|
|
});
|
|
|
|
const { projects, loading, error, fetchProjects, deleteProject } = useProjectApi();
|
|
|
|
const deleting = ref<string | null>(null);
|
|
const confirmDelete = ref(false);
|
|
|
|
onMounted(() => fetchProjects());
|
|
|
|
function statusColor(status: string) {
|
|
if (status === "active") return "success" as const;
|
|
if (status === "archived") return "neutral" as const;
|
|
if (status === "completed") return "info" as const;
|
|
return "neutral" as const;
|
|
}
|
|
|
|
async function confirmAndDelete(id: string) {
|
|
deleting.value = id;
|
|
await deleteProject(id);
|
|
deleting.value = null;
|
|
confirmDelete.value = false;
|
|
}
|
|
|
|
const columns: TableColumn<Project>[] = [
|
|
{ accessorKey: "name", header: "Name" },
|
|
{ accessorKey: "status", header: "Status" },
|
|
{
|
|
accessorKey: "description",
|
|
header: "Description",
|
|
cell: ({ row }) => row.getValue("description") ?? "—",
|
|
},
|
|
{ accessorKey: "createdAt", header: "Created" },
|
|
{ id: "actions", header: "" },
|
|
];
|
|
</script>
|
|
|
|
<template>
|
|
<UPage>
|
|
<UPageHeader
|
|
title="Projects"
|
|
description="Manage your projects"
|
|
:links="[{ label: 'New project', icon: 'i-lucide-plus', to: '/projects/new', color: 'primary' }]" />
|
|
|
|
<UPageBody>
|
|
<UAlert v-if="error" color="error" variant="soft" icon="i-lucide-alert-circle" :title="error" class="mb-4" />
|
|
|
|
<UCard>
|
|
<UTable :columns="columns" :data="projects" :loading="loading" class="flex-1">
|
|
<template #status-cell="{ row }">
|
|
<UBadge
|
|
:label="row.getValue('status')"
|
|
:color="statusColor(row.getValue('status') as string)"
|
|
variant="subtle"
|
|
class="capitalize" />
|
|
</template>
|
|
|
|
<template #createdAt-cell="{ row }">
|
|
<span class="text-muted">{{ new Date(row.getValue("createdAt") as string).toLocaleDateString() }}</span>
|
|
</template>
|
|
|
|
<template #actions-cell="{ row }">
|
|
<div class="flex justify-end gap-1">
|
|
<UButton
|
|
icon="i-lucide-pencil"
|
|
color="neutral"
|
|
variant="ghost"
|
|
size="sm"
|
|
:to="`/projects/${row.original.id}/edit`"
|
|
aria-label="Edit" />
|
|
<UButton
|
|
icon="i-lucide-trash-2"
|
|
color="error"
|
|
variant="ghost"
|
|
size="sm"
|
|
:loading="deleting === row.original.id"
|
|
aria-label="Delete"
|
|
@click="
|
|
confirmDelete = true;
|
|
deleting = row.original.id;
|
|
" />
|
|
</div>
|
|
</template>
|
|
|
|
<template #empty>
|
|
<div class="py-12 text-center text-muted">
|
|
<UIcon name="i-lucide-folder-open" class="size-8 mx-auto mb-2" />
|
|
<p>No projects yet.</p>
|
|
</div>
|
|
</template>
|
|
</UTable>
|
|
</UCard>
|
|
|
|
<UModal v-model:open="confirmDelete">
|
|
<template #header>
|
|
<h3 class="text-lg font-semibold">Delete project</h3>
|
|
</template>
|
|
|
|
<template #body>
|
|
<p class="text-sm text-muted">Are you sure you want to delete this project? This action cannot be undone.</p>
|
|
</template>
|
|
|
|
<template #footer>
|
|
<div class="flex gap-3 justify-end">
|
|
<UButton label="Cancel" color="neutral" variant="outline" @click="confirmDelete = false" />
|
|
<UButton label="Delete" color="error" @click="deleting && confirmAndDelete(deleting)" />
|
|
</div>
|
|
</template>
|
|
</UModal>
|
|
</UPageBody>
|
|
</UPage>
|
|
</template>
|