mitlist/fe/src/components/CreateListModal.vue
mohamad 02238974aa
All checks were successful
Deploy to Production, build images and push to Gitea Registry / build_and_push (pull_request) Successful in 1m17s
Refactor: Update styling and functionality in various components
This commit includes several enhancements across multiple files:
- In `valerie-ui.scss`, improved formatting of CSS properties and adjusted selectors for better readability and consistency.
- In `CreateListModal.vue`, introduced a sentinel value for group selection and refined the logic for handling group options.
- In `VModal.vue`, streamlined modal structure and removed unnecessary styles, centralizing modal styling in `valerie-ui.scss`.
- In `VTextarea.vue`, adjusted aria attributes for better accessibility and improved code clarity.
- Updated `api-config.ts` to switch the API base URL to a local development environment.

These changes aim to enhance maintainability, accessibility, and overall user experience.
2025-06-01 20:41:04 +02:00

140 lines
4.6 KiB
Vue

<template>
<VModal :model-value="isOpen" @update:model-value="closeModal" title="Create New List">
<template #default>
<form @submit.prevent="onSubmit">
<VFormField label="List Name" :error-message="formErrors.listName">
<VInput type="text" v-model="listName" required ref="listNameInput" />
</VFormField>
<VFormField label="Description">
<VTextarea v-model="description" :rows="3" />
</VFormField>
<VFormField label="Associate with Group (Optional)" v-if="props.groups && props.groups.length > 0">
<VSelect v-model="selectedGroupId" :options="groupOptionsForSelect" placeholder="None" />
</VFormField>
<!-- Form submission is handled by button in footer slot -->
</form>
</template>
<template #footer>
<VButton variant="neutral" @click="closeModal" type="button">Cancel</VButton>
<VButton type="submit" variant="primary" :disabled="loading" @click="onSubmit" class="ml-2">
<VSpinner v-if="loading" size="sm" />
Create
</VButton>
</template>
</VModal>
</template>
<script setup lang="ts">
import { ref, watch, nextTick, computed } from 'vue';
import { useVModel } from '@vueuse/core'; // onClickOutside removed
import { apiClient, API_ENDPOINTS } from '@/config/api'; // Assuming this path is correct
import { useNotificationStore } from '@/stores/notifications';
import VModal from '@/components/valerie/VModal.vue';
import VFormField from '@/components/valerie/VFormField.vue';
import VInput from '@/components/valerie/VInput.vue';
import VTextarea from '@/components/valerie/VTextarea.vue';
import VSelect from '@/components/valerie/VSelect.vue';
import VButton from '@/components/valerie/VButton.vue';
import VSpinner from '@/components/valerie/VSpinner.vue';
const props = defineProps<{
modelValue: boolean;
groups?: Array<{ label: string; value: number }>;
}>();
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void;
(e: 'created', newList: any): void;
}>();
const isOpen = useVModel(props, 'modelValue', emit);
const listName = ref('');
const description = ref('');
const SENTINEL_NO_GROUP = 0; // Using 0 to represent 'None' or 'Personal List'
const selectedGroupId = ref<number>(SENTINEL_NO_GROUP); // Initialize with sentinel
const loading = ref(false);
const formErrors = ref<{ listName?: string }>({});
const notificationStore = useNotificationStore();
const listNameInput = ref<InstanceType<typeof VInput> | null>(null);
// const modalContainerRef = ref<HTMLElement | null>(null); // Removed
const groupOptionsForSelect = computed(() => {
// VSelect's placeholder should work if selectedGroupId is the sentinel value
return props.groups ? props.groups.map(g => ({ label: g.label, value: g.value })) : [];
});
watch(isOpen, (newVal) => {
if (newVal) {
// Reset form when opening
listName.value = '';
description.value = '';
// If a single group is passed, pre-select it. Otherwise, default to sentinel
if (props.groups && props.groups.length === 1) {
selectedGroupId.value = props.groups[0].value;
} else {
selectedGroupId.value = SENTINEL_NO_GROUP; // Reset to sentinel
}
formErrors.value = {};
nextTick(() => {
// listNameInput.value?.focus?.(); // This might still be an issue depending on VInput. Commenting out for now.
});
}
});
// onClickOutside removed, VModal handles backdrop clicks
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 {
const payload = {
name: listName.value,
description: description.value,
group_id: selectedGroupId.value === SENTINEL_NO_GROUP ? null : selectedGroupId.value,
};
const response = await apiClient.post(API_ENDPOINTS.LISTS.BASE, payload);
notificationStore.addNotification({ message: 'List created successfully', type: 'success' });
emit('created', response.data);
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>
.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>