So I have an a component with composable, one of them is modal, so the modal is only the canvas, I just pass the slot to the body, but when I do a create data, the vuelidate is always failing even though the data is filled.
I’ve tried to $v.$reset();it and it still error on validate
TestHoliday.vue
<template>
<div>
<div class="card w-full rounded-none border border-gray-300">
<div
class="card-body bg-gray-200 p-3 flex flex-col md:flex-row justify-between"
>
<div class="mb-3 md:mb-0">
<h3 class="text-xl font-bold text-primary flex justify-start">
Holiday Table
</h3>
</div>
<div class="flex flex-col md:flex-row">
<button
@click="openModal"
class="btn btn-primary btn-sm text-white mb-2 md:mb-0 md:mr-2"
>
Add Holiday
</button>
<button
class="btn btn-secondary btn-sm text-white mb-2 md:mb-0 md:mr-2 w-full md:w-36"
@click.prevent="generateHoliday"
:disabled="stateLoading == true"
>
<div
:class="
stateLoading
? 'loading loading-spinner loading-sm text-white'
: ''
"
>
Generate Holiday
</div>
</button>
</div>
</div>
</div>
<div class="card w-full bg-white rounded-none mb-5">
<div class="card-body text-gray-700 p-3">
<div class="overflow-x-auto">
<TableManageHolidays></TableManageHolidays>
</div>
</div>
</div>
<!-- Modal -->
<Modal
:isOpen="showModal"
:isAlertExists="stateAlert"
:alertMessage="alertMessage"
@close="closeModal"
@click.self="closeModal"
@update:isAlertExists="updateAlertState"
>
<!-- Slot for header -->
<template #header>{{ isEditing ? "Edit" : "Add" }} Holiday Date</template>
<!-- Slot for body -->
<template #body>
<form
id="holidayForm"
@submit.prevent="submitData"
class="mt-5 flex flex-col gap-5"
>
<div class="flex flex-col gap-2 form-control">
<label for="name">Holiday Name</label>
<input
type="text"
v-model="state.holiday.name"
:class="{ invalid: v$.holiday.name.$error }"
id="name"
placeholder="Holiday Name"
class="input input-bordered w-full"
autofocus
/>
<span v-if="v$.holiday.name.$error" class="text-sm text-red-500">{{
v$.holiday.name.$errors[0].$message
}}</span>
</div>
<div class="flex flex-col gap-2 form-control">
<label for="date">Holiday Date</label>
<input
type="date"
v-model="state.holiday.date"
:class="{ invalid: v$.holiday.date.$error }"
id="date"
placeholder="Holiday Date"
class="input input-bordered w-full"
/>
<span v-if="v$.holiday.date.$error" class="text-sm text-red-500">{{
v$.holiday.date.$errors[0].$message
}}</span>
</div>
<div class="flex flex-col gap-2 form-control">
<label for="description">Holiday Description</label>
<textarea
class="textarea textarea-bordered"
v-model="state.holiday.description"
id="description"
placeholder="Holiday Description (Optional)"
></textarea>
</div>
<div class="flex justify-end">
<button
type="submit"
:class="isEditing ? 'btn-warning' : 'btn-primary'"
class="btn btn-primary w-full md:w-auto"
>
{{ isEditing ? "Save" : "Add" }} Holiday
</button>
</div>
</form>
</template>
</Modal>
</div>
</template>
<script>
import { useAdminStore } from "@/stores/admin";
import moment from "moment";
import Swal from "sweetalert2";
import { useVuelidate } from "@vuelidate/core";
import { required } from "@vuelidate/validators";
import { computed, onMounted, reactive, ref } from "vue";
import Modal from "@/components/Modal.vue";
import TableManageHolidays from "@/components/admin/department,client/TableManageHolidays.vue";
export default {
components: {
Modal,
TableManageHolidays,
},
name: "ManageHolidays",
setup() {
const state = reactive({
holiday: {
name: "",
date: "",
description: "",
},
});
const rules = computed(() => ({
holiday: {
name: { required },
date: { required },
},
}));
const v$ = useVuelidate(rules, state);
// const v$ = {
// $reset: () => {},
// $validate: async () => true,
// $errors: [],
// };
const admin = useAdminStore();
const showModal = ref(false);
// ? Alert
const stateAlert = ref(false);
const alertMessage = ref("");
let holidays = ref([]);
const stateLoading = ref(false);
const isEditing = ref(false);
const originalHoliday = ref({});
const today = ref(new Date());
const fetchHolidays = async () => {
const data = await admin.fetchHolidays();
const mappedHoliday = data.map((item) => {
return {
...item,
date: moment(item.date).format("DD MMMM YYYY"),
};
});
holidays.value = mappedHoliday;
return mappedHoliday;
};
onMounted(async () => {
await fetchHolidays();
});
return {
fetchHolidays,
admin,
state,
v$,
holidays,
stateLoading,
isEditing,
originalHoliday,
alertMessage,
today,
stateAlert,
showModal,
};
},
methods: {
formatDate(value) {
return moment(value).format("DD MMMM YYYY");
},
updateAlertState(value) {
this.stateAlert = value;
},
clearForm() {
this.state.holiday = {
name: "",
date: "",
description: "",
};
this.v$.$reset();
},
openModal() {
this.clearForm();
this.isEditing = false;
this.showModal = true;
},
closeModal() {
this.showModal = false;
},
closeAlert() {
this.stateAlert = false;
},
async submitData() {
if (this.isEditing) {
await this.updateData();
} else {
await this.addData();
}
},
async addData() {
console.log("Holiday before validation:", this.state.holiday);
this.v$.$reset();
try {
const isValid = await this.v$.$validate();
console.log("Validation result:", isValid);
if (!isValid) {
console.error("Validation failed:", this.v$.$errors);
return;
}
// Check if the data is exists
const check = await this.admin.checkHolidayByDate(
this.state.holiday.date
);
// console.log("check console :", check);
// If the data exists then it will throuw status 200
if (check.status == 200) {
// Throw error that data is exists
// Prevent any creating data with the same date
throw new Error("Holiday Date already exist!");
}
} catch (error) {
if (error.status == 404) {
const response = await this.admin.addHoliday(this.state.holiday);
// this.holidays = await this.admin.fetchHolidays();
await this.fetchHolidays();
// this.$refs.theModal.close();
this.closeModal();
Swal.fire({
icon: "success",
title: "Success...",
text: response.data.message,
timer: 2500,
});
} else {
// this.$refs.theModal.showModal();
this.openModal();
this.stateAlert = true;
this.alertMessage = error.message;
return;
}
}
},
async editData(holiday) {
try {
this.clearForm();
this.isEditing = true;
const response = await this.admin.checkHolidayById(holiday.holiday_Id);
response.date = response.date.split("T")[0];
this.state.holiday = {
...response,
};
this.originalHoliday = {
...response,
};
// this.$refs.theModal.showModal();
this.openModal();
console.log("modal: ", showModal);
} catch (error) {}
},
async updateData() {
try {
this.v$.$validate();
if (this.v$.$error) {
return;
}
// Check for changes in data
if (
this.state.holiday.name === this.originalHoliday.name &&
this.state.holiday.date === this.originalHoliday.date &&
this.state.holiday.description === this.originalHoliday.description
) {
this.stateAlert = true;
this.alertMessage = "No changes detected!";
return;
}
const response = await this.admin.updateHoliday(this.state.holiday);
// this.holidays = await this.admin.fetchHolidays();
await this.fetchHolidays();
this.closeModal();
await Swal.fire({
icon: "success",
title: "Success...",
text: response.data.message,
timer: 2500,
});
} catch (error) {
this.$refs.theModal.showModal();
Swal.fire({
icon: "error",
title: "Error...",
text: error.data.message,
timer: 2500,
});
}
},
async deleteData(id) {
Swal.fire({
title: "Apa anda yakin?",
icon: "warning",
showCancelButton: true,
confirmButtonColor: "#3085d6",
cancelButtonColor: "#d33",
confirmButtonText: "Yes",
}).then(async (result) => {
if (result.isConfirmed) {
try {
const response = await this.admin.deleteHoliday(id);
Swal.fire({
title: "Success...",
text: response.data.message,
icon: "success",
timer: 2500,
});
// this.holidays = await this.admin.fetchHolidays();
await this.fetchHolidays();
} catch (error) {
Swal.fire({
icon: "error",
title: "Error...",
text: error.data.message,
timer: 2500,
});
}
}
});
},
async generateHoliday() {
this.stateLoading = true;
let totalSuccessInsert = 0;
let totalFailedInsert = 0;
let totalExistingDate = 0;
try {
const googleHoliday = await this.admin.fetchGoogleHoliday();
const promises = googleHoliday.map(async (data) => {
try {
await this.admin.checkHolidayByDate(data.date);
totalExistingDate += 1;
} catch (error) {
if (error.status === 404) {
const newHoliday = {
name: data.name,
date: data.date,
description: null,
};
try {
await this.admin.addHoliday(newHoliday);
totalSuccessInsert += 1;
} catch (error) {
totalFailedInsert += 1;
}
} else {
totalFailedInsert += 1;
}
}
});
await Promise.all(promises);
this.stateLoading = false;
Swal.fire({
title: "Holiday Generated!",
html: `${totalSuccessInsert} Berhasil, ${totalFailedInsert} Gagal, ${totalExistingDate} Telah Ada`,
icon: "info",
});
await this.fetchHolidays();
} catch (error) {
this.stateLoading = false;
Swal.fire({
title: "Error",
text: "Failed to fetch holiday data. Please try again.",
icon: "error",
});
}
},
},
};
</script>
<style scoped>
span {
color: red;
font-size: 0.9em;
}
</style>
Modal.vue
<template>
<div
v-if="isOpen"
class="fixed inset-0 p-8 overflow-auto h-full bg-gray-900 bg-opacity-50 flex items-center justify-center z-50"
>
<dialog id="theModal" ref="theModal" class="modal">
<div class="modal-box">
<form method="dialog">
<button
@click="closeModal"
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
>
✕
</button>
</form>
<h3 class="text-lg font-bold">
<slot name="header">Default Modal Title</slot>
</h3>
<div v-if="isAlertExists">
<!-- Alert Section -->
<div
role="alert"
class="alert alert-warning mb-4 flex items-center justify-between mt-3"
>
<div class="flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 shrink-0 stroke-current"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
Warning: {{ alertMessage }}
</div>
<!-- Close Alert Button -->
<button class="btn btn-sm btn-circle btn-ghost" @click="closeAlert">
✕
</button>
</div>
</div>
<div>
<slot name="body"> </slot>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button @click="closeModal">close</button>
</form>
</dialog>
</div>
</template>
<script>
import { nextTick, onMounted, ref } from "vue";
export default {
props: {
isOpen: {
type: Boolean,
required: true,
},
isAlertExists: {
type: Boolean,
default: false,
},
alertMessage: {
type: String,
required: false,
default: "",
},
},
setup(props) {
// console.log(props.isAlertExists);
const showAlert = ref(true);
return { showAlert };
},
watch: {
isOpen(newVal) {
nextTick(() => {
if (newVal) {
this.$refs.theModal.showModal();
}
});
},
},
methods: {
closeModal() {
this.$emit("close");
this.$refs.theModal.close();
},
closeAlert() {
// this.showAlert = false;
this.$emit("update:isAlertExists", false);
},
},
};
</script>
<style scoped>
/* Optional custom styling */
</style>
Just ignore the table component, the problem is on the addData() function that has vuelidate is not working properly