Add template switch and reset flows for existing projects
This commit is contained in:
@@ -43,14 +43,70 @@ const breadcrumbs: BreadcrumbItem[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const switchTemplateForm = useForm({
|
||||
confirmation_key: '',
|
||||
source_template_id: props.sourceTemplates.find(
|
||||
(template) => template.id !== props.project.project_template_id,
|
||||
)?.id,
|
||||
});
|
||||
|
||||
const resetTemplateForm = useForm({
|
||||
confirmation_key: '',
|
||||
});
|
||||
|
||||
const deleteForm = useForm({
|
||||
confirmation_key: '',
|
||||
});
|
||||
|
||||
const availableSwitchTemplates = computed(() =>
|
||||
props.sourceTemplates.filter(
|
||||
(template) => template.id !== props.project.project_template_id,
|
||||
),
|
||||
);
|
||||
|
||||
const currentTemplate = computed(() => props.project.project_template ?? null);
|
||||
|
||||
const isSwitchConfirmationValid = computed(
|
||||
() => switchTemplateForm.confirmation_key === props.project.key,
|
||||
);
|
||||
|
||||
const canSwitchTemplate = computed(
|
||||
() =>
|
||||
isSwitchConfirmationValid.value &&
|
||||
switchTemplateForm.source_template_id !== undefined,
|
||||
);
|
||||
|
||||
const isResetConfirmationValid = computed(
|
||||
() => resetTemplateForm.confirmation_key === props.project.key,
|
||||
);
|
||||
|
||||
const isDeleteConfirmationValid = computed(
|
||||
() => deleteForm.confirmation_key === props.project.key,
|
||||
);
|
||||
|
||||
const submitSwitchTemplate = (): void => {
|
||||
if (!canSwitchTemplate.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
switchTemplateForm.submit(
|
||||
ProjectController.switchTemplate(props.project.id),
|
||||
{
|
||||
preserveScroll: true,
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const submitResetTemplate = (): void => {
|
||||
if (!isResetConfirmationValid.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
resetTemplateForm.submit(ProjectController.resetTemplate(props.project.id), {
|
||||
preserveScroll: true,
|
||||
});
|
||||
};
|
||||
|
||||
const submitDelete = (): void => {
|
||||
if (!isDeleteConfirmationValid.value) {
|
||||
return;
|
||||
@@ -152,6 +208,139 @@ const submitDelete = (): void => {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Template baseline</CardTitle>
|
||||
<CardDescription>
|
||||
Switch this project to another published template or reset it back to the currently linked template baseline. Both actions can overwrite local configuration.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent class="space-y-6">
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div class="border-border/70 rounded-xl border p-4">
|
||||
<p class="text-muted-foreground text-xs font-semibold uppercase tracking-[0.18em]">
|
||||
Current linked template
|
||||
</p>
|
||||
<p class="text-foreground mt-2 text-sm font-semibold">
|
||||
{{ currentTemplate?.name ?? 'No linked template' }}
|
||||
</p>
|
||||
<p class="text-muted-foreground mt-2 text-sm">
|
||||
{{
|
||||
templateState?.inheritsFromTemplate
|
||||
? 'Inherited and overridden areas stay visible after switching or resetting.'
|
||||
: 'This project currently behaves like a standalone baseline.'
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="border-border/70 rounded-xl border p-4">
|
||||
<p class="text-muted-foreground text-xs font-semibold uppercase tracking-[0.18em]">
|
||||
Warning
|
||||
</p>
|
||||
<p class="text-foreground mt-2 text-sm font-medium">
|
||||
Template changes can replace board workflow, ticket metadata, repositories, and notifications for copied areas.
|
||||
</p>
|
||||
<p class="text-muted-foreground mt-2 text-sm">
|
||||
Confirm each action with the exact project key to avoid accidental overwrites.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-6 xl:grid-cols-2">
|
||||
<form class="space-y-4 rounded-xl border p-4" @submit.prevent="submitSwitchTemplate">
|
||||
<div class="space-y-1">
|
||||
<h3 class="text-sm font-semibold">Switch to another template</h3>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
Applying a different template replaces the copied areas with that template's current baseline.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="source_template_id">Published template</Label>
|
||||
<select
|
||||
id="source_template_id"
|
||||
v-model="switchTemplateForm.source_template_id"
|
||||
:disabled="switchTemplateForm.processing || availableSwitchTemplates.length === 0"
|
||||
class="border-input bg-background shadow-xs focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border px-3 py-2 text-sm outline-none transition-[color,box-shadow] focus-visible:ring-[3px]"
|
||||
>
|
||||
<option :value="undefined" disabled>
|
||||
Select a template
|
||||
</option>
|
||||
<option
|
||||
v-for="template in availableSwitchTemplates"
|
||||
:key="template.id"
|
||||
:value="template.id"
|
||||
>
|
||||
{{ template.name }}
|
||||
</option>
|
||||
</select>
|
||||
<InputError :message="switchTemplateForm.errors.source_template_id" />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="switch_confirmation_key">Confirmation key</Label>
|
||||
<Input
|
||||
id="switch_confirmation_key"
|
||||
v-model="switchTemplateForm.confirmation_key"
|
||||
:disabled="switchTemplateForm.processing"
|
||||
placeholder="Type the project key exactly"
|
||||
/>
|
||||
<InputError :message="switchTemplateForm.errors.confirmation_key" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<p class="text-muted-foreground text-sm">
|
||||
Existing project-specific configuration in copied areas may be replaced or removed.
|
||||
</p>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
:disabled="switchTemplateForm.processing || !canSwitchTemplate"
|
||||
>
|
||||
Switch template
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="space-y-4 rounded-xl border p-4" @submit.prevent="submitResetTemplate">
|
||||
<div class="space-y-1">
|
||||
<h3 class="text-sm font-semibold">Reset to current template baseline</h3>
|
||||
<p class="text-muted-foreground text-sm">
|
||||
Re-apply the currently linked template and clear local divergence inside copied areas.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="reset_confirmation_key">Confirmation key</Label>
|
||||
<Input
|
||||
id="reset_confirmation_key"
|
||||
v-model="resetTemplateForm.confirmation_key"
|
||||
:disabled="resetTemplateForm.processing || !currentTemplate"
|
||||
placeholder="Type the project key exactly"
|
||||
/>
|
||||
<InputError :message="resetTemplateForm.errors.confirmation_key" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<p class="text-muted-foreground text-sm">
|
||||
This restores the current template baseline for copied areas and rewinds project-level overrides there.
|
||||
</p>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
:disabled="resetTemplateForm.processing || !isResetConfirmationValid || !currentTemplate"
|
||||
>
|
||||
Reset to baseline
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card
|
||||
class="border-red-200/80 bg-red-50/60 dark:border-red-900/60 dark:bg-red-950/20"
|
||||
>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import type { ProjectTemplateModel } from '@/types/models/project-template-model';
|
||||
|
||||
export type ProjectModel = {
|
||||
id: number;
|
||||
name: string;
|
||||
key: string;
|
||||
project_template_id?: number | null;
|
||||
project_template?: ProjectTemplateModel | null;
|
||||
show_author: boolean;
|
||||
show_story_points: boolean;
|
||||
created_at: string;
|
||||
|
||||
@@ -9,6 +9,7 @@ export type ProjectTemplateModel = {
|
||||
name: string;
|
||||
slug: string;
|
||||
copy_areas: string[];
|
||||
configuration_snapshot: Record<string, unknown>;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
};
|
||||
|
||||
@@ -69,8 +69,11 @@ export type ProjectTemplateStateArea = {
|
||||
export type ProjectTemplateState = {
|
||||
effectiveConfiguration: Record<string, unknown>;
|
||||
inheritedAreas: ProjectTemplateStateArea[];
|
||||
inheritedConfiguration?: Record<string, unknown>;
|
||||
inheritsFromTemplate: boolean;
|
||||
overriddenAreas: ProjectTemplateStateArea[];
|
||||
overriddenConfiguration?: Record<string, unknown>;
|
||||
templateConfiguration?: Record<string, unknown>;
|
||||
templateId: number;
|
||||
templateName: string;
|
||||
templateSlug: string;
|
||||
@@ -117,7 +120,9 @@ export type ProjectBoardSettingsPageProps = {
|
||||
|
||||
export type ProjectGeneralSettingsPageProps = {
|
||||
project: ProjectModel;
|
||||
sourceTemplates: ProjectTemplateModel[];
|
||||
status?: string | null;
|
||||
templateState?: ProjectTemplateState | null;
|
||||
};
|
||||
|
||||
export type ProjectBoardPageProps = {
|
||||
|
||||
Reference in New Issue
Block a user