185 lines
7.8 KiB
Vue
185 lines
7.8 KiB
Vue
<script setup lang="ts">
|
|
import { Head, Link } from '@inertiajs/vue3';
|
|
import {
|
|
ArrowUpRight,
|
|
Columns3,
|
|
FolderGit2,
|
|
ListTodo,
|
|
Plus,
|
|
} from 'lucide-vue-next';
|
|
import { computed } from 'vue';
|
|
import FeedbackState from '@/components/FeedbackState.vue';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { useLocalization } from '@/composables/useLocalization';
|
|
import AppLayout from '@/layouts/AppLayout.vue';
|
|
import { create, index, show } from '@/routes/projects';
|
|
import { index as backlogIndex } from '@/routes/projects/backlog';
|
|
import { index as boardIndex } from '@/routes/projects/board';
|
|
import type { BreadcrumbItem, ProjectsIndexPageProps } from '@/types';
|
|
|
|
defineProps<ProjectsIndexPageProps>();
|
|
const { t } = useLocalization();
|
|
const breadcrumbs = computed<BreadcrumbItem[]>(() => [
|
|
{ title: t('navigation.projects'), href: index() },
|
|
]);
|
|
</script>
|
|
|
|
<template>
|
|
<AppLayout :breadcrumbs="breadcrumbs">
|
|
<Head :title="t('navigation.projects')" />
|
|
|
|
<div class="gap-page md:p-page flex min-w-0 flex-1 flex-col p-4">
|
|
<header
|
|
class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"
|
|
>
|
|
<div class="gap-stack grid min-w-0">
|
|
<p class="text-label text-muted-foreground uppercase">
|
|
{{ t('projects.workspace') }}
|
|
</p>
|
|
<h1 class="text-headline-mobile md:text-headline">
|
|
{{ t('navigation.projects') }}
|
|
</h1>
|
|
<p class="text-muted-foreground max-w-2xl">
|
|
{{ t('projects.description') }}
|
|
</p>
|
|
</div>
|
|
<Button
|
|
v-if="$page.props.auth.permissions.projectCreate"
|
|
as-child
|
|
class="self-start"
|
|
>
|
|
<Link :href="create()"
|
|
><Plus aria-hidden="true" />{{
|
|
t('projects.create')
|
|
}}</Link
|
|
>
|
|
</Button>
|
|
</header>
|
|
|
|
<FeedbackState
|
|
v-if="status"
|
|
state="success"
|
|
:title="t('projects.updated')"
|
|
:description="status"
|
|
/>
|
|
|
|
<FeedbackState
|
|
v-if="projects.length === 0"
|
|
state="empty"
|
|
:title="t('projects.empty.title')"
|
|
:description="
|
|
$page.props.auth.permissions.projectCreate
|
|
? t('projects.empty.createDescription')
|
|
: t('projects.empty.accessDescription')
|
|
"
|
|
>
|
|
<Button
|
|
v-if="$page.props.auth.permissions.projectCreate"
|
|
as-child
|
|
variant="outline"
|
|
>
|
|
<Link :href="create()"
|
|
><Plus aria-hidden="true" />{{
|
|
t('projects.empty.create')
|
|
}}</Link
|
|
>
|
|
</Button>
|
|
</FeedbackState>
|
|
|
|
<section
|
|
v-else
|
|
aria-labelledby="project-list-title"
|
|
class="gap-column grid"
|
|
>
|
|
<h2
|
|
id="project-list-title"
|
|
class="text-caption text-muted-foreground"
|
|
>
|
|
{{ t('projects.available', { count: projects.length }) }}
|
|
</h2>
|
|
<ul class="gap-column grid md:grid-cols-2 xl:grid-cols-3">
|
|
<li
|
|
v-for="project in projects"
|
|
:key="project.id"
|
|
class="min-w-0"
|
|
>
|
|
<article
|
|
class="bg-card text-card-foreground flex h-full min-w-0 flex-col rounded-md border"
|
|
>
|
|
<Link
|
|
:href="show(project.id)"
|
|
:aria-label="
|
|
t('projects.open', { name: project.name })
|
|
"
|
|
class="gap-column hover:bg-accent/50 group flex min-w-0 flex-1 items-start rounded-t-md p-5 transition-colors"
|
|
>
|
|
<span
|
|
class="bg-accent text-primary flex size-11 shrink-0 items-center justify-center rounded-md"
|
|
>
|
|
<FolderGit2
|
|
class="size-5"
|
|
aria-hidden="true"
|
|
/>
|
|
</span>
|
|
<div class="gap-stack grid min-w-0 flex-1">
|
|
<Badge
|
|
variant="outline"
|
|
class="max-w-full justify-self-start whitespace-normal break-all"
|
|
>{{ project.key }}</Badge
|
|
>
|
|
<h3
|
|
class="text-title group-hover:text-primary break-words"
|
|
>
|
|
{{ project.name }}
|
|
</h3>
|
|
<span
|
|
class="text-caption text-muted-foreground"
|
|
>{{ t('projects.overview') }}</span
|
|
>
|
|
</div>
|
|
<ArrowUpRight
|
|
class="text-muted-foreground mt-1 size-4 shrink-0"
|
|
aria-hidden="true"
|
|
/>
|
|
</Link>
|
|
<div
|
|
class="gap-stack p-card grid grid-cols-2 border-t"
|
|
>
|
|
<Button as-child variant="secondary">
|
|
<Link
|
|
:href="boardIndex(project.id)"
|
|
:aria-label="
|
|
t('projects.openBoard', {
|
|
name: project.name,
|
|
})
|
|
"
|
|
>
|
|
<Columns3 aria-hidden="true" />{{
|
|
t('navigation.board')
|
|
}}
|
|
</Link>
|
|
</Button>
|
|
<Button as-child variant="ghost">
|
|
<Link
|
|
:href="backlogIndex(project.id)"
|
|
:aria-label="
|
|
t('projects.openBacklog', {
|
|
name: project.name,
|
|
})
|
|
"
|
|
>
|
|
<ListTodo aria-hidden="true" />{{
|
|
t('navigation.backlog')
|
|
}}
|
|
</Link>
|
|
</Button>
|
|
</div>
|
|
</article>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
</div>
|
|
</AppLayout>
|
|
</template>
|