ça fonctionne toujours pas :
console :
 Final check before prepareRegistrationData: undefined AppList.vue:128:13
11:38:48,950 ExtraData type: object AppList.vue:131:13
11:38:48,950 ExtraData value: [object Object] AppList.vue:132:13
11:38:48,950 Saving item with data: {"id":22,"code":"065-426","description":"Mathematiques : SN","volet1_code":"065-410","volet1_percentage":30,"volet2_code":"065-420","volet2_percentage":70,"volet3_code":"","volet3_percentage":null,"volet4_code":null,"volet4_percentage":null,"updated_at":null,"created_at":{"format":"Y-m-d H:i:s","date":"2024-06-06 15:04:24.000000","timezone_type":3,"timezone":"America/Toronto"},"volet1_registration_count":1,"volet2_registration_count":0,"volet3_registration_count":0,"volet4_registration_count":0,"registrations":[{"id":308,"user_id":63,"subject_id":22,"subject":{"id":22,"code":"065-426","description":"Mathematiques : SN","volet1_code":"065-410","volet1_percentage":30,"volet2_code":"065-420","volet2_percentage":70,"volet3_code":"","volet3_percentage":null,"volet4_code":null,"volet4_percentage":null,"updated_at":null,"created_at":{"format":"Y-m-d H:i:s","date":"2024-06-06 15:04:24.000000","timezone_type":3,"timezone":"America/Toronto"},"volet1_registration_count":1,"volet2_registration_count":0,"volet3_registration_count":0,"volet4_registration_count":0},"fiche":96842,"student_id":null,"student":{"fiche":96842,"first_name":"Sara-Maude","last_name":"Hudon","school_code":"009","permanent_code":"HUDS90520809","address":"175, De Honfleur","city":"Sainte-Monique","zipcode":"G0W2T0","phone_number":"4183472026","phone_type":null,"parent_name1":"Stéphane Hudon","parent_name2":"Mélanie Larouche","birthday":{"format":"Y-m-d H:i:s","date":"2008-02-28 00:00:00.000000","timezone_type":3,"timezone":"America/Toronto"},"createdAt":{"format":"Y-m-d","date":"2012-03-23 00:00:00.000000","timezone_type":3,"timezone":"America/Toronto"},"updatedAt":{"format":"Y-m-d H:i:s","date":"2024-02-16 20:14:00.000000","timezone_type":3,"timezone":"America/Toronto"},"external":false},"year":2023,"volet1":true,"volet2":false,"volet3":false,"volet4":false,"volet1_results":null,"volet2_results":null,"volet3_results":null,"volet4_results":null,"volet1_results_june":null,"volet2_results_june":null,"volet3_results_june":null,"volet4_results_june":null,"volet1_presence":null,"volet2_presence":null,"volet3_presence":null,"volet4_presence":null,"notes":null,"created_at":{"format":"Y-m-d H:i:s","date":"2024-09-17 13:10:51.000000","timezone_type":3,"timezone":"America/Toronto"},"updated_at":null}],"volet_details":"065-410 - 30%","registration_details":1,"result":""} AppList.vue:139:13
11:38:48,950 id: 22 (type: number) AppList.vue:141:15
11:38:48,950 code: 065-426 (type: string) AppList.vue:141:15
11:38:48,950 description: Mathematiques : SN (type: string) AppList.vue:141:15
11:38:48,950 volet1_code: 065-410 (type: string) AppList.vue:141:15
11:38:48,950 volet1_percentage: 30 (type: number) AppList.vue:141:15
11:38:48,950 volet2_code: 065-420 (type: string) AppList.vue:141:15
11:38:48,950 volet2_percentage: 70 (type: number) AppList.vue:141:15
11:38:48,950 volet3_code:  (type: string) AppList.vue:141:15
11:38:48,950 volet3_percentage: null (type: object) AppList.vue:141:15
11:38:48,950 volet4_code: null (type: object) AppList.vue:141:15
11:38:48,950 volet4_percentage: null (type: object) AppList.vue:141:15
11:38:48,950 updated_at: null (type: object) AppList.vue:141:15
11:38:48,950 created_at: [object Object] (type: object) AppList.vue:141:15
11:38:48,950 volet1_registration_count: 1 (type: number) AppList.vue:141:15
11:38:48,950 volet2_registration_count: 0 (type: number) AppList.vue:141:15
11:38:48,950 volet3_registration_count: 0 (type: number) AppList.vue:141:15
11:38:48,950 volet4_registration_count: 0 (type: number) AppList.vue:141:15
11:38:48,950 registrations: [object Object] (type: object) AppList.vue:141:15
11:38:48,950 volet_details: 065-410 - 30% (type: string) AppList.vue:141:15
11:38:48,950 registration_details: 1 (type: number) AppList.vue:141:15
11:38:48,951 result:  (type: string) AppList.vue:141:15
11:38:48,957
XHRPATCH
https://se.cslsj.qc.ca/dev/reprise-examen/api/students/22/registrations/2023
[HTTP/2 200  43ms]

	
code	"065-426"
created_at	{…}
date	"2024-06-06 15:04:24.000000"
format	"Y-m-d H:i:s"
timezone	"America/Toronto"
timezone_type	3
description	"Mathematiques : SN"
id	22
registration_details	1
registrations	[…]
0	{…}
created_at	{…}
fiche	96842
id	308
notes	null
student	{…}
student_id	null
subject	{…}
subject_id	22
updated_at	null
user_id	63
volet1	true
volet1_presence	null
volet1_results	null
volet1_results_june	null
volet2	false
volet2_presence	null
volet2_results	null
volet2_results_june	null
volet3	false
volet3_presence	null
volet3_results	null
volet3_results_june	null
volet4	false
volet4_presence	null
volet4_results	null
volet4_results_june	null
year	2023
result	""
updated_at	null
volet_details	"065-410 - 30%"
volet1_code	"065-410"
volet1_percentage	30
volet1_registration_count	1
volet2_code	"065-420"
volet2_percentage	70
volet2_registration_count	0
volet3_code	""
volet3_percentage	null
volet3_registration_count	0
volet4_code	null
volet4_percentage	null
volet4_registration_count	0
11:38:48,964
XHROPTIONS
https://se.cslsj.qc.ca/dev/reprise-examen/api/students/22/registrations/2023
[HTTP/2 200  121ms]

