mitlist/fe/src/components/CreateListModal.vue

150 lines
4.5 KiB
Vue

<template>
<div v-if="isOpen" class="modal-backdrop open" @click.self="closeModal">
<div class="modal-container" role="dialog" aria-modal="true" aria-labelledby="createListModalTitle">
<div class="modal-header">
<h3 id="createListModalTitle">Create New List</h3>
<button class="close-button" @click="closeModal" aria-label="Close modal">
<svg class="icon" aria-hidden="true"><use xlink:href="#icon-close" /></svg>
</button>
</div>
<form @submit.prevent="onSubmit">
<div class="modal-body">
<div class="form-group">
<label for="listName" class="form-label">List Name</label>
<input
type="text"
id="listName"
v-model="listName"
class="form-input"
required
ref="listNameInput"
/>
<p v-if="formErrors.listName" class="form-error-text">{{ formErrors.listName }}</p>
</div>
<div class="form-group">
<label for="description" class="form-label">Description</label>
<textarea
id="description"
v-model="description"
class="form-input"
rows="3"
></textarea>
</div>
<div class="form-group" v-if="groups && groups.length > 0">
<label for="selectedGroup" class="form-label">Associate with Group (Optional)</label>
<select id="selectedGroup" v-model="selectedGroupId" class="form-input">
<option :value="null">None</option>
<option v-for="group in groups" :key="group.value" :value="group.value">
{{ group.label }}
</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-neutral" @click="closeModal">Cancel</button>
<button type="submit" class="btn btn-primary ml-2" :disabled="loading">
<span v-if="loading" class="spinner-dots-sm" role="status"><span/><span/><span/></span>
Create
</button>
</div>
</form>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue';
import { useVModel, onClickOutside } from '@vueuse/core';
import { apiClient, API_ENDPOINTS } from '@/config/api'; // Assuming this path is correct
import { useNotificationStore } from '@/stores/notifications';
const props = defineProps<{
modelValue: boolean;
groups?: Array<{ label: string; value: number }>;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void;
(e: 'created'): void;
}>();
const isOpen = useVModel(props, 'modelValue', emit);
const listName = ref('');
const description = ref('');
const selectedGroupId = ref<number | null>(null); // Store only the ID
const loading = ref(false);
const formErrors = ref<{ listName?: string }>({});
const notificationStore = useNotificationStore();
const listNameInput = ref<HTMLInputElement | null>(null);
const modalContainerRef = ref<HTMLElement | null>(null); // For onClickOutside
watch(isOpen, (newVal) => {
if (newVal) {
// Reset form when opening
listName.value = '';
description.value = '';
selectedGroupId.value = null;
formErrors.value = {};
nextTick(() => {
listNameInput.value?.focus();
});
}
});
onClickOutside(modalContainerRef, () => {
if (isOpen.value) {
closeModal();
}
});
const closeModal = () => {
isOpen.value = false;
};
const validateForm = () => {
formErrors.value = {};
if (!listName.value.trim()) {
formErrors.value.listName = 'Name is required';
}
return Object.keys(formErrors.value).length === 0;
};
const onSubmit = async () => {
if (!validateForm()) {
return;
}
loading.value = true;
try {
await apiClient.post(API_ENDPOINTS.LISTS.BASE, {
name: listName.value,
description: description.value,
group_id: selectedGroupId.value,
});
notificationStore.addNotification({ message: 'List created successfully', type: 'success' });
emit('created');
closeModal();
} catch (error: unknown) {
const message = error instanceof Error ? error.message : 'Failed to create list';
notificationStore.addNotification({ message, type: 'error' });
console.error(message, error);
} finally {
loading.value = false;
}
};
</script>
<style scoped>
.form-error-text {
color: var(--danger);
font-size: 0.85rem;
margin-top: 0.25rem;
}
.ml-2 {
margin-left: 0.5rem; /* from Valerie UI utilities */
}
</style>