Files
CVAtron/resources/js/stores/resume.ts
Matthias Guillitte cb242e59ba
Some checks failed
linter / quality (push) Successful in 3m37s
tests / ci (push) Failing after 13m21s
Minimal Viable Product + Refactor to pinia store + Fix PDF export
2025-09-16 16:30:37 +02:00

361 lines
16 KiB
TypeScript

import { httpApi } from '@/lib/utils';
import { Resume, ResumeComponentData, ResumeComponentPlacement } from '@/types/resume';
import { set } from '@vueuse/core';
import { defineStore } from 'pinia'
import { computed, ComputedRef, watch } from 'vue';
const useResumesStore = defineStore('resumes', {
state: () => ({
resumes: [] as Array<Resume>,
resumesAreFetched: false as boolean,
currentResumeIndex: -1 as number,
selectedResumePlacementIndex: -1 as number,
}),
getters: {
hasResumes: (state) => computed(() => state.resumes.length > 0),
/* === CURRENT RESUME === */
currentResume(state): ComputedRef<Resume | null> {
console.debug("Current resume index : ", state.currentResumeIndex);
return computed(() => state.currentResumeIndex >= 0 ? state.resumes[state.currentResumeIndex] : null);
},
hasCurrentResume: (state) => computed(() => state.currentResumeIndex >= 0 && state.currentResumeIndex < state.resumes.length),
currentResumeName() {
return computed(() => {
const resume = this.currentResume;
return resume ? (resume.value?.name || 'Sans titre') : 'Sans titre';
});
},
/* === SELECTED RESUME PLACEMENT === */
hasCurrentSelectedResumePlacement(): ComputedRef<boolean> {
return computed(() => {
const currentResume = this.currentResume;
const selectedPlacementIndex = this.selectedResumePlacementIndex;
return currentResume !== null &&
selectedPlacementIndex >= 0 &&
selectedPlacementIndex < (currentResume.value?.components_placements?.length ?? 0);
});
},
currentSelectedResumePlacement(): ComputedRef<ResumeComponentPlacement | null> {
return computed(() => {
if (!this.hasCurrentSelectedResumePlacement.value) return null;
const currentResume = this.currentResume;
const selectedPlacementIndex = this.selectedResumePlacementIndex;
return currentResume.value.components_placements[selectedPlacementIndex];
});
},
},
actions: {
async fetchResumes() {
try {
this.resumesAreFetched = false;
// get from cache
const cachedResumes = localStorage.resumes;
if (cachedResumes) {
this.setResumes(JSON.parse(cachedResumes));
}
const { data: resumes, error } = await httpApi<Resume[]>(route("resumes.index"));
if (error || !resumes) {
console.error('Failed to fetch resumes:', error);
return;
}
this.setResumes(resumes);
// Store in cache
this.saveResumesToCache();
this.resumesAreFetched = true;
} catch (error) {
console.error('Failed to fetch resumes:', error);
}
},
async updateResumeToApi(resumeIndex: number) {
try {
if (!this.resumesAreFetched) {
console.warn("Resumes are not fetched yet. Cannot update to API.");
return;
}
if (resumeIndex < 0 || resumeIndex >= this.resumes.length) {
console.warn("Invalid resume index. Cannot update to API.");
return;
}
const resumeToUpdate = this.resumes[resumeIndex];
const { data: updatedResume, error } = await httpApi<null>(route("resumes.update", { resume: resumeToUpdate.id }), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '',
'Accept': 'application/json'
},
body: JSON.stringify(resumeToUpdate),
});
if (error || !updatedResume) {
console.error('Failed to update resumes:', error);
return;
}
} catch (error) {
console.error('Failed to update resumes:', error);
}
},
async updateCurrentResumeToApi() {
await this.updateResumeToApi(this.currentResumeIndex);
},
async saveResumesToCache() {
localStorage.resumes = JSON.stringify(this.resumes);
},
setResumes(resumes: Array<Resume>) {
this.resumes = resumes;
},
addResume(resume: Resume) {
this.resumes.push(resume);
this.currentResumeIndex = this.resumes.length - 1;
},
removeResume(index: number) {
if (index < 0 || index >= this.resumes.length) return;
this.resumes.splice(index, 1);
if (this.currentResumeIndex >= this.resumes.length) {
this.currentResumeIndex = this.resumes.length - 1;
}
},
removeResumeById(id: number) {
const index = this.resumes.findIndex(resume => resume.id === id);
if (index === -1) return;
this.removeResume(index);
},
setCurrentResume(index: number) {
if (index < 0 || index >= this.resumes.length) return;
this.currentResumeIndex = index;
},
setCurrentResumeById(id: number) {
const index = this.resumes.findIndex(resume => resume.id === id);
if (index === -1) return;
this.setCurrentResume(index);
},
setAndUpdateCurrentResume(resume: Resume) {
this.setCurrentResumeById(resume.id);
this.updateCurrentResume(resume);
},
setAndUpdateCurrentResumeWhenFetched(resume: Resume) {
watch(() => this.resumesAreFetched, (newVal) => {
if (newVal === true) {
this.setAndUpdateCurrentResume(resume);
}
});
},
updateCurrentResume(updatedResume: Resume) {
if (this.currentResumeIndex < 0 || this.currentResumeIndex >= this.resumes.length) return;
set(this.resumes, this.currentResumeIndex, updatedResume);
this.saveResumesToCache();
},
setCurrentResumeName(name: string) {
if (this.currentResumeIndex < 0 || this.currentResumeIndex >= this.resumes.length) return;
this.resumes[this.currentResumeIndex].name = name;
this.saveResumesToCache();
},
/* === RESUME COMPONENT PLACEMENTS === */
hasComponentsPlacements(): ComputedRef<boolean> {
return computed(() => {
const currentResume = this.currentResume;
return currentResume !== null && (currentResume.value?.components_placements?.length ?? 0) > 0;
});
},
async updateCurrentResumePlacementsToApi(index: number) {
// resume-component-placements.update
try {
if (!this.hasCurrentResume.value) {
console.warn("No current resume selected. Cannot update placements to API.");
return;
}
const currentResume = this.currentResume.value;
if (!currentResume) {
console.warn("Current resume is null. Cannot update placements to API.");
return;
}
const componentPlacements = currentResume.components_placements;
if (!componentPlacements) {
console.warn("Current resume has no components placements. Cannot update placements to API.");
return;
}
const componentPlacement = componentPlacements[index];
if (!componentPlacement) {
console.warn("Invalid component placement index. Cannot update placements to API.");
return;
}
console.debug("Updating component placement:", componentPlacement);
const { data: updatedPlacement, error } = await httpApi<ResumeComponentPlacement>(route("resume-component-placements.update", componentPlacement.id), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '',
'Accept': 'application/json'
},
body: JSON.stringify(componentPlacement),
});
if (error || !updatedPlacement) {
console.error('Failed to update resume placements:', error);
return;
}
// Update the local state with the updated placement
this.modifyResumePlacements(index, updatedPlacement);
this.saveResumesToCache();
} catch (error) {
console.error('Failed to update resume placements:', error);
}
},
setSelectedResumePlacement(index: number) {
const currentResume = this.currentResume;
if (!this.hasCurrentResume.value || !currentResume.value?.components_placements) {
this.selectedResumePlacementIndex = -1;
return;
}
if (index < 0 || index >= currentResume.value.components_placements.length) {
this.selectedResumePlacementIndex = -1;
return;
}
this.selectedResumePlacementIndex = index;
},
setSelectedResumePlacementById(id: number) {
if (!this.hasCurrentResume.value || !this.currentResume.value?.components_placements) return;
const resumePlacementIndex = this.currentResume.value?.components_placements.findIndex(placement => placement.id === id) ?? -1;
this.setSelectedResumePlacement(resumePlacementIndex);
},
modifyResumePlacements(index:number, modifiedPlacement: ResumeComponentPlacement) {
if (!this.hasCurrentResume.value || !this.currentResume.value?.components_placements) return;
const currentResume = this.currentResume.value;
set(currentResume.components_placements!, index, modifiedPlacement);
},
modifyCurrentSelectedResumePlacement(updatedPlacement: ResumeComponentPlacement) {
if (!this.hasCurrentSelectedResumePlacement.value) return;
this.modifyResumePlacements(this.selectedResumePlacementIndex, updatedPlacement);
},
swapComponentsPlacementsOrder(indexA: number, indexB: number) {
if (!this.hasCurrentResume.value || !this.currentResume.value?.components_placements) return;
const currentResume = this.currentResume.value;
const placements = currentResume.components_placements!;
if (indexA < 0 || indexA >= placements.length || indexB < 0 || indexB >= placements.length) return;
// Swap the order values
const tempOrder = placements[indexA].order;
placements[indexA].order = placements[indexB].order;
placements[indexB].order = tempOrder;
// Swap the placements in the array
[placements[indexA], placements[indexB]] = [placements[indexB], placements[indexA]];
// Update the components placements
this.updateCurrentResumePlacementsToApi(indexA);
this.updateCurrentResumePlacementsToApi(indexB);
},
async unlinkComponentPlacement(index: number) {
if (!this.hasComponentsPlacements) return;
const currentResume = this.currentResume.value!;
if (index < 0 || index >= currentResume.components_placements!.length) return;
// Call the 'resume-component-placements.unlink' API endpoint
// It will return the new component_data that need to be set to the placement
const placementToUnlink = currentResume.components_placements![index];
try {
const { data: newComponentData, error } = await httpApi<ResumeComponentData>(route("resume-component-placements.unlink", placementToUnlink.id), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '',
'Accept': 'application/json'
},
});
if (error || !newComponentData) {
console.error('Failed to unlink resume placement:', error);
return;
}
const updatedPlacement = { ...placementToUnlink, component_data: newComponentData };
// Update the local state with the updated placement
this.modifyResumePlacements(index, updatedPlacement);
this.saveResumesToCache();
} catch (error) {
console.error('Failed to unlink resume placement:', error);
}
},
async deleteComponentPlacement(index: number) {
if (!this.hasComponentsPlacements) return;
const currentResume = this.currentResume.value!;
if (index < 0 || index >= currentResume.components_placements!.length) return;
const placementToDelete = currentResume.components_placements![index];
try {
const { error } = await httpApi<null>(route("resume-component-placements.destroy", placementToDelete.id), {
method: 'DELETE',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '',
'Accept': 'application/json'
},
});
if (error) {
console.error('Failed to delete resume placement:', error);
return;
}
// Remove the placement from the local state
currentResume.components_placements!.splice(index, 1);
this.saveResumesToCache();
// Clear selected placement if it was the deleted one
if (this.selectedResumePlacementIndex === index) {
this.clearSelectedResumePlacement();
}
} catch (error) {
console.error('Failed to delete resume placement:', error);
}
},
clearSelectedResumePlacement() {
this.selectedResumePlacementIndex = -1;
},
},
});
// const useCurrentResumeStore = defineStore('currentResume', {
// state: () => ({
// currentResume: null as Resume | null,
// }),
// getters: {
// hasCurrentResume: (state) => computed(() => state.currentResume !== null),
// currentResumeName: (state) => computed(() => state.currentResume?.name ?? 'Sans titre'),
// },
// actions: {
// setCurrentResume(resume: Resume) {
// this.currentResume = resume;
// },
// setResumeName(name: string) {
// if (this.currentResume) {
// this.currentResume.name = name;
// }
// }
// },
// });
// const useSelectedResumePlacementStore = defineStore('selectedResumePlacement', {
// state: () => ({
// selectedResumePlacement: null as ResumeComponentPlacement | null,
// }),
// actions: {
// setSelectedResumePlacement(placement: ResumeComponentPlacement | null) {
// this.selectedResumePlacement = placement;
// },
// },
// });
export { useResumesStore };