11:38:49,136 API response: {"data":"Method Not Allowed in <strong>/var/www/reprise-backend/development/vendor/league/route/src/Dispatcher.php:127</strong> <pre>#0 /var/www/reprise-backend/development/vendor/league/route/src/Dispatcher.php(38): League\\Route\\Dispatcher->setMethodNotAllowedDecoratorMiddleware()\n#1 /var/www/reprise-backend/development/vendor/league/route/src/Router.php(110): League\\Route\\Dispatcher->dispatchRequest()\n#2 /var/www/reprise-backend/development/vendor/mcnd/lean/src/Kernel.php(165): League\\Route\\Router->dispatch()\n#3 /var/www/reprise-backend/development/vendor/mcnd/lean/src/Kernel.php(48): Lean\\Kernel->handleRequest()\n#4 /var/www/reprise-backend/development/src/Kernel.php(10): Lean\\Kernel->__construct()\n#5 /var/www/reprise-backend/development/public/index.php(4): require_once('...')\n#6 {main}</pre>","status":200,"statusText":"","headers":{"content-length":"310","content-type":"text/html; charset=UTF-8"},"config":{"transitional":{"silentJSONParsing":true,"forcedJSONParsing":true,"clarifyTimeoutError":false},"adapter":["xhr","http","fetch"],"transformRequest":[null],"transformResponse":[null],"timeout":0,"xsrfCookieName":"XSRF-TOKEN","xsrfHeaderName":"X-XSRF-TOKEN","maxContentLength":-1,"maxBodyLength":-1,"env":{},"headers":{"Accept":"application/json, text/plain, */*","Content-Type":"application/json","Authorization":"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOjYzLCJuYW1lIjoiQWRtaW4gQWNjb3VudCIsImlhdCI6MTcxNzM1OTY1NSwiZXhwIjoxNzE3NDQ2MDU1fQ.vnPS4rXy8AZsZXvwN1BNa_mN9qmK002Z72IaTbEZdJA"},"baseURL":"https://se.cslsj.qc.ca/dev/reprise-examen/api","method":"patch","url":"/students/22/registrations/2023","data":"{\"id\":22,\"code\":\"065-426\",\"description\":\"Mathematiques : SN\",\"volet1_code\":\"065-410\",\"volet1_percentage\":30,\"volet2_code\":\"065-420\",\"volet2_percentage\":70,\"volet3_code\":\"\",\"volet3_percentage\":null,\"volet4_code\":null,\"volet4_percentage\":null,\"updated_at\":null,\"created_at\":{\"format\":\"Y-m-d H:i:s\",\"date\":\"2024-06-06 15:04:24.000000\",\"timezone_type\":3,\"timezone\":\"America/Toronto\"},\"volet1_registration_count\":1,\"volet2_registration_count\":0,\"volet3_registration_count\":0,\"volet4_registration_count\":0,\"registrations\":[{\"id\":308,\"user_id\":63,\"subject_id\":22,\"subject\":{\"id\":22,\"code\":\"065-426\",\"description\":\"Mathematiques : SN\",\"volet1_code\":\"065-410\",\"volet1_percentage\":30,\"volet2_code\":\"065-420\",\"volet2_percentage\":70,\"volet3_code\":\"\",\"volet3_percentage\":null,\"volet4_code\":null,\"volet4_percentage\":null,\"updated_at\":null,\"created_at\":{\"format\":\"Y-m-d H:i:s\",\"date\":\"2024-06-06 15:04:24.000000\",\"timezone_type\":3,\"timezone\":\"America/Toronto\"},\"volet1_registration_count\":1,\"volet2_registration_count\":0,\"volet3_registration_count\":0,\"volet4_registration_count\":0},\"fiche\":96842,\"student_id\":null,\"student\":{\"fiche\":96842,\"first_name\":\"Sara-Maude\",\"last_name\":\"Hudon\",\"school_code\":\"009\",\"permanent_code\":\"HUDS90520809\",\"address\":\"175, De Honfleur\",\"city\":\"Sainte-Monique\",\"zipcode\":\"G0W2T0\",\"phone_number\":\"4183472026\",\"phone_type\":null,\"parent_name1\":\"Stéphane Hudon\",\"parent_name2\":\"Mélanie Larouche\",\"birthday\":{\"format\":\"Y-m-d H:i:s\",\"date\":\"2008-02-28 00:00:00.000000\",\"timezone_type\":3,\"timezone\":\"America/Toronto\"},\"createdAt\":{\"format\":\"Y-m-d\",\"date\":\"2012-03-23 00:00:00.000000\",\"timezone_type\":3,\"timezone\":\"America/Toronto\"},\"updatedAt\":{\"format\":\"Y-m-d H:i:s\",\"date\":\"2024-02-16 20:14:00.000000\",\"timezone_type\":3,\"timezone\":\"America/Toronto\"},\"external\":false},\"year\":2023,\"volet1\":true,\"volet2\":false,\"volet3\":false,\"volet4\":false,\"volet1_results\":null,\"volet2_results\":null,\"volet3_results\":null,\"volet4_results\":null,\"volet1_results_june\":null,\"volet2_results_june\":null,\"volet3_results_june\":null,\"volet4_results_june\":null,\"volet1_presence\":null,\"volet2_presence\":null,\"volet3_presence\":null,\"volet4_presence\":null,\"notes\":null,\"created_at\":{\"format\":\"Y-m-d H:i:s\",\"date\":\"2024-09-17 13:10:51.000000\",\"timezone_type\":3,\"timezone\":\"America/Toronto\"},\"updated_at\":null}],\"volet_details\":\"065-410 - 30%\",\"registration_details\":1,\"result\":\"\"}"},"request":{}} AppList.vue:154:15
11:38:49,136
Error from API: undefined AppList.vue:161:17
11:38:49,137
Opération échouée : 
Object { global: "Une erreur inattendue s'est produite." }
Results.vue:251:11
11:38:49,137 Final notes value before sending to API: undefined


