diff --git a/thanasoft-front/src/services/index.ts b/thanasoft-front/src/services/index.ts index c90087e..cceb8b6 100644 --- a/thanasoft-front/src/services/index.ts +++ b/thanasoft-front/src/services/index.ts @@ -1,3 +1,4 @@ export * from "./http"; export { default as AuthService } from "./auth"; export { default as WebmailService } from "./webmail"; +export { default as SousTraitantService } from "./sousTraitant"; diff --git a/thanasoft-front/src/services/sousTraitant.ts b/thanasoft-front/src/services/sousTraitant.ts new file mode 100644 index 0000000..e9387fa --- /dev/null +++ b/thanasoft-front/src/services/sousTraitant.ts @@ -0,0 +1,189 @@ +import { request } from "./http"; + +export type SousTraitantStatut = "actif" | "inactif" | "en_evaluation"; + +export interface SousTraitant { + id: number; + nom_entreprise: string; + siret: string | null; + forme_juridique: string | null; + code_ape: string | null; + adresse: string | null; + contact_principal: string; + telephone: string | null; + email: string | null; + site_web: string | null; + numero_contrat: string | null; + montant_contrat: string | number | null; + date_debut_contrat: string | null; + date_fin_contrat: string | null; + type_prestation: string | null; + conditions_paiement: string | null; + statut: SousTraitantStatut; + note_qualite: string | number | null; + certifications_labels: string[]; + created_at: string; + updated_at: string; +} + +export interface SousTraitantListResponse { + data: SousTraitant[]; + meta?: { + current_page: number; + last_page: number; + per_page: number; + total: number; + from?: number; + to?: number; + stats?: { + actif: number; + inactif: number; + en_evaluation: number; + }; + }; +} + +export interface SousTraitantResponse { + data: SousTraitant; +} + +export interface CreateSousTraitantPayload { + nom_entreprise: string; + siret?: string | null; + forme_juridique?: string | null; + code_ape?: string | null; + adresse?: string | null; + contact_principal: string; + telephone?: string | null; + email?: string | null; + site_web?: string | null; + numero_contrat?: string | null; + montant_contrat?: number | string | null; + date_debut_contrat?: string | null; + date_fin_contrat?: string | null; + type_prestation?: string | null; + conditions_paiement?: string | null; + statut?: SousTraitantStatut; + note_qualite?: number | string | null; + certifications_labels?: string[]; +} + +export interface UpdateSousTraitantPayload + extends Partial { + id: number; +} + +export const SousTraitantService = { + async getAllSousTraitants(params?: { + page?: number; + per_page?: number; + search?: string; + statut?: SousTraitantStatut; + sort_by?: string; + sort_direction?: "asc" | "desc"; + }): Promise { + const response = await request({ + url: "/api/sous-traitants", + method: "get", + params, + }); + + return response; + }, + + async getSousTraitant(id: number): Promise { + const response = await request({ + url: `/api/sous-traitants/${id}`, + method: "get", + }); + + return response; + }, + + async createSousTraitant( + payload: CreateSousTraitantPayload + ): Promise { + const formattedPayload = this.transformSousTraitantPayload(payload); + + const response = await request({ + url: "/api/sous-traitants", + method: "post", + data: formattedPayload, + }); + + return response; + }, + + async updateSousTraitant( + payload: UpdateSousTraitantPayload + ): Promise { + const { id, ...updateData } = payload; + const formattedPayload = this.transformSousTraitantPayload(updateData); + + const response = await request({ + url: `/api/sous-traitants/${id}`, + method: "put", + data: formattedPayload, + }); + + return response; + }, + + async deleteSousTraitant( + id: number + ): Promise<{ success?: boolean; message: string }> { + const response = await request<{ success?: boolean; message: string }>({ + url: `/api/sous-traitants/${id}`, + method: "delete", + }); + + return response; + }, + + transformSousTraitantPayload( + payload: Partial + ): Record { + const transformed: Record = { ...payload }; + + if ( + Array.isArray(transformed.certifications_labels) && + transformed.certifications_labels.length > 0 + ) { + transformed.certifications_labels = transformed.certifications_labels + .map((item) => (typeof item === "string" ? item.trim() : item)) + .filter(Boolean); + } + + Object.keys(transformed).forEach((key) => { + if (transformed[key] === undefined) { + delete transformed[key]; + } + }); + + return transformed; + }, + + async searchSousTraitants( + query: string, + params?: { + exact_match?: boolean; + } + ): Promise { + const response = await request<{ + data: SousTraitant[]; + count: number; + message: string; + }>({ + url: "/api/sous-traitants/searchBy", + method: "get", + params: { + name: query, + exact_match: params?.exact_match || false, + }, + }); + + return response.data; + }, +}; + +export default SousTraitantService; diff --git a/thanasoft-front/src/stores/sousTraitantStore.ts b/thanasoft-front/src/stores/sousTraitantStore.ts new file mode 100644 index 0000000..36a8d26 --- /dev/null +++ b/thanasoft-front/src/stores/sousTraitantStore.ts @@ -0,0 +1,345 @@ +import { defineStore } from "pinia"; +import { computed, ref } from "vue"; +import SousTraitantService from "@/services/sousTraitant"; + +import type { + CreateSousTraitantPayload, + SousTraitant, + SousTraitantStatut, + UpdateSousTraitantPayload, +} from "@/services/sousTraitant"; + +export const useSousTraitantStore = defineStore("sousTraitant", () => { + const sousTraitants = ref([]); + const currentSousTraitant = ref(null); + const loading = ref(false); + const error = ref(null); + const searchResults = ref([]); + const filters = ref<{ + page: number; + per_page: number; + search?: string; + statut?: SousTraitantStatut; + sort_by?: string; + sort_direction?: "asc" | "desc"; + }>({ + page: 1, + per_page: 10, + sort_by: "created_at", + sort_direction: "desc", + }); + + const pagination = ref({ + current_page: 1, + last_page: 1, + per_page: 10, + total: 0, + from: 0, + to: 0, + }); + + const allSousTraitants = computed(() => sousTraitants.value); + const activeSousTraitants = computed(() => + sousTraitants.value.filter((item) => item.statut === "actif") + ); + const inactiveSousTraitants = computed(() => + sousTraitants.value.filter((item) => item.statut === "inactif") + ); + const evaluatingSousTraitants = computed(() => + sousTraitants.value.filter((item) => item.statut === "en_evaluation") + ); + const isLoading = computed(() => loading.value); + const hasError = computed(() => error.value !== null); + const getError = computed(() => error.value); + const getPagination = computed(() => pagination.value); + const getSousTraitantById = computed(() => (id: number) => + sousTraitants.value.find((item) => item.id === id) + ); + + const setLoading = (isLoading: boolean) => { + loading.value = isLoading; + }; + + const setError = (err: string | null) => { + error.value = err; + }; + + const clearError = () => { + error.value = null; + }; + + const setSousTraitants = (items: SousTraitant[]) => { + sousTraitants.value = items; + }; + + const setCurrentSousTraitant = (item: SousTraitant | null) => { + currentSousTraitant.value = item; + }; + + const setSearchResults = (items: SousTraitant[]) => { + searchResults.value = items; + }; + + const setPagination = (meta: any) => { + if (meta) { + const getValue = (val: any) => (Array.isArray(val) ? val[0] : val); + pagination.value = { + current_page: Number(getValue(meta.current_page)) || 1, + last_page: Number(getValue(meta.last_page)) || 1, + per_page: Number(getValue(meta.per_page)) || 10, + total: Number(getValue(meta.total)) || 0, + from: Number(getValue(meta.from)) || 0, + to: Number(getValue(meta.to)) || 0, + }; + + filters.value = { + ...filters.value, + page: pagination.value.current_page, + per_page: pagination.value.per_page, + }; + } + }; + + const setFilters = (params?: { + page?: number; + per_page?: number; + search?: string; + statut?: SousTraitantStatut; + sort_by?: string; + sort_direction?: "asc" | "desc"; + }) => { + filters.value = { + ...filters.value, + ...params, + page: params?.page ?? filters.value.page ?? 1, + per_page: params?.per_page ?? filters.value.per_page ?? 10, + }; + }; + + const fetchSousTraitants = async (params?: { + page?: number; + per_page?: number; + search?: string; + statut?: SousTraitantStatut; + sort_by?: string; + sort_direction?: "asc" | "desc"; + }) => { + setLoading(true); + setError(null); + + try { + setFilters(params); + + const requestParams = Object.fromEntries( + Object.entries(filters.value).filter( + ([, value]) => value !== undefined && value !== null && value !== "" + ) + ) as { + page?: number; + per_page?: number; + search?: string; + statut?: SousTraitantStatut; + sort_by?: string; + sort_direction?: "asc" | "desc"; + }; + + const response = + await SousTraitantService.getAllSousTraitants(requestParams); + setSousTraitants(response.data); + if (response.meta) { + setPagination(response.meta); + } + return response; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Failed to fetch sous-traitants"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + const fetchSousTraitant = async (id: number) => { + setLoading(true); + setError(null); + + try { + const response = await SousTraitantService.getSousTraitant(id); + setCurrentSousTraitant(response.data); + return response.data; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Failed to fetch sous-traitant"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + const createSousTraitant = async (payload: CreateSousTraitantPayload) => { + setLoading(true); + setError(null); + + try { + const response = await SousTraitantService.createSousTraitant(payload); + sousTraitants.value.push(response.data); + setCurrentSousTraitant(response.data); + return response.data; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Failed to create sous-traitant"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + const updateSousTraitant = async (payload: UpdateSousTraitantPayload) => { + setLoading(true); + setError(null); + + try { + const response = await SousTraitantService.updateSousTraitant(payload); + const updatedSousTraitant = response.data; + + const index = sousTraitants.value.findIndex( + (item) => item.id === updatedSousTraitant.id + ); + if (index !== -1) { + sousTraitants.value[index] = updatedSousTraitant; + } + + if ( + currentSousTraitant.value && + currentSousTraitant.value.id === updatedSousTraitant.id + ) { + setCurrentSousTraitant(updatedSousTraitant); + } + + return updatedSousTraitant; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Failed to update sous-traitant"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + const deleteSousTraitant = async (id: number) => { + setLoading(true); + setError(null); + + try { + const response = await SousTraitantService.deleteSousTraitant(id); + + sousTraitants.value = sousTraitants.value.filter((item) => item.id !== id); + + if (currentSousTraitant.value && currentSousTraitant.value.id === id) { + setCurrentSousTraitant(null); + } + + return response; + } catch (err: any) { + const errorMessage = + err.response?.data?.message || + err.message || + "Failed to delete sous-traitant"; + setError(errorMessage); + throw err; + } finally { + setLoading(false); + } + }; + + const searchSousTraitants = async ( + query: string, + exactMatch: boolean = false + ) => { + setLoading(true); + setError(null); + + try { + const results = await SousTraitantService.searchSousTraitants(query, { + exact_match: exactMatch, + }); + setSearchResults(results); + return results; + } catch (err) { + setError("Erreur lors de la recherche des sous-traitants"); + console.error("Error searching sous-traitants:", err); + setSearchResults([]); + throw err; + } finally { + setLoading(false); + } + }; + + const clearCurrentSousTraitant = () => { + setCurrentSousTraitant(null); + }; + + const clearStore = () => { + sousTraitants.value = []; + currentSousTraitant.value = null; + searchResults.value = []; + error.value = null; + pagination.value = { + current_page: 1, + last_page: 1, + per_page: 10, + total: 0, + from: 0, + to: 0, + }; + filters.value = { + page: 1, + per_page: 10, + sort_by: "created_at", + sort_direction: "desc", + }; + }; + + return { + sousTraitants, + currentSousTraitant, + loading, + error, + searchResults, + filters, + + allSousTraitants, + activeSousTraitants, + inactiveSousTraitants, + evaluatingSousTraitants, + isLoading, + hasError, + getError, + getPagination, + getSousTraitantById, + + fetchSousTraitants, + fetchSousTraitant, + createSousTraitant, + updateSousTraitant, + deleteSousTraitant, + searchSousTraitants, + clearCurrentSousTraitant, + clearStore, + clearError, + }; +}); + +export default useSousTraitantStore;