262 lines
8.7 KiB
Vue
262 lines
8.7 KiB
Vue
<template>
|
|
<MalioDrawer v-model="isOpen" :title="isEditing ? $t('workflows.editWorkflow') : $t('workflows.addWorkflow')">
|
|
<form class="flex flex-col gap-4" @submit.prevent="handleSubmit">
|
|
<MalioInputText
|
|
v-model="form.name"
|
|
:label="$t('workflows.name')"
|
|
input-class="w-full"
|
|
:error="touched.name && !form.name.trim() ? $t('workflows.name') + ' requis' : ''"
|
|
@blur="touched.name = true"
|
|
/>
|
|
|
|
<div class="flex items-center gap-2">
|
|
<input
|
|
id="isDefault"
|
|
v-model="form.isDefault"
|
|
type="checkbox"
|
|
class="h-4 w-4 rounded border-neutral-300 text-primary-500 focus:ring-primary-500"
|
|
/>
|
|
<label for="isDefault" class="text-sm font-medium text-neutral-700">
|
|
{{ $t('workflows.isDefault') }}
|
|
</label>
|
|
</div>
|
|
|
|
<div class="mt-2">
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-sm font-bold text-neutral-900">{{ $t('workflows.statuses') }}</h3>
|
|
<MalioButton
|
|
type="button"
|
|
icon-name="mdi:plus"
|
|
icon-position="left"
|
|
button-class="w-auto px-3 py-1 text-xs"
|
|
:label="$t('workflows.addStatus')"
|
|
@click="addStatus"
|
|
/>
|
|
</div>
|
|
|
|
<div class="mt-3 flex flex-col gap-3">
|
|
<div
|
|
v-for="(s, idx) in form.statuses"
|
|
:key="idx"
|
|
class="rounded border border-neutral-200 p-3"
|
|
>
|
|
<div class="flex items-end gap-2">
|
|
<MalioInputText
|
|
v-model="s.label"
|
|
label="Libellé"
|
|
input-class="w-full"
|
|
/>
|
|
<select
|
|
v-model="s.category"
|
|
class="h-10 rounded border border-neutral-300 px-2 text-sm"
|
|
aria-label="Catégorie"
|
|
>
|
|
<option v-for="c in categoryOptions" :key="c.value" :value="c.value">
|
|
{{ c.label }}
|
|
</option>
|
|
</select>
|
|
<button
|
|
type="button"
|
|
class="h-10 px-2 text-red-600 hover:text-red-800"
|
|
aria-label="Supprimer"
|
|
@click="removeStatus(idx)"
|
|
>
|
|
<Icon name="mdi:delete" size="20" />
|
|
</button>
|
|
</div>
|
|
<div class="mt-2 flex items-center gap-3">
|
|
<ColorPicker v-model="s.color" />
|
|
<label class="ml-auto flex items-center gap-1 text-xs text-neutral-700">
|
|
<input v-model="s.isFinal" type="checkbox" class="h-3 w-3" />
|
|
{{ $t('archive.statusFinal') }}
|
|
</label>
|
|
<label class="flex flex-col text-xs text-neutral-700">
|
|
Position
|
|
<input
|
|
v-model.number="s.position"
|
|
type="number"
|
|
class="mt-1 h-9 w-16 rounded border border-neutral-300 px-2 text-sm"
|
|
/>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 flex justify-end">
|
|
<MalioButton
|
|
label="Enregistrer"
|
|
button-class="w-auto px-6"
|
|
:disabled="isSubmitting"
|
|
@click="handleSubmit"
|
|
/>
|
|
</div>
|
|
</form>
|
|
</MalioDrawer>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { Workflow, StatusCategory } from '~/services/dto/workflow'
|
|
import type { TaskStatusWrite } from '~/services/dto/task-status'
|
|
import { useWorkflowService } from '~/services/workflows'
|
|
import { useTaskStatusService } from '~/services/task-statuses'
|
|
|
|
const { t } = useI18n()
|
|
|
|
const props = defineProps<{
|
|
modelValue: boolean
|
|
item: Workflow | null
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'update:modelValue', value: boolean): void
|
|
(e: 'saved'): void
|
|
}>()
|
|
|
|
const isOpen = computed({
|
|
get: () => props.modelValue,
|
|
set: v => emit('update:modelValue', v),
|
|
})
|
|
|
|
const isEditing = computed(() => !!props.item)
|
|
const isSubmitting = ref(false)
|
|
|
|
type StatusForm = {
|
|
id?: number
|
|
label: string
|
|
color: string
|
|
position: number
|
|
isFinal: boolean
|
|
category: StatusCategory
|
|
}
|
|
|
|
const form = reactive<{
|
|
name: string
|
|
isDefault: boolean
|
|
statuses: StatusForm[]
|
|
}>({
|
|
name: '',
|
|
isDefault: false,
|
|
statuses: [],
|
|
})
|
|
|
|
const touched = reactive({ name: false })
|
|
|
|
const categoryOptions: { value: StatusCategory, label: string }[] = [
|
|
{ value: 'todo', label: t('workflows.categories.todo') },
|
|
{ value: 'in_progress', label: t('workflows.categories.in_progress') },
|
|
{ value: 'blocked', label: t('workflows.categories.blocked') },
|
|
{ value: 'review', label: t('workflows.categories.review') },
|
|
{ value: 'done', label: t('workflows.categories.done') },
|
|
]
|
|
|
|
watch(() => props.modelValue, (open) => {
|
|
if (!open) return
|
|
if (props.item) {
|
|
form.name = props.item.name
|
|
form.isDefault = props.item.isDefault
|
|
form.statuses = props.item.statuses.map(s => ({
|
|
id: s.id,
|
|
label: s.label,
|
|
color: s.color,
|
|
position: s.position,
|
|
isFinal: s.isFinal,
|
|
category: s.category,
|
|
}))
|
|
} else {
|
|
form.name = ''
|
|
form.isDefault = false
|
|
form.statuses = []
|
|
}
|
|
touched.name = false
|
|
})
|
|
|
|
function addStatus() {
|
|
form.statuses.push({
|
|
label: '',
|
|
color: '#222783',
|
|
position: form.statuses.length,
|
|
isFinal: false,
|
|
category: 'todo',
|
|
})
|
|
}
|
|
|
|
function removeStatus(idx: number) {
|
|
form.statuses.splice(idx, 1)
|
|
}
|
|
|
|
const workflowService = useWorkflowService()
|
|
const statusService = useTaskStatusService()
|
|
|
|
async function handleSubmit() {
|
|
touched.name = true
|
|
if (!form.name.trim()) return
|
|
|
|
isSubmitting.value = true
|
|
try {
|
|
if (isEditing.value && props.item) {
|
|
await workflowService.update(props.item.id, {
|
|
name: form.name.trim(),
|
|
isDefault: form.isDefault,
|
|
position: props.item.position,
|
|
})
|
|
await syncStatuses(props.item)
|
|
} else {
|
|
const created = await workflowService.create({
|
|
name: form.name.trim(),
|
|
isDefault: form.isDefault,
|
|
position: 0,
|
|
})
|
|
for (const s of form.statuses) {
|
|
const payload: TaskStatusWrite = {
|
|
label: s.label,
|
|
color: s.color,
|
|
position: s.position,
|
|
isFinal: s.isFinal,
|
|
category: s.category,
|
|
workflow: `/api/workflows/${created.id}`,
|
|
}
|
|
await statusService.create(payload)
|
|
}
|
|
}
|
|
emit('saved')
|
|
isOpen.value = false
|
|
} finally {
|
|
isSubmitting.value = false
|
|
}
|
|
}
|
|
|
|
async function syncStatuses(workflow: Workflow) {
|
|
const existingIds = new Set(workflow.statuses.map(s => s.id))
|
|
const keptIds = new Set<number>()
|
|
|
|
for (const s of form.statuses) {
|
|
if (s.id) {
|
|
keptIds.add(s.id)
|
|
await statusService.update(s.id, {
|
|
label: s.label,
|
|
color: s.color,
|
|
position: s.position,
|
|
isFinal: s.isFinal,
|
|
category: s.category,
|
|
})
|
|
} else {
|
|
await statusService.create({
|
|
label: s.label,
|
|
color: s.color,
|
|
position: s.position,
|
|
isFinal: s.isFinal,
|
|
category: s.category,
|
|
workflow: `/api/workflows/${workflow.id}`,
|
|
})
|
|
}
|
|
}
|
|
|
|
for (const id of existingIds) {
|
|
if (id && !keptIds.has(id)) {
|
|
await statusService.remove(id)
|
|
}
|
|
}
|
|
}
|
|
</script>
|