Minimal Viable Product + Refactor to pinia store + Fix PDF export
This commit is contained in:
360
resources/js/stores/resume.ts
Normal file
360
resources/js/stores/resume.ts
Normal file
@@ -0,0 +1,360 @@
|
||||
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 };
|
||||
Reference in New Issue
Block a user