results.vue :
<script setup>
import { ref, onMounted, computed, watch } from 'vue';
import AppList from '@/components/AppList.vue';
import AppCustomAlert from '@/components/AppCustomAlert.vue';
import apiClient from '@/services/axiosConfig.js';

const subjects = ref([]);
const selectedItem = ref(null);
const showSuccessAlert = ref(false);
const errors = ref({});

const studentsMap = ref({});

const padFiche = (fiche) => {
  return fiche.toString().padStart(7, '0');
};

const getCurrentSchoolYear = () => 2023; 

const columns = ref([
  { header: 'Nom de la Matière', field: 'description', class: 'col-3 left-align' },
  {
    header: 'Volet',
    field: 'volet_details',
    class: 'col-3 center-align',
    formatter: (item) => item.volet_details
  },
  { header: 'Nombre d\'inscriptions', field: 'registration_details', class: 'col-4 center-align' }
]);

const customActions = ref([
  { name: 'customModal', label: 'Entrer les résultats', icon: 'cil-spreadsheet', tooltip: 'Entrer les résultats', iconClass: 'text-primary' }
]);

const modalForms = ref({
  default: {
    fields: [
      { name: 'subject_info', label: '', type: 'custom', class: 'col-md-12' },
      { name: 'student_info', label: '', type: 'custom', class: 'col-md-12' },
    ]
  }
});

const apiUrls = computed(() => ({
  update: (fiche, registrationId) => {
    if (fiche) {
      return /students/${fiche}/registrations/${getCurrentSchoolYear()};
    }
    return /students/registrations/${registrationId}/${getCurrentSchoolYear()};
  }
}));

