209 lines
6.9 KiB
Vue
209 lines
6.9 KiB
Vue
<template>
|
|
<AppDrawer v-model="isOpen" :title="isEditing ? 'Modifier un utilisateur' : 'Ajouter un utilisateur'">
|
|
<form class="flex flex-col gap-2" @submit.prevent="handleSubmit">
|
|
<MalioInputText
|
|
v-model="form.username"
|
|
label="Nom d'utilisateur"
|
|
input-class="w-full"
|
|
:error="touched.username && !form.username.trim() ? 'Le nom est requis' : ''"
|
|
@blur="touched.username = true"
|
|
/>
|
|
<MalioInputText
|
|
v-model="form.password"
|
|
label="Mot de passe"
|
|
input-class="w-full"
|
|
type="password"
|
|
:placeholder="isEditing ? 'Laisser vide pour ne pas changer' : ''"
|
|
:error="touched.password && !isEditing && !form.password ? 'Le mot de passe est requis' : ''"
|
|
@blur="touched.password = true"
|
|
/>
|
|
<div class="mt-4">
|
|
<label class="text-sm font-semibold text-neutral-700">Rôles</label>
|
|
<div class="mt-2 flex flex-col gap-2">
|
|
<label
|
|
v-for="role in availableRoles"
|
|
:key="role"
|
|
class="flex items-center gap-2 text-sm text-neutral-700"
|
|
>
|
|
<input
|
|
v-model="form.roles"
|
|
type="checkbox"
|
|
:value="role"
|
|
class="rounded border-neutral-300"
|
|
/>
|
|
{{ role }}
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
<MalioSelect
|
|
v-model="form.clientId"
|
|
label="Client"
|
|
:options="clientOptions"
|
|
placeholder="Aucun client"
|
|
class="w-full"
|
|
@update:model-value="onClientChange"
|
|
/>
|
|
</div>
|
|
|
|
<div v-if="form.clientId !== null" class="mt-2">
|
|
<label class="text-sm font-semibold text-neutral-700">Projets autorisés</label>
|
|
<div class="mt-2 flex flex-col gap-2">
|
|
<label
|
|
v-for="project in filteredProjects"
|
|
:key="project.id"
|
|
class="flex items-center gap-2 text-sm text-neutral-700"
|
|
>
|
|
<input
|
|
v-model="form.allowedProjectIds"
|
|
type="checkbox"
|
|
:value="project.id"
|
|
class="rounded border-neutral-300"
|
|
/>
|
|
{{ project.name }}
|
|
</label>
|
|
<span v-if="filteredProjects.length === 0" class="text-sm text-neutral-400">
|
|
Aucun projet pour ce client.
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 flex justify-end">
|
|
<button
|
|
type="submit"
|
|
class="rounded-md bg-primary-500 px-6 py-2 text-sm font-semibold text-white hover:bg-secondary-500 disabled:cursor-not-allowed disabled:opacity-50"
|
|
:disabled="isSubmitting"
|
|
>
|
|
Enregistrer
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</AppDrawer>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import type { UserData, UserWrite } from '~/services/dto/user-data'
|
|
import { useUserService } from '~/services/users'
|
|
import { useClientService } from '~/services/clients'
|
|
import { useProjectService } from '~/services/projects'
|
|
import type { Client } from '~/services/dto/client'
|
|
import type { Project } from '~/services/dto/project'
|
|
|
|
const props = defineProps<{
|
|
modelValue: boolean
|
|
item: UserData | 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 availableRoles = ['ROLE_ADMIN', 'ROLE_USER', 'ROLE_CLIENT']
|
|
|
|
const isEditing = computed(() => !!props.item)
|
|
const isSubmitting = ref(false)
|
|
|
|
const clients = ref<Client[]>([])
|
|
const allProjects = ref<Project[]>([])
|
|
|
|
const clientOptions = computed(() => [
|
|
{ label: 'Aucun client', value: null as number | null },
|
|
...clients.value.map((c) => ({ label: c.name, value: c.id as number | null })),
|
|
])
|
|
|
|
const filteredProjects = computed(() => {
|
|
if (form.clientId === null) return []
|
|
return allProjects.value.filter(
|
|
(p) => p.client !== null && p.client.id === form.clientId,
|
|
)
|
|
})
|
|
|
|
const form = reactive({
|
|
username: '',
|
|
password: '',
|
|
roles: [] as string[],
|
|
clientId: null as number | null,
|
|
allowedProjectIds: [] as number[],
|
|
})
|
|
|
|
const touched = reactive({
|
|
username: false,
|
|
password: false,
|
|
})
|
|
|
|
function onClientChange(value: number | null) {
|
|
form.clientId = value
|
|
form.allowedProjectIds = []
|
|
if (value !== null && !form.roles.includes('ROLE_CLIENT')) {
|
|
form.roles = [...form.roles.filter((r) => r !== 'ROLE_USER'), 'ROLE_CLIENT']
|
|
}
|
|
}
|
|
|
|
watch(() => props.modelValue, async (open) => {
|
|
if (open) {
|
|
if (props.item) {
|
|
form.username = props.item.username ?? ''
|
|
form.password = ''
|
|
form.roles = [...props.item.roles]
|
|
form.clientId = props.item.client?.id ?? null
|
|
form.allowedProjectIds = props.item.allowedProjects?.map((p) => p.id) ?? []
|
|
} else {
|
|
form.username = ''
|
|
form.password = ''
|
|
form.roles = ['ROLE_USER']
|
|
form.clientId = null
|
|
form.allowedProjectIds = []
|
|
}
|
|
touched.username = false
|
|
touched.password = false
|
|
|
|
const [loadedClients, loadedProjects] = await Promise.all([
|
|
useClientService().getAll(),
|
|
useProjectService().getAll({ archived: false }),
|
|
])
|
|
clients.value = loadedClients
|
|
allProjects.value = loadedProjects
|
|
}
|
|
})
|
|
|
|
const { create, update } = useUserService()
|
|
|
|
async function handleSubmit() {
|
|
touched.username = true
|
|
touched.password = true
|
|
if (!form.username.trim()) return
|
|
if (!isEditing.value && !form.password) return
|
|
|
|
isSubmitting.value = true
|
|
try {
|
|
const payload: UserWrite = {
|
|
username: form.username.trim(),
|
|
roles: form.roles,
|
|
client: form.clientId !== null ? `/api/clients/${form.clientId}` : null,
|
|
allowedProjects: form.allowedProjectIds.map((id) => `/api/projects/${id}`),
|
|
}
|
|
if (form.password) {
|
|
payload.password = form.password
|
|
}
|
|
|
|
if (isEditing.value && props.item) {
|
|
await update(props.item.id, payload)
|
|
} else {
|
|
await create(payload)
|
|
}
|
|
|
|
emit('saved')
|
|
isOpen.value = false
|
|
} finally {
|
|
isSubmitting.value = false
|
|
}
|
|
}
|
|
</script>
|