Add editable project template flow
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
<script setup lang="ts">
|
||||
import { Head, Link, useForm } from '@inertiajs/vue3';
|
||||
import { computed } from 'vue';
|
||||
import Heading from '@/components/Heading.vue';
|
||||
import InputError from '@/components/InputError.vue';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import AppLayout from '@/layouts/AppLayout.vue';
|
||||
import { index as projectTemplatesIndex } from '@/routes/project-templates';
|
||||
import type { BreadcrumbItem, ProjectTemplatesEditPageProps } from '@/types';
|
||||
|
||||
const props = defineProps<ProjectTemplatesEditPageProps>();
|
||||
|
||||
const breadcrumbs: BreadcrumbItem[] = [
|
||||
{
|
||||
title: 'Project templates',
|
||||
href: projectTemplatesIndex(),
|
||||
},
|
||||
{
|
||||
title: props.template.name,
|
||||
href: `/project-templates/${props.template.id}/edit`,
|
||||
},
|
||||
];
|
||||
|
||||
const form = useForm({
|
||||
confirm_linked_projects_update: false,
|
||||
copy_areas: [...props.template.copy_areas],
|
||||
name: props.template.name,
|
||||
});
|
||||
|
||||
const copyAreasError = computed(() => {
|
||||
return (
|
||||
form.errors.copy_areas ??
|
||||
Object.entries(form.errors).find(([key]) => key.startsWith('copy_areas.'))?.[1] ??
|
||||
''
|
||||
);
|
||||
});
|
||||
|
||||
const linkedProjectCount = computed(() => props.template.linked_project_count ?? 0);
|
||||
|
||||
const submit = (): void => {
|
||||
form.patch(`/project-templates/${props.template.id}`, {
|
||||
preserveScroll: true,
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AppLayout :breadcrumbs="breadcrumbs">
|
||||
<Head :title="`Edit ${template.name}`" />
|
||||
|
||||
<div class="mx-auto flex w-full max-w-3xl flex-1 flex-col gap-6 p-4">
|
||||
<div class="space-y-3">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<Badge variant="outline" class="px-3 py-1">{{ template.slug }}</Badge>
|
||||
<Badge v-if="linkedProjectCount > 0" variant="secondary">
|
||||
{{ linkedProjectCount }} linked {{ linkedProjectCount === 1 ? 'project' : 'projects' }}
|
||||
</Badge>
|
||||
</div>
|
||||
<Heading
|
||||
:title="`Edit ${template.name}`"
|
||||
description="Refresh the template baseline from its source project while keeping immutable identifiers protected."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Template details</CardTitle>
|
||||
<CardDescription>
|
||||
<template v-if="template.source_project">
|
||||
This template baseline is rebuilt from {{ template.source_project.name }} ({{ template.source_project.key }}) when you save.
|
||||
</template>
|
||||
<template v-else>
|
||||
This template baseline is rebuilt from its source project when you save.
|
||||
</template>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<form class="space-y-6" @submit.prevent="submit">
|
||||
<div class="grid gap-2">
|
||||
<Label for="name">Template name</Label>
|
||||
<Input id="name" v-model="form.name" required placeholder="Foundation template" />
|
||||
<InputError :message="form.errors.name" />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="slug">Template slug</Label>
|
||||
<Input id="slug" :model-value="template.slug" disabled />
|
||||
<p class="text-muted-foreground text-sm">
|
||||
The slug stays locked so existing template references remain stable.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3">
|
||||
<div>
|
||||
<Label>Configuration groups</Label>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
Saving rebuilds the selected groups from the current source-project configuration.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-3">
|
||||
<Label
|
||||
v-for="copyAreaOption in copyAreaOptions"
|
||||
:key="copyAreaOption.value"
|
||||
class="flex cursor-pointer items-start gap-3 rounded-xl border p-4"
|
||||
>
|
||||
<input
|
||||
v-model="form.copy_areas"
|
||||
type="checkbox"
|
||||
:value="copyAreaOption.value"
|
||||
class="mt-1"
|
||||
/>
|
||||
<div class="space-y-1">
|
||||
<div class="font-medium">{{ copyAreaOption.label }}</div>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
{{ copyAreaOption.description }}
|
||||
</p>
|
||||
</div>
|
||||
</Label>
|
||||
</div>
|
||||
<InputError :message="copyAreasError" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="linkedProjectCount > 0"
|
||||
class="space-y-3 rounded-xl border border-amber-300 bg-amber-50 p-4 text-amber-950 dark:border-amber-900/70 dark:bg-amber-950/40 dark:text-amber-100"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<div class="font-medium">Warning</div>
|
||||
<p class="text-sm">
|
||||
Saving this template updates the baseline for {{ linkedProjectCount }} linked {{ linkedProjectCount === 1 ? 'project' : 'projects' }}.
|
||||
Inherited areas will follow the new template version, while existing overrides stay untouched.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Label class="flex cursor-pointer items-start gap-3">
|
||||
<input v-model="form.confirm_linked_projects_update" type="checkbox" class="mt-1" />
|
||||
<span class="text-sm">
|
||||
I understand that linked projects inheriting from this template will be affected.
|
||||
</span>
|
||||
</Label>
|
||||
|
||||
<InputError :message="form.errors.confirm_linked_projects_update" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<Button type="submit" :disabled="form.processing">
|
||||
Save template changes
|
||||
</Button>
|
||||
|
||||
<Button as-child variant="outline">
|
||||
<Link :href="projectTemplatesIndex()">Cancel</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</AppLayout>
|
||||
</template>
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
CardTitle,
|
||||
} from '@/components/ui/card';
|
||||
import AppLayout from '@/layouts/AppLayout.vue';
|
||||
import { edit as projectTemplatesEdit } from '@/routes/project-templates';
|
||||
import { create as projectsCreate, index as projectsIndex } from '@/routes/projects/index';
|
||||
import type { BreadcrumbItem, ProjectTemplatesIndexPageProps } from '@/types';
|
||||
|
||||
@@ -69,6 +70,9 @@ const removeTemplate = (templateId: number): void => {
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<CardTitle>{{ template.name }}</CardTitle>
|
||||
<Badge variant="secondary">{{ template.slug }}</Badge>
|
||||
<Badge v-if="template.linked_project_count" variant="outline">
|
||||
{{ template.linked_project_count }} linked {{ template.linked_project_count === 1 ? 'project' : 'projects' }}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardDescription>
|
||||
<template v-if="template.source_project">
|
||||
@@ -91,6 +95,12 @@ const removeTemplate = (templateId: number): void => {
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<Button as-child variant="secondary">
|
||||
<Link :href="projectTemplatesEdit(template.id)">
|
||||
Edit
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
<Button as-child variant="outline">
|
||||
<Link :href="projectsCreate({ query: { template: template.id } })">
|
||||
Use template
|
||||
|
||||
@@ -10,6 +10,7 @@ export type ProjectTemplateModel = {
|
||||
slug: string;
|
||||
copy_areas: string[];
|
||||
configuration_snapshot: Record<string, unknown>;
|
||||
linked_project_count?: number;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
};
|
||||
|
||||
@@ -61,6 +61,11 @@ export type ProjectTemplatesCreatePageProps = {
|
||||
project: ProjectModel;
|
||||
};
|
||||
|
||||
export type ProjectTemplatesEditPageProps = {
|
||||
copyAreaOptions: ProjectDuplicateCopyAreaOption[];
|
||||
template: ProjectTemplateModel;
|
||||
};
|
||||
|
||||
export type ProjectTemplateStateArea = {
|
||||
label: string;
|
||||
value: string;
|
||||
|
||||
Reference in New Issue
Block a user