264 lines
9.3 KiB
Vue
264 lines
9.3 KiB
Vue
<script setup lang="ts">
|
|
import { Head, Link } from '@inertiajs/vue3';
|
|
import {
|
|
BellRing,
|
|
GitBranch,
|
|
KanbanSquare,
|
|
LayoutTemplate,
|
|
Settings2,
|
|
ShieldCheck,
|
|
Tags,
|
|
} from 'lucide-vue-next';
|
|
import { computed } from 'vue';
|
|
import ProjectPageHeader from '@/components/projects/ProjectPageHeader.vue';
|
|
import ProjectTabs from '@/components/projects/ProjectTabs.vue';
|
|
import { Button } from '@/components/ui/button';
|
|
import AppLayout from '@/layouts/AppLayout.vue';
|
|
import { index as projectsIndex } from '@/routes/projects';
|
|
import { index as boardIndex } from '@/routes/projects/board';
|
|
import { index as settingsIndex } from '@/routes/projects/settings';
|
|
import { create as projectTemplateCreate } from '@/routes/projects/templates';
|
|
import type {
|
|
BreadcrumbItem,
|
|
EmbeddedProjectSettingsSectionProps,
|
|
ProjectAccessPageProps,
|
|
ProjectBoardSettingsPageProps,
|
|
ProjectGeneralSettingsPageProps,
|
|
ProjectNotificationSettingsPageProps,
|
|
ProjectRepositorySettingsPageProps,
|
|
ProjectSettingsPageProps,
|
|
ProjectSettingsSection,
|
|
ProjectTicketMetadataSettingsPageProps,
|
|
} from '@/types';
|
|
import AccessSection from './Access.vue';
|
|
import BoardSection from './settings/Board.vue';
|
|
import GeneralSection from './settings/General.vue';
|
|
import MetadataSection from './settings/Metadata.vue';
|
|
import NotificationsSection from './settings/Notifications.vue';
|
|
import RepositoriesSection from './settings/Repositories.vue';
|
|
|
|
const props = defineProps<ProjectSettingsPageProps>();
|
|
|
|
const breadcrumbs: BreadcrumbItem[] = [
|
|
{
|
|
title: 'Projects',
|
|
href: projectsIndex(),
|
|
},
|
|
{
|
|
title: props.project.name,
|
|
href: boardIndex(props.project.id),
|
|
},
|
|
{
|
|
title: 'Settings',
|
|
href: settingsIndex(props.project.id),
|
|
},
|
|
];
|
|
|
|
const settingsSections = [
|
|
{
|
|
description: 'Project identity, templates, and deletion',
|
|
icon: Settings2,
|
|
label: 'General',
|
|
value: 'general' as const,
|
|
},
|
|
{
|
|
description: 'Columns, statuses, and transitions',
|
|
icon: KanbanSquare,
|
|
label: 'Board',
|
|
value: 'board' as const,
|
|
},
|
|
{
|
|
description: 'Types, priorities, fields, and links',
|
|
icon: Tags,
|
|
label: 'Metadata',
|
|
value: 'metadata' as const,
|
|
},
|
|
{
|
|
description: 'Linked source repositories',
|
|
icon: GitBranch,
|
|
label: 'Repositories',
|
|
value: 'repositories' as const,
|
|
},
|
|
{
|
|
description: 'Webhook and Discord automation',
|
|
icon: BellRing,
|
|
label: 'Notifications',
|
|
value: 'notifications' as const,
|
|
},
|
|
{
|
|
description: 'User and team permissions',
|
|
icon: ShieldCheck,
|
|
label: 'Access',
|
|
value: 'access' as const,
|
|
},
|
|
];
|
|
|
|
const activeSectionDetails = computed(
|
|
() =>
|
|
settingsSections.find(
|
|
(section) => section.value === props.activeSection,
|
|
) ?? settingsSections[0],
|
|
);
|
|
|
|
const accessSectionData = computed(
|
|
() =>
|
|
props.sectionData as EmbeddedProjectSettingsSectionProps<ProjectAccessPageProps>,
|
|
);
|
|
const boardSectionData = computed(
|
|
() =>
|
|
props.sectionData as EmbeddedProjectSettingsSectionProps<ProjectBoardSettingsPageProps>,
|
|
);
|
|
const generalSectionData = computed(
|
|
() =>
|
|
props.sectionData as EmbeddedProjectSettingsSectionProps<ProjectGeneralSettingsPageProps>,
|
|
);
|
|
const metadataSectionData = computed(
|
|
() =>
|
|
props.sectionData as EmbeddedProjectSettingsSectionProps<ProjectTicketMetadataSettingsPageProps>,
|
|
);
|
|
const notificationsSectionData = computed(
|
|
() =>
|
|
props.sectionData as EmbeddedProjectSettingsSectionProps<ProjectNotificationSettingsPageProps>,
|
|
);
|
|
const repositoriesSectionData = computed(
|
|
() =>
|
|
props.sectionData as EmbeddedProjectSettingsSectionProps<ProjectRepositorySettingsPageProps>,
|
|
);
|
|
|
|
const sectionHref = (section: ProjectSettingsSection) =>
|
|
settingsIndex(props.project.id, { query: { section } });
|
|
</script>
|
|
|
|
<template>
|
|
<AppLayout :breadcrumbs="breadcrumbs" :project="project">
|
|
<Head :title="`${project.name} settings`" />
|
|
|
|
<div class="app-page max-w-none gap-5">
|
|
<ProjectPageHeader
|
|
:project="project"
|
|
context="Project settings"
|
|
description="Project configuration and access controls."
|
|
>
|
|
<Button
|
|
v-if="
|
|
activeSection === 'general' &&
|
|
$page.props.auth.permissions.projectCreate
|
|
"
|
|
as-child
|
|
size="sm"
|
|
variant="outline"
|
|
>
|
|
<Link :href="projectTemplateCreate(project.id)">
|
|
<LayoutTemplate class="size-4" />
|
|
Save as template
|
|
</Link>
|
|
</Button>
|
|
</ProjectPageHeader>
|
|
|
|
<div class="overflow-x-auto">
|
|
<ProjectTabs
|
|
active="settings"
|
|
:can-manage-project="true"
|
|
:project="project"
|
|
/>
|
|
</div>
|
|
|
|
<div
|
|
v-if="status"
|
|
:class="
|
|
error
|
|
? 'rounded border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-800 dark:border-red-900 dark:bg-red-950/40 dark:text-red-200'
|
|
: 'status-success'
|
|
"
|
|
>
|
|
{{ status }}
|
|
</div>
|
|
|
|
<div class="grid min-w-0 gap-5 xl:grid-cols-[15rem_minmax(0,1fr)]">
|
|
<nav
|
|
aria-label="Project settings"
|
|
class="border-outline-variant/70 bg-surface-container-lowest flex gap-1 overflow-x-auto rounded-md border p-1.5 xl:sticky xl:top-20 xl:flex-col xl:self-start xl:overflow-visible"
|
|
>
|
|
<Link
|
|
v-for="section in settingsSections"
|
|
:key="section.value"
|
|
:href="sectionHref(section.value)"
|
|
preserve-scroll
|
|
:class="[
|
|
'flex min-w-max items-center gap-3 rounded px-3 py-2.5 text-left text-sm transition-colors xl:min-w-0',
|
|
activeSection === section.value
|
|
? 'bg-primary/10 text-primary font-semibold'
|
|
: 'text-muted-foreground hover:bg-muted hover:text-foreground',
|
|
]"
|
|
>
|
|
<component :is="section.icon" class="size-4 shrink-0" />
|
|
<span class="min-w-0">
|
|
<span class="block">{{ section.label }}</span>
|
|
<span
|
|
class="mt-0.5 hidden text-xs font-normal opacity-75 xl:block"
|
|
>
|
|
{{ section.description }}
|
|
</span>
|
|
</span>
|
|
</Link>
|
|
</nav>
|
|
|
|
<section class="min-w-0 space-y-5">
|
|
<div class="border-outline-variant/70 border-b pb-4">
|
|
<h2 class="text-lg font-bold tracking-tight">
|
|
{{ activeSectionDetails.label }}
|
|
</h2>
|
|
<p class="text-muted-foreground mt-1 text-sm">
|
|
{{ activeSectionDetails.description }}
|
|
</p>
|
|
</div>
|
|
|
|
<GeneralSection
|
|
v-if="activeSection === 'general'"
|
|
v-bind="generalSectionData"
|
|
:project="project"
|
|
:status="status"
|
|
embedded
|
|
/>
|
|
<BoardSection
|
|
v-else-if="activeSection === 'board'"
|
|
v-bind="boardSectionData"
|
|
:project="project"
|
|
:status="status"
|
|
embedded
|
|
/>
|
|
<MetadataSection
|
|
v-else-if="activeSection === 'metadata'"
|
|
v-bind="metadataSectionData"
|
|
:project="project"
|
|
:status="status"
|
|
:error="error"
|
|
embedded
|
|
/>
|
|
<RepositoriesSection
|
|
v-else-if="activeSection === 'repositories'"
|
|
v-bind="repositoriesSectionData"
|
|
:project="project"
|
|
:status="status"
|
|
embedded
|
|
/>
|
|
<NotificationsSection
|
|
v-else-if="activeSection === 'notifications'"
|
|
v-bind="notificationsSectionData"
|
|
:project="project"
|
|
:status="status"
|
|
embedded
|
|
/>
|
|
<AccessSection
|
|
v-else
|
|
v-bind="accessSectionData"
|
|
:project="project"
|
|
:status="status"
|
|
embedded
|
|
/>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</AppLayout>
|
|
</template>
|