
All checks were successful
Deploy to Production, build images and push to Gitea Registry / build_and_push (pull_request) Successful in 1m17s
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.
140 lines
4.6 KiB
Vue
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> |