Files
vigilcare-clinical/vigilcare-dashboard/src/components/admin/UserFormModal.vue
T

149 lines
5.2 KiB
Vue

<script setup>
import { reactive, computed, watch } from 'vue'
import Modal from '@/components/ui/Modal.vue'
import Button from '@/components/ui/Button.vue'
import {
emptyUserForm,
ROLE_OPTIONS,
validateUserForm,
} from '@/composables/userForm'
const props = defineProps({
open: { type: Boolean, default: false },
title: { type: String, default: 'Edit User' },
mode: { type: String, default: 'edit', validator: (v) => ['create', 'edit'].includes(v) },
initialValues: { type: Object, default: () => emptyUserForm() },
submitting: { type: Boolean, default: false },
error: { type: String, default: '' },
})
const emit = defineEmits(['close', 'submit'])
const values = reactive(emptyUserForm())
const touched = reactive({
username: false,
password: false,
displayName: false,
role: false,
})
const validation = computed(() => validateUserForm(values, props.mode))
const fieldErrors = computed(() => validation.value.errors)
watch(() => props.open, (isOpen) => {
if (!isOpen) return
Object.assign(values, props.initialValues)
for (const key of Object.keys(touched)) touched[key] = false
}, { immediate: true })
function onBlur(key) {
touched[key] = true
}
function showError(key) {
return Boolean(touched[key] && fieldErrors.value[key])
}
function submit() {
for (const key of Object.keys(touched)) touched[key] = true
if (!validation.value.valid) return
emit('submit', validation.value.payload)
}
</script>
<template>
<Modal :open="open" :title="title" @close="emit('close')">
<form class="space-y-4" @submit.prevent="submit">
<div v-if="mode === 'create'">
<label class="mb-2 block text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
Username
</label>
<input
v-model="values.username"
type="text"
autocomplete="off"
class="w-full rounded border px-4 py-2 text-sm focus-visible:ring-2 focus-visible:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-white"
:class="showError('username') ? 'border-red-500' : 'border-gray-300'"
@blur="onBlur('username')"
>
<p v-if="showError('username')" class="mt-1 text-xs text-red-600 dark:text-red-400">
{{ fieldErrors.username }}
</p>
</div>
<div v-if="mode === 'create'">
<label class="mb-2 block text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
Password
</label>
<input
v-model="values.password"
type="password"
autocomplete="new-password"
class="w-full rounded border px-4 py-2 text-sm focus-visible:ring-2 focus-visible:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-white"
:class="showError('password') ? 'border-red-500' : 'border-gray-300'"
@blur="onBlur('password')"
>
<p v-if="showError('password')" class="mt-1 text-xs text-red-600 dark:text-red-400">
{{ fieldErrors.password }}
</p>
</div>
<div>
<label class="mb-2 block text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
Display name
</label>
<input
v-model="values.displayName"
type="text"
class="w-full rounded border px-4 py-2 text-sm focus-visible:ring-2 focus-visible:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-white"
:class="showError('displayName') ? 'border-red-500' : 'border-gray-300'"
@blur="onBlur('displayName')"
>
<p v-if="showError('displayName')" class="mt-1 text-xs text-red-600 dark:text-red-400">
{{ fieldErrors.displayName }}
</p>
</div>
<div>
<label class="mb-2 block text-xs font-medium uppercase tracking-wide text-gray-500 dark:text-gray-400">
Role
</label>
<select
v-model="values.role"
class="w-full rounded border px-4 py-2 text-sm focus-visible:ring-2 focus-visible:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-white"
:class="showError('role') ? 'border-red-500' : 'border-gray-300'"
@blur="onBlur('role')"
>
<option v-for="opt in ROLE_OPTIONS" :key="opt.value" :value="opt.value">
{{ opt.label }}
</option>
</select>
<p v-if="showError('role')" class="mt-1 text-xs text-red-600 dark:text-red-400">
{{ fieldErrors.role }}
</p>
</div>
<div v-if="mode === 'edit'" class="flex items-center gap-2">
<input
id="user-active"
v-model="values.isActive"
type="checkbox"
class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500"
>
<label for="user-active" class="text-sm text-gray-700 dark:text-gray-300">
Account active
</label>
</div>
<p v-if="error" class="text-sm text-red-600 dark:text-red-400">{{ error }}</p>
<div class="flex justify-end gap-2">
<Button type="button" variant="ghost" @click="emit('close')">Cancel</Button>
<Button type="submit" :disabled="submitting">
{{ submitting ? 'Saving…' : 'Save' }}
</Button>
</div>
</form>
</Modal>
</template>