const filterRegistrationData = (registration) => {
  const { id, resultField, result } = registration;
  // Ne garder que l'ID d'inscription et les résultats pertinents
  return {
    [resultField]: result,
  };
};

const fetchSubjectsAndRegistrations = async () => {
  try {
    const [registrationsResponse, subjectsResponse] = await Promise.all([
      apiClient.get(/students/registrations/${getCurrentSchoolYear()}),
      apiClient.get(/subjects/${getCurrentSchoolYear()})
    ]);

    if (subjectsResponse.data.status === "success" && Array.isArray(subjectsResponse.data.data)) {
      const registrations = registrationsResponse.data.data || [];

      const fiches = [...new Set(registrations.map(reg => reg.fiche).filter(fiche => fiche))];

      const paddedFiches = fiches.map(fiche => padFiche(fiche));

      const studentPromises = paddedFiches.map(fiche => 
        apiClient.get(/students/${fiche}).then(res => res.data.data)
      );
      const students = await Promise.all(studentPromises);

      students.forEach(student => {
        if (student.fiche) {
          const paddedFiche = padFiche(student.fiche);
          studentsMap.value[paddedFiche] = student;
        }
      });

      const registrationsWithStudents = registrations.map(reg => {
        const fichePadded = padFiche(reg.fiche);
        return {
          ...reg,
          student: studentsMap.value[fichePadded] || {}
        };
      });

      subjects.value = subjectsResponse.data.data.map(subject => {
        const subjectRegistrations = registrationsWithStudents.filter(reg => reg.subject_id === subject.id);
        return {
          ...subject,
          registrations: subjectRegistrations 
        };
      }).filter(subject => subject.registrations.length > 0);
      
    } else {
      console.error('Failed to fetch subjects:', subjectsResponse);
    }
  } catch (error) {
    console.error('Erreur lors de la récupération des données:', error);
  }
};

const processedSubjects = computed(() => {
  return subjects.value.map(subject => {
    const voletDetailsGeneral = [];

    for (let i = 1; i <= 4; i++) {
      const code = subject[volet${i}_code];
      const percentage = subject[volet${i}_percentage];

      const hasRegistrations = subject.registrations.some(reg => reg[volet${i}]);

      if (code && code.trim() !== '' && hasRegistrations) {
        voletDetailsGeneral.push(${code} - ${percentage || 0}%);
      }
    }

    return {
      ...subject,
      volet_details: voletDetailsGeneral.join('<br/>'),
      registration_details: subject.registrations.length
    };
  });
});

const groupedRegistrations = computed(() => {
  if (!selectedItem.value) return {};

  const volets = {};

  selectedItem.value.registrations.forEach(reg => {
    for (let i = 1; i <= 4; i++) {
      if (reg[volet${i}]) { 
        const code = selectedItem.value[volet${i}_code];
        const percentage = selectedItem.value[volet${i}_percentage];

        if (code && code.trim() !== '') {
          if (!volets[code]) {
            volets[code] = {
              code,
              percentage,
              registrations: []
            };
          }

          const resultField = volet${i}_results;
          const result = reg[resultField];

          volets[code].registrations.push({
            ...reg,
            student: reg.student,
            result,
            resultField,
            voletIndex: i
          });
        }
      }
    }
  });

  return volets;
});

const handleCustomAction = async (actionName, item) => {
  selectedItem.value = JSON.parse(JSON.stringify(item));
  console.log('Selected Item:', selectedItem.value); 
};

const validateResults = (updates) => {
  let isValid = true;
  errors.value = {};

  for (const registrationId in updates) {
    const data = updates[registrationId];

    for (const field in data) {
      const result = data[field];
      if (result === null || result === undefined || result === '') {
        errors.value[registrationId] = 'Le résultat est requis.';
        isValid = false;
      } else if (result < 0 || result > 100) {
        errors.value[registrationId] = 'Le résultat doit être entre 0 et 100.';
        isValid = false;
      }
    }
  }

  return isValid;
};

