228 lines
8.9 KiB
Vue
228 lines
8.9 KiB
Vue
<script setup lang="ts">
|
|
import { Head, Link } from '@inertiajs/vue3';
|
|
import {
|
|
ArrowLeft,
|
|
ArrowRight,
|
|
Columns3,
|
|
Layers3,
|
|
ListTodo,
|
|
} from 'lucide-vue-next';
|
|
import { computed } from '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 { projectSettingsNavigation } from '@/lib/navigation';
|
|
import { index, show } from '@/routes/projects';
|
|
import { index as backlogIndex } from '@/routes/projects/backlog';
|
|
import { index as boardIndex } from '@/routes/projects/board';
|
|
import { create as projectTemplateCreate } from '@/routes/projects/templates';
|
|
import type { BreadcrumbItem, ProjectShowPageProps } from '@/types';
|
|
|
|
const props = defineProps<ProjectShowPageProps>();
|
|
const { t } = useLocalization();
|
|
const breadcrumbs = computed<BreadcrumbItem[]>(() => [
|
|
{ title: t('navigation.projects'), href: index() },
|
|
{ title: props.project.name, href: show(props.project.id) },
|
|
]);
|
|
const projectAreas = computed(() => [
|
|
{
|
|
title: t('navigation.board'),
|
|
description: t('projects.boardDescription'),
|
|
action: t('projects.boardAction'),
|
|
href: boardIndex(props.project.id),
|
|
icon: Columns3,
|
|
},
|
|
{
|
|
title: t('navigation.backlog'),
|
|
description: t('projects.backlogDescription'),
|
|
action: t('projects.backlogAction'),
|
|
href: backlogIndex(props.project.id),
|
|
icon: ListTodo,
|
|
},
|
|
]);
|
|
const settingsItems = computed(() =>
|
|
projectSettingsNavigation(
|
|
{
|
|
id: props.project.id,
|
|
name: props.project.name,
|
|
key: props.project.key,
|
|
canManage: props.canManageAccess,
|
|
canCreateTickets: false,
|
|
},
|
|
t,
|
|
),
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<AppLayout :breadcrumbs="breadcrumbs">
|
|
<Head :title="project.name" />
|
|
|
|
<div class="gap-page md:p-page flex min-w-0 flex-1 flex-col p-4">
|
|
<Link
|
|
:href="index()"
|
|
class="text-caption text-muted-foreground hover:text-primary min-h-control inline-flex items-center gap-2 self-start rounded-md"
|
|
>
|
|
<ArrowLeft class="size-4" aria-hidden="true" />{{
|
|
t('projects.back')
|
|
}}
|
|
</Link>
|
|
|
|
<header
|
|
class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"
|
|
>
|
|
<div class="gap-stack grid min-w-0">
|
|
<Badge
|
|
variant="outline"
|
|
class="max-w-full justify-self-start whitespace-normal break-all"
|
|
>{{ project.key }}</Badge
|
|
>
|
|
<h1
|
|
class="text-headline-mobile md:text-headline break-words"
|
|
>
|
|
{{ project.name }}
|
|
</h1>
|
|
<p class="text-muted-foreground max-w-2xl">
|
|
{{ t('projects.hubDescription') }}
|
|
</p>
|
|
</div>
|
|
<Button
|
|
v-if="$page.props.auth.permissions.projectCreate"
|
|
as-child
|
|
variant="outline"
|
|
class="self-start"
|
|
>
|
|
<Link :href="projectTemplateCreate(project.id)"
|
|
><Layers3 aria-hidden="true" />{{
|
|
t('projects.saveTemplate')
|
|
}}</Link
|
|
>
|
|
</Button>
|
|
</header>
|
|
|
|
<section
|
|
:aria-label="t('projects.workAreas')"
|
|
class="gap-column grid md:grid-cols-2"
|
|
>
|
|
<Link
|
|
v-for="area in projectAreas"
|
|
:key="area.href.url"
|
|
:href="area.href"
|
|
:aria-label="area.action"
|
|
class="gap-column bg-card hover:border-primary/50 hover:shadow-card-hover group flex min-w-0 flex-col rounded-md border p-5 transition-[border-color,box-shadow]"
|
|
>
|
|
<span
|
|
class="bg-accent text-primary flex size-11 items-center justify-center rounded-md"
|
|
>
|
|
<component
|
|
:is="area.icon"
|
|
class="size-5"
|
|
aria-hidden="true"
|
|
/>
|
|
</span>
|
|
<div class="gap-stack grid">
|
|
<h2 class="text-title">{{ area.title }}</h2>
|
|
<p class="text-muted-foreground">
|
|
{{ area.description }}
|
|
</p>
|
|
</div>
|
|
<span
|
|
class="text-primary mt-auto flex items-center gap-2 pt-2 font-semibold"
|
|
>
|
|
{{ area.action
|
|
}}<ArrowRight class="size-4" aria-hidden="true" />
|
|
</span>
|
|
</Link>
|
|
</section>
|
|
|
|
<section
|
|
v-if="settingsItems.length"
|
|
aria-labelledby="project-settings-title"
|
|
class="gap-column grid"
|
|
>
|
|
<div class="gap-stack grid">
|
|
<h2 id="project-settings-title" class="text-title">
|
|
{{ t('navigation.projectSettings') }}
|
|
</h2>
|
|
<p class="text-muted-foreground">
|
|
{{ t('projects.settingsDescription') }}
|
|
</p>
|
|
</div>
|
|
<ul class="gap-stack grid sm:grid-cols-2 xl:grid-cols-3">
|
|
<li
|
|
v-for="item in settingsItems"
|
|
:key="item.title"
|
|
class="min-w-0"
|
|
>
|
|
<Link
|
|
:href="item.href"
|
|
class="gap-card bg-card hover:bg-accent hover:text-accent-foreground min-h-control flex h-full items-center rounded-md border p-4 transition-colors"
|
|
>
|
|
<component
|
|
:is="item.icon"
|
|
class="text-muted-foreground size-4 shrink-0"
|
|
aria-hidden="true"
|
|
/>
|
|
<span
|
|
class="min-w-0 flex-1 break-words font-medium"
|
|
>{{ item.title }}</span
|
|
>
|
|
<ArrowRight
|
|
class="text-muted-foreground size-4 shrink-0"
|
|
aria-hidden="true"
|
|
/>
|
|
</Link>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<section
|
|
v-if="templateState?.inheritsFromTemplate"
|
|
aria-labelledby="project-template-title"
|
|
class="gap-column bg-card flex min-w-0 flex-col rounded-md border p-5"
|
|
>
|
|
<div class="gap-card flex items-start">
|
|
<Layers3
|
|
class="text-muted-foreground mt-0.5 size-5 shrink-0"
|
|
aria-hidden="true"
|
|
/>
|
|
<div class="gap-stack grid min-w-0">
|
|
<h2
|
|
id="project-template-title"
|
|
class="text-title break-words"
|
|
>
|
|
{{
|
|
t('projects.templateTitle', {
|
|
name: templateState.templateName,
|
|
})
|
|
}}
|
|
</h2>
|
|
<p class="text-muted-foreground">
|
|
{{ t('projects.templateDescription') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="gap-stack flex flex-wrap">
|
|
<Badge
|
|
v-for="area in templateState.inheritedAreas"
|
|
:key="`inherited-${area.value}`"
|
|
variant="outline"
|
|
class="max-w-full whitespace-normal break-words"
|
|
>
|
|
{{ t('projects.inheritedArea', { name: area.label }) }}
|
|
</Badge>
|
|
<Badge
|
|
v-for="area in templateState.overriddenAreas"
|
|
:key="`overridden-${area.value}`"
|
|
variant="secondary"
|
|
class="max-w-full whitespace-normal break-words"
|
|
>
|
|
{{ t('projects.overriddenArea', { name: area.label }) }}
|
|
</Badge>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</AppLayout>
|
|
</template>
|