const handleCustomSave = async (currentItem) => {
  const updates = {};

  Object.values(groupedRegistrations.value).forEach(volet => {
    volet.registrations.forEach(reg => {
      const registrationId = reg.id;
      const resultField = reg.resultField; 
      const result = reg.result; 

      if (!updates[registrationId]) {
        // Utiliser la fonction de filtrage pour ne récupérer que les champs de résultat
        updates[registrationId] = filterRegistrationData(reg);
      }
    });
  });

  // Validation des résultats avant envoi
  if (!validateResults(updates)) {
    return;
  }

  try {
    for (const registrationId in updates) {
      const dataToUpdate = updates[registrationId];
      const fiche = groupedRegistrations.value[registrationId]?.student?.fiche;

      // Construire l'URL de la mise à jour en utilisant la fiche ou l'ID d'inscription
      const updateUrl = apiUrls.value.update(fiche, registrationId);

      // Envoyer uniquement les champs filtrés pour cette inscription
      await apiClient.patch(updateUrl, dataToUpdate);
    }

    showSuccessAlert.value = true;
    selectedItem.value = null;
    console.log('Données envoyées :', dataToUpdate);
  } catch (error) {
    console.error('Erreur lors de la mise à jour des résultats:', error);
  }
};



const handleSuccess = (message) => {
  successMessage.value = message;
  showSuccessAlert.value = true;
  setTimeout(() => {
    showSuccessAlert.value = false;
  }, 3000);
};

const handleError = (error) => {
  console.error('Opération échouée :', error);
};

const validateAndCorrectResults = () => {
  subjects.value.forEach(subject => {
    subject.registrations.forEach(registration => {
      for (let i = 1; i <=4; i++) {
        const result = registration[volet${i}_results];
        if (result !== null && (isNaN(parseInt(result, 10)) || result > 100)) {
          registration[volet${i}_results] = result > 100 ? 100 : parseInt(result, 10);
        }
      }
    });
  });
};

watch(() => subjects.value, validateAndCorrectResults, { deep: true });

onMounted(fetchSubjectsAndRegistrations);
</script>

<template>
  <div>
    <AppCustomAlert v-if="showSuccessAlert" :message="'Les résultats ont été enregistrés avec succès!'" type="success" />
    <AppList
      :title="'Liste des Matières'"
      :columns="columns"
      :items="processedSubjects"
      :customModalTitle="'Entrer'"
      :itemType="'les Résultats'"
      :customButtonLabel="'Enregistrer'"
      :customActions="customActions"
      :modalForms="modalForms"
      :showAddButton="false"
      :shouldFetchData="false"
      :apiUrls="apiUrls"
      :customHandleSaveItem="handleCustomSave" 
      @itemSaved="handleSuccess('Les résultats ont été enregistrés avec succès.')"
      @itemDeleted="handleSuccess('Les résultats ont été supprimés avec succès.')"
      @operationFailed="handleError"
      @customAction="handleCustomAction"
    >
      <template v-slot:modal-body="{ currentItem }">
        <div v-if="selectedItem">
          <h3>{{ selectedItem.description }}</h3>
          <div v-for="(volet, code) in groupedRegistrations" :key="code" class="volet-section">
            <h4>{{ code }}</h4>
            <div v-for="reg in volet.registrations" :key="reg.id" class="student-result-container">
              <div class="student-info">
                <span class="student-name">{{ reg.student.first_name }} {{ reg.student.last_name }}</span>
                <span class="student-fiche">(#{{ reg.fiche }})</span>
              </div>
              <div class="result-input">
                <input
                  type="number"
                  v-model="reg.result"
                  min="0"
                  max="100"
                  placeholder="Résultat"
                  class="result-input-field"
                />
                <div v-if="errors[reg.id]" class="error-message">
                  {{ errors[reg.id] }}
                </div>
              </div>
            </div>
          </div>
        </div>
      </template>
    </AppList>
  </div>
</template>

<style scoped>
@import "@/styles/_results.scss";

.volet-section {
  margin-bottom: 20px;
  padding: 10px;
  border: none; /* Supprime la bordure */
  border-radius: 5px;
}

.volet-section h4 {
  margin-bottom: 5px;
  color: #333;
}

.volet-section p {
  margin-bottom: 10px;
  color: #555;
}

.student-result-container {
  display: flex;
  align-items: center;
  margin-bottom: 10px;
}

.student-info {
  flex: 1;
}

.student-name {
  font-weight: bold;
}

.student-fiche {
  margin-left: 10px;
  color: #666;
}

.result-input {
  flex: 1;
}

.result-input label {
  margin-right: 5px;
}
</style>

applist.vue :
<script setup>
import { ref, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'; 
import AppCustomAlert from '@/components/AppCustomAlert.vue'
import apiClient from '@/services/axiosConfig.js'

const props = defineProps({
  title: String,
  apiUrls: Object,
  columns: Array,
  items: Array,
  itemType: String,
  itemTypeToDelete: String,
  modalForms: Object,
  modalClass: String,
  customModalTitle: String,
  customButtonLabel: String,
  customApiUrl: String,
  newRegistration: Object,
  apiUrls: {
    type: Object,
    default: () => ({
      fetch: '',
      create: '',
      update: '',
      delete: ''
    })
  },
  modalType: { type: String, default: 'default' },
  layout: Array,
  isRowClickable: { type: Boolean, default: false },
  showAddButton: { type: Boolean, default: true },
  showTable: { type: Boolean, default: true },
  customActions: { type: Array, default: () => [] },
  showActions: { type: Boolean, default: true },
  extraData: { type: [Object, Function], default: () => ({}) },
  watchProperty: { type: String, default: null, },
  shouldFetchData: { type: Boolean, default: true }
})

const isHovering = ref(false);
const emit = defineEmits(['itemSaved', 'itemDeleted', 'itemAction', 'operationFailed', 'customAction', 'propertyChanged', 'openAddModal', 'openEditModal']);

const items = ref([])
const currentItem = ref({});
const errorMessage = ref('')
const successMessage = ref('')
const showModal = ref(false)
const showSuccessAlert = ref(false)
const isEditMode = ref(false)
const errors = ref({})
const showConfirmDeleteModal = ref(false);
const idToDelete = ref(null);
const customModal = ref(false);
const modalTitle = ref('');
const modalButtonLabel = ref('');
const apiUrl = ref('');
const router = useRouter();

const fetchItems = async () => {
  if (props.shouldFetchData && props.apiUrls.fetch) {
    try {
      const response = await apiClient.get(props.apiUrls.fetch);
      if (response.data && Array.isArray(response.data.data)) {
        items.value = response.data.data;
      } else {
        throw new Error('Invalid data format received.');
      }
    } catch (error) {
      console.error(Error loading items: ${error.message});
    }
  }
};

const openModal = (item = {}, isCustom = false) => {
  currentItem.value = { ...item, result: item.result || '' };
  isEditMode.value = !!item.id;
  errors.value = {};

  if (isEditMode.value) {
    apiUrl.value = typeof props.apiUrls.update === 'function' 
      ? props.apiUrls.update(item.id) 
      : ${props.apiUrls.update}/${item.id};
    modalTitle.value = props.customModalTitle || 'Modifier'; 
    modalButtonLabel.value = props.customButtonLabel || 'Modifier'; 
  } else {
    apiUrl.value = props.apiUrls.create;
    modalTitle.value = props.customModalTitle || 'Ajouter'; 
    modalButtonLabel.value = props.customButtonLabel || 'Ajouter'; 
  }

  showModal.value = true;
};


const closeModal = () => {
  showModal.value = false;
  currentItem.value = {};
  errors.value = {};
};

function formatInputDate(date) {
  if (date && typeof date === 'object' && date.date) {
    return date.date.split(' ')[0];
  } else if (typeof date === 'string') {
    return date.split(' ')[0];
  }
  return '';
}

const handleCustomAction = (actionName, item) => {
  if (actionName === 'redirectToDetails') {
    emit('redirectAction', item);
  } else if (actionName === 'customModal') {
    emit('customAction', actionName, item);
    openModal(item, true);
  } else {
    emit('customAction', actionName, item);
    openModal(item);
  }
};

const handleSaveItem = async () => {
  if (props.modalType === 'customModal') {
    emit('itemSaved', currentItem.value);
    closeModal();
  } else {
    console.log(Final check before prepareRegistrationData: ${currentItem.value.notes});
    let extraData = typeof props.extraData === 'function' ? props.extraData() : props.extraData;

    console.log(ExtraData type: ${typeof extraData});
    console.log(ExtraData value: ${extraData});

    const itemToSave = {
      ...currentItem.value, 
      ...extraData
    };

    console.log(Saving item with data: ${JSON.stringify(itemToSave)});
    Object.entries(itemToSave).forEach(([key, value]) => {
      console.log(${key}: ${value} (type: ${typeof value}));
    });

    if (!validateFields()) {
      console.error(Validation failed: ${JSON.stringify(errors.value)});
      emit('operationFailed', errors.value || { global: "Vérifiez les champs et réessayez." });
      return;
    }

    try {
      const method = itemToSave.id ? 'patch' : 'post';
      const response = await apiClient[method](apiUrl.value, itemToSave);

      console.log(API response: ${JSON.stringify(response)});

      if (response.data.status === 'success') {
        emit('itemSaved', itemToSave);
        closeModal();
        fetchItems();
      } else {
        console.error(Error from API: ${response.data.message});
        emit('operationFailed', response.data.errors || { global: "Une erreur inattendue s'est produite." });
      }
    } catch (error) {
      console.error(Error during API call: ${error});
      emit('operationFailed', { global: error.message || "Erreur de communication avec le serveur." });
    }
    console.log(Final notes value before sending to API: ${itemToSave.notes});
  }
};

const saveItem = async (item) => {
  errors.value = {}
  if (!validateFields()) {
    return { success: false, errors: errors.value }
  }

  const itemToSend = { ...item }

  try {
    const method = item.id ? 'patch' : 'post'
    const url = item.id ? ${props.apiUrls.update}/${item.id} : props.apiUrls.create
    await apiClient[method](url, itemToSend)
    return { success: true }
  } catch (error) {
    errorMessage.value = Error saving item: ${error.message}
    return { success: false }
  }
}

const handleActionClick = (action, item) => {
  emit('itemAction', { action, item });
};

const validateFields = () => {
  let isValid = true;
  errors.value = {};

  props.modalForms.default.fields.forEach(field => {
    if (field.required && (currentItem.value[field.name] === undefined || currentItem.value[field.name] === '')) {
      errors.value[field.name] = field.errorMessage || 'Ce champ est requis';
      isValid = false;
    }
  });

  return isValid;
};

const validateEmail = (email) => {
  const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return pattern.test(email);
}

const confirmDelete = (id) => {
  idToDelete.value = id;
  showConfirmDeleteModal.value = true;
}

const deleteItem = async () => {
  if (!idToDelete.value) return;

  try {
    let deleteUrl;
    if (typeof props.apiUrls.delete === 'function') {
      deleteUrl = props.apiUrls.delete(idToDelete.value);
    } else {
      deleteUrl = ${props.apiUrls.delete}/${idToDelete.value};
    }
    await apiClient.delete(deleteUrl);
    items.value = items.value.filter(item => item.id !== idToDelete.value);
    showConfirmDeleteModal.value = false;
    emit('itemDeleted', idToDelete.value);
  } catch (error) {
    errorMessage.value = Erreur lors de la suppression de l'élément : ${error.message};
    showConfirmDeleteModal.value = false;
  }
};


function getClass(field) {
  return field.class || 'col-md-12';
}

if (props.watchProperty) {
  watch(() => currentItem.value[props.watchProperty], (newValue, oldValue) => {
    emit('propertyChanged', { property: props.watchProperty, newValue });
  });
}

watch(() => props.items, (newItems, oldItems) => {
  if (newItems !== oldItems) {
    items.value = [...newItems];
  }
}, { deep: true });
onMounted(fetchItems)
</script>

<template>
  <div>
    <AppCustomAlert v-if="showSuccessAlert" :message="successMessage" type="success" style="text-align: center;" />
    <CRow v-if="showTable" class="justify-content-center mt-4">
      <CCol :xs="12">
        <CCard class="shadow-card">
          <CCardHeader class="d-flex justify-content-between align-items-center">
            <div><i>{{ title }}</i></div>
            <CButton v-if="showAddButton" color="success" variant="outline" class="btn-add hover-text-white"
              @click="openModal()">
              <div class="d-flex align-items-center hover-text-white">
                <CIcon icon="cil-plus" class="me-2 hover-text-white" /> Ajouter {{ itemType }}
              </div>
            </CButton>
            <div v-else class="placeholder-button"></div>
          </CCardHeader>
          <CCardBody>
            <AppCustomAlert v-if="errorMessage" :message="errorMessage" type="error" />
            <table class="table table-striped">
              <thead>
                <tr>
                  <th v-for="column in columns" :key="column.field" :class="column.class">{{ column.header }}</th>
                  <th v-if="showActions" class="actions-header">
                    <div class="d-flex justify-content-end actions-text">Actions</div>
                  </th>
                </tr>
              </thead>
              <tbody>
                <tr v-for="item in items" :key="item.id" @click="isRowClickable ? () => emit('rowClick', item) : null"
                  :class="isRowClickable ? 'clickable-row' : ''">
                  <td v-for="column in columns" :key="column.field" :class="column.class">
                    <span v-if="column.formatter" v-html="column.formatter(item)"></span>
                    <span v-else>{{ item[column.field] }}</span>
                  </td>
                  <td v-if="showActions" class="actions-cell">
                    <div class="d-flex justify-content-end">
                      <CButton v-for="action in customActions" :key="action.name"
                        @click="() => handleCustomAction(action.name, item)"
                        v-c-popover="{ content: action.tooltip, placement: 'left', trigger: 'hover' }"
                        :class="['custom-action-button', action.iconClass]">
                        <CIcon :icon="action.icon" :class="action.iconClass" />
                      </CButton>
                      <CButton v-if="customActions.length === 0" @click="openModal(item)"
                        v-c-popover="{ content: 'Modifier', placement: 'left', trigger: 'hover' }">
                        <CIcon icon="cil-pencil" class="icon-pencil me-2" />
                      </CButton>
                      <CButton v-if="customActions.length === 0" @click="confirmDelete(item.id)"
                        v-c-popover="{ content: 'Supprimer', placement: 'right', trigger: 'hover' }">
                        <CIcon icon="cil-trash" class="icon-trash" />
                      </CButton>
                    </div>
                  </td>
                </tr>
              </tbody>
            </table>
          </CCardBody>
        </CCard>
      </CCol>
    </CRow>
    <CModal v-model:visible="showModal" :class="modalClass" alignment="center" @close="closeModal">
      <CModalHeader @close="closeModal">
        <CModalTitle>{{ modalTitle }} {{ itemType }}</CModalTitle>
      </CModalHeader>
      <CModalBody>
        <template v-if="$slots['modal-body']">
          <slot name="modal-body" :currentItem="currentItem" :errors="errors"></slot>
        </template>
        <template v-else>
          <div class="row">
            <slot name="customBody" :currentItem="currentItem" :errors="errors">
              <template v-if="props.modalType === 'customModal'">
                <div v-for="field in modalForms.customModal.fields"
                    :key="field.name" :class="['mb-3', getClass(field)]">
                  <label :for="field.name" class="form-label">{{ field.label }}</label>
                  <input v-if="field.type === 'text'" :type="field.type" :id="field.name" v-model="currentItem[field.name]"
                    :class="['form-control', { 'is-invalid': errors[field.name] }]" :required="field.required"
                    :disabled="field.disabled || false" />
                </div>
              </template>
              <template v-else>
                <div v-for="field in modalForms.default.fields" :key="field.name" :class="['mb-3', getClass(field)]">
                  <label :for="field.name" class="form-label" v-if="field.type !== 'checkboxes'">{{ field.label }}</label>
                  <select v-if="field.type === 'select'" :id="field.name" v-model="currentItem[field.name]"
                    :class="['form-control', { 'is-invalid': errors[field.name] }]" :required="field.required">
                    <option disabled value="">{{ field.placeholder }}</option>
                    <option v-for="option in field.options" :key="option.value" :value="option.value">{{ option.text }}</option>
                  </select>
                  <input v-if="field.type === 'text'" :type="field.type" :id="field.name"
                    v-model="currentItem[field.name]" :class="['form-control', { 'is-invalid': errors[field.name] }]"
                    :required="field.required" />
                  <input v-else-if="field.type === 'number' || field.type === 'date'" :type="field.type" :id="field.name"
                    v-model="currentItem[field.name]" :class="['form-control', { 'is-invalid': errors[field.name] }]"
                    :required="field.required" />
                  <div v-if="field.type === 'textarea'" class="form-group">
                    <textarea :id="field.name" v-model="props.newRegistration.notes" class="form-control" :rows="field.rows || 1" :placeholder="field.placeholder || ''"></textarea>
                  </div>
                  <div v-if="field.type === 'checkboxes'">
                    <div v-for="option in field.options" :key="option.id">
                      <input class="form-check-input" type="checkbox" :id="checkbox-${option.id}"
                            v-model="currentItem[field.name]" :value="option.id" :checked="option.checked">
                      <label class="form-check-label" :for="checkbox-${option.id}">{{ option.text }}</label>
                    </div>
                  </div>
                  <div v-if="errors[field.name]" class="invalid-feedback">
                    {{ errors[field.name] }}
                  </div>
                </div>
              </template>
            </slot>
          </div>
        </template>
      </CModalBody>
      <CModalFooter>
        <CButton class="btn-box-shadow btn-secondary" @click="closeModal">Annuler</CButton>
        <CButton class="btn-box-shadow btn-primary" @click="handleSaveItem">{{ modalButtonLabel }}</CButton>
      </CModalFooter>
    </CModal>
    <CModal v-model:visible="showConfirmDeleteModal" alignment="center" @close="() => showConfirmDeleteModal = false">
      <CModalHeader>
        <CModalTitle>Confirmation de Suppression</CModalTitle>
      </CModalHeader>
      <CModalBody>
        Êtes-vous sûr de vouloir supprimer {{ itemTypeToDelete }} ?
      </CModalBody>
      <CModalFooter>
        <CButton class="btn-box-shadow btn-secondary" @click="() => showConfirmDeleteModal = false">Annuler</CButton>
        <CButton class="btn-box-shadow btn-danger white-text" @click="deleteItem">Supprimer</CButton>
      </CModalFooter>
    </CModal>
  </div>
</template>

<style scoped>
@import "@/styles/_applist.scss";
@import "@/styles/_header.scss";
</style>