From b62cb3d71761860689b6a982916536a99162bc71 Mon Sep 17 00:00:00 2001
From: Nyavokevin <42602932+nyavokevin@users.noreply.github.com>
Date: Tue, 21 Oct 2025 12:38:07 +0300
Subject: [PATCH] add location
---
.../Organism/CRM/ClientDetailPresentation.vue | 33 +-
.../CRM/client/ClientDetailContent.vue | 43 +-
.../CRM/client/ClientDetailSidebar.vue | 5 +
.../molecules/client/ClientLocationsTab.vue | 192 ++++++++
.../molecules/client/ClientTabNavigation.vue | 11 +
.../molecules/location/LocationModal.vue | 273 +++++++++++
.../src/services/client_location.ts | 272 +++++++++++
thanasoft-front/src/stores/clientLocation.ts | 456 ++++++++++++++++++
.../src/views/pages/CRM/ClientDetails.vue | 46 ++
9 files changed, 1328 insertions(+), 3 deletions(-)
create mode 100644 thanasoft-front/src/components/molecules/client/ClientLocationsTab.vue
create mode 100644 thanasoft-front/src/components/molecules/location/LocationModal.vue
create mode 100644 thanasoft-front/src/services/client_location.ts
create mode 100644 thanasoft-front/src/stores/clientLocation.ts
diff --git a/thanasoft-front/src/components/Organism/CRM/ClientDetailPresentation.vue b/thanasoft-front/src/components/Organism/CRM/ClientDetailPresentation.vue
index d93e354..524ee81 100644
--- a/thanasoft-front/src/components/Organism/CRM/ClientDetailPresentation.vue
+++ b/thanasoft-front/src/components/Organism/CRM/ClientDetailPresentation.vue
@@ -23,7 +23,8 @@
:initials="getInitials(client.name)"
:client-name="client.name"
:client-type="client.type_label || 'Client'"
- :contacts-count="client.length"
+ :contacts-count="contacts.length"
+ :locations-count="locations.length"
:is-active="client.is_active"
:active-tab="activeTab"
@edit-avatar="triggerFileInput"
@@ -44,12 +45,17 @@
:active-tab="activeTab"
:client="client"
:contacts="contacts"
+ :locations="locations"
:formatted-address="formatAddress(client)"
:client-id="client.id"
:contact-is-loading="contactLoading"
+ :location-is-loading="locationLoading"
@change-tab="activeTab = $event"
@updating-client="handleUpdateClient"
@create-contact="handleAddContact"
+ @create-location="handleAddLocation"
+ @modify-location="handleModifyLocation"
+ @remove-location="handleRemoveLocation"
/>
@@ -71,6 +77,11 @@ const props = defineProps({
required: false,
default: () => [],
},
+ locations: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
isLoading: {
type: Boolean,
default: false,
@@ -91,6 +102,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
+ locationLoading: {
+ type: Boolean,
+ default: false,
+ },
});
const localAvatar = ref(props.clientAvatar);
@@ -99,6 +114,9 @@ const emit = defineEmits([
"updateTheClient",
"handleFileInput",
"add-new-contact",
+ "add-new-location",
+ "modify-location",
+ "remove-location",
]);
const handleAvatarUpload = (event) => {
@@ -123,10 +141,21 @@ const inputFile = () => {
};
const handleAddContact = (data) => {
- // TODO: Implement add contact functionality
emit("add-new-contact", data);
};
+const handleAddLocation = (data) => {
+ emit("add-new-location", data);
+};
+
+const handleModifyLocation = (location) => {
+ emit("modify-location", location);
+};
+
+const handleRemoveLocation = (locationId) => {
+ emit("remove-location", locationId);
+};
+
const getInitials = (name) => {
if (!name) return "?";
return name
diff --git a/thanasoft-front/src/components/Organism/CRM/client/ClientDetailContent.vue b/thanasoft-front/src/components/Organism/CRM/client/ClientDetailContent.vue
index 865e619..a80e6a5 100644
--- a/thanasoft-front/src/components/Organism/CRM/client/ClientDetailContent.vue
+++ b/thanasoft-front/src/components/Organism/CRM/client/ClientDetailContent.vue
@@ -31,6 +31,18 @@
+
+
+
+
+
@@ -43,6 +55,7 @@ import ClientOverview from "@/components/molecules/client/ClientOverview.vue";
import ClientInfoTab from "@/components/molecules/client/ClientInfoTab.vue";
import ClientContactsTab from "@/components/molecules/client/ClientContactsTab.vue";
import ClientAddressTab from "@/components/molecules/client/ClientAddressTab.vue";
+import ClientLocationsTab from "@/components/molecules/client/ClientLocationsTab.vue";
import ClientNotesTab from "@/components/molecules/client/ClientNotesTab.vue";
import { defineProps, defineEmits } from "vue";
@@ -59,6 +72,10 @@ defineProps({
type: Array,
default: () => [],
},
+ locations: {
+ type: Array,
+ default: () => [],
+ },
formattedAddress: {
type: String,
default: "Aucune adresse renseignée",
@@ -71,9 +88,21 @@ defineProps({
type: Boolean,
default: false,
},
+ locationIsLoading: {
+ type: Boolean,
+ default: false,
+ },
});
-const emit = defineEmits(["change-tab", "create-contact", "updatingClient"]);
+const emit = defineEmits([
+ "change-tab",
+ "create-contact",
+ "updatingClient",
+ "create-location",
+ "modify-location",
+ "remove-location",
+]);
+
const updateClient = (updatedClient) => {
emit("updatingClient", updatedClient);
};
@@ -81,4 +110,16 @@ const updateClient = (updatedClient) => {
const handleCreateContact = (newContact) => {
emit("create-contact", newContact);
};
+
+const handleCreateLocation = (newLocation) => {
+ emit("create-location", newLocation);
+};
+
+const handleModifyLocation = (location) => {
+ emit("modify-location", location);
+};
+
+const handleRemoveLocation = (locationId) => {
+ emit("remove-location", locationId);
+};
diff --git a/thanasoft-front/src/components/Organism/CRM/client/ClientDetailSidebar.vue b/thanasoft-front/src/components/Organism/CRM/client/ClientDetailSidebar.vue
index 1574698..241bfc8 100644
--- a/thanasoft-front/src/components/Organism/CRM/client/ClientDetailSidebar.vue
+++ b/thanasoft-front/src/components/Organism/CRM/client/ClientDetailSidebar.vue
@@ -18,6 +18,7 @@
@@ -49,6 +50,10 @@ defineProps({
type: Number,
default: 0,
},
+ locationsCount: {
+ type: Number,
+ default: 0,
+ },
isActive: {
type: Boolean,
default: true,
diff --git a/thanasoft-front/src/components/molecules/client/ClientLocationsTab.vue b/thanasoft-front/src/components/molecules/client/ClientLocationsTab.vue
new file mode 100644
index 0000000..1f38e81
--- /dev/null
+++ b/thanasoft-front/src/components/molecules/client/ClientLocationsTab.vue
@@ -0,0 +1,192 @@
+
+
+
+
+
+
+
+
+ |
+ Nom
+ |
+
+ Adresse
+ |
+
+ GPS Latitude
+ |
+
+ GPS Longitude
+ |
+ Actions |
+
+
+
+
+
+
+
+
+
+
+ {{ location.name || "-" }}
+
+
+ |
+
+
+ {{ formatAddress(location) }}
+
+ |
+
+
+ {{ location.gps_lat ? Number(location.gps_lat).toFixed(6) : "-" }}
+
+ |
+
+
+ {{ location.gps_lng ? Number(location.gps_lng).toFixed(6) : "-" }}
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+ Aucune localisation pour ce client
+
+
+
+
+
+
+
+
+
diff --git a/thanasoft-front/src/components/molecules/client/ClientTabNavigation.vue b/thanasoft-front/src/components/molecules/client/ClientTabNavigation.vue
index d318467..ec9cd3e 100644
--- a/thanasoft-front/src/components/molecules/client/ClientTabNavigation.vue
+++ b/thanasoft-front/src/components/molecules/client/ClientTabNavigation.vue
@@ -26,6 +26,13 @@
:is-active="activeTab === 'address'"
@click="$emit('change-tab', 'address')"
/>
+
+
+
+
+
+
+
diff --git a/thanasoft-front/src/services/client_location.ts b/thanasoft-front/src/services/client_location.ts
new file mode 100644
index 0000000..c5aa3c1
--- /dev/null
+++ b/thanasoft-front/src/services/client_location.ts
@@ -0,0 +1,272 @@
+import { request } from "./http";
+
+export interface ClientLocationAddress {
+ address_line1: string | null;
+ address_line2: string | null;
+ postal_code: string | null;
+ city: string | null;
+ country_code: string;
+ full_address?: string;
+}
+
+export interface ClientLocation {
+ id: number;
+ client_id: number;
+ name: string | null;
+ address_line1: string | null;
+ address_line2: string | null;
+ postal_code: string | null;
+ city: string | null;
+ country_code: string;
+ gps_lat: number | null;
+ gps_lng: number | null;
+ code_portail: string | null;
+ code_alarm: string | null;
+ code_funeraire: string | null;
+ is_default: boolean;
+ full_address?: string;
+ gps_coordinates?: {
+ lat: number;
+ lng: number;
+ } | null;
+ created_at: string;
+ updated_at: string;
+}
+
+export interface ClientLocationListResponse {
+ data: ClientLocation[];
+ meta?: {
+ current_page: number;
+ last_page: number;
+ per_page: number;
+ total: number;
+ };
+}
+
+export interface ClientLocationResponse {
+ data: ClientLocation;
+}
+
+export interface CreateClientLocationPayload {
+ client_id: number;
+ name: string;
+ address_line1?: string | null;
+ address_line2?: string | null;
+ postal_code?: string | null;
+ city?: string | null;
+ country_code?: string;
+ gps_lat?: number | null;
+ gps_lng?: number | null;
+ code_portail?: string | null;
+ code_alarm?: string | null;
+ code_funeraire?: string | null;
+ is_default?: boolean;
+}
+
+export interface UpdateClientLocationPayload
+ extends Partial {
+ id: number;
+}
+
+export const ClientLocationService = {
+ /**
+ * Get all client locations with pagination
+ */
+ async getAllClientLocations(params?: {
+ page?: number;
+ per_page?: number;
+ client_id?: number;
+ is_default?: boolean;
+ search?: string;
+ }): Promise {
+ const response = await request({
+ url: "/api/client-locations",
+ method: "get",
+ params,
+ });
+
+ return response;
+ },
+
+ /**
+ * Get a specific client location by ID
+ */
+ async getClientLocation(id: number): Promise {
+ const response = await request({
+ url: `/api/client-locations/${id}`,
+ method: "get",
+ });
+
+ return response;
+ },
+
+ /**
+ * Create a new client location
+ */
+ async createClientLocation(
+ payload: CreateClientLocationPayload
+ ): Promise {
+ const formattedPayload = this.transformClientLocationPayload(payload);
+
+ const response = await request({
+ url: "/api/client-locations",
+ method: "post",
+ data: formattedPayload,
+ });
+
+ return response;
+ },
+
+ /**
+ * Update an existing client location
+ */
+ async updateClientLocation(
+ payload: UpdateClientLocationPayload
+ ): Promise {
+ const { id, ...updateData } = payload;
+ const formattedPayload = this.transformClientLocationPayload(updateData);
+
+ const response = await request({
+ url: `/api/client-locations/${id}`,
+ method: "put",
+ data: formattedPayload,
+ });
+
+ return response;
+ },
+
+ /**
+ * Delete a client location
+ */
+ async deleteClientLocation(
+ id: number
+ ): Promise<{ success: boolean; message: string }> {
+ const response = await request<{ success: boolean; message: string }>({
+ url: `/api/client-locations/${id}`,
+ method: "delete",
+ });
+
+ return response;
+ },
+
+ /**
+ * Set a location as default for a client
+ */
+ async setAsDefaultLocation(id: number): Promise {
+ const response = await request({
+ url: `/api/client-locations/${id}/set-default`,
+ method: "patch",
+ });
+
+ return response;
+ },
+
+ /**
+ * Get the default location for a client
+ */
+ async getDefaultClientLocation(
+ clientId: number
+ ): Promise {
+ try {
+ const response = await request({
+ url: "/api/client-locations",
+ method: "get",
+ params: {
+ client_id: clientId,
+ is_default: true,
+ per_page: 1,
+ },
+ });
+
+ return response.data.length > 0 ? { data: response.data[0] } : null;
+ } catch (error) {
+ console.error("Error fetching default location:", error);
+ return null;
+ }
+ },
+
+ /**
+ * Search client locations by name, address, or city
+ */
+ async getClientLocations(query: string): Promise {
+ const response = await request<{
+ data: ClientLocation[];
+ }>({
+ url: `/api/clients/${query}/locations`,
+ method: "get",
+ });
+
+ return response.data;
+ },
+
+ /**
+ * Transform client location payload to match Laravel form request structure
+ */
+ transformClientLocationPayload(
+ payload: Partial
+ ): any {
+ const transformed: any = { ...payload };
+
+ // Ensure boolean values are properly formatted
+ if (typeof transformed.is_default === "boolean") {
+ transformed.is_default = transformed.is_default ? 1 : 0;
+ }
+
+ // Set default country code if not provided
+ if (!transformed.country_code) {
+ transformed.country_code = "FR";
+ }
+
+ // Remove undefined values to avoid sending them
+ Object.keys(transformed).forEach((key) => {
+ if (transformed[key] === undefined) {
+ delete transformed[key];
+ }
+ });
+
+ return transformed;
+ },
+
+ /**
+ * Bulk update client locations
+ */
+ async bulkUpdateLocations(
+ updates: Array<{
+ id: number;
+ name?: string;
+ is_default?: boolean;
+ }>
+ ): Promise<{ success: boolean; message: string }> {
+ const response = await request<{ success: boolean; message: string }>({
+ url: "/api/client-locations/bulk-update",
+ method: "patch",
+ data: { updates },
+ });
+
+ return response;
+ },
+
+ /**
+ * Validate address using GPS coordinates
+ */
+ async validateAddress(
+ locationId: number
+ ): Promise<{
+ valid: boolean;
+ coordinates: { lat: number; lng: number } | null;
+ message: string;
+ }> {
+ const response = await request<{
+ valid: boolean;
+ coordinates: { lat: number; lng: number } | null;
+ message: string;
+ }>({
+ url: `/api/client-locations/${locationId}/validate-address`,
+ method: "get",
+ });
+
+ return response;
+ },
+};
+
+export default ClientLocationService;
diff --git a/thanasoft-front/src/stores/clientLocation.ts b/thanasoft-front/src/stores/clientLocation.ts
new file mode 100644
index 0000000..6bad766
--- /dev/null
+++ b/thanasoft-front/src/stores/clientLocation.ts
@@ -0,0 +1,456 @@
+import { defineStore } from "pinia";
+import { ref, computed } from "vue";
+import ClientLocationService from "@/services/client_location";
+
+import type {
+ ClientLocation,
+ CreateClientLocationPayload,
+ UpdateClientLocationPayload,
+ ClientLocationListResponse,
+} from "@/services/client_location";
+
+export const useClientLocationStore = defineStore("clientLocation", () => {
+ // State
+ const clientLocations = ref([]);
+ const currentClientLocation = ref(null);
+ const loading = ref(false);
+ const error = ref(null);
+ const searchResults = ref([]);
+
+ // Pagination state
+ const pagination = ref({
+ current_page: 1,
+ last_page: 1,
+ per_page: 10,
+ total: 0,
+ });
+
+ // Getters
+ const allClientLocations = computed(() => clientLocations.value);
+ const defaultLocations = computed(() =>
+ clientLocations.value.filter((location) => location.is_default)
+ );
+ const nonDefaultLocations = computed(() =>
+ clientLocations.value.filter((location) => !location.is_default)
+ );
+ const isLoading = computed(() => loading.value);
+ const hasError = computed(() => error.value !== null);
+ const getError = computed(() => error.value);
+ const getLocationById = computed(() => (id: number) =>
+ clientLocations.value.find((location) => location.id === id)
+ );
+
+ const getDefaultLocationByClientId = computed(() => (clientId: number) =>
+ clientLocations.value.find(
+ (location) => location.client_id === clientId && location.is_default
+ )
+ );
+ const getPagination = computed(() => pagination.value);
+
+ // Actions
+ const setLoading = (isLoading: boolean) => {
+ loading.value = isLoading;
+ };
+
+ const setError = (err: string | null) => {
+ error.value = err;
+ };
+
+ const clearError = () => {
+ error.value = null;
+ };
+
+ const setClientLocations = (newLocations: ClientLocation[]) => {
+ clientLocations.value = newLocations;
+ };
+
+ const setCurrentClientLocation = (location: ClientLocation | null) => {
+ currentClientLocation.value = location;
+ };
+
+ const setSearchResults = (results: ClientLocation[]) => {
+ searchResults.value = results;
+ };
+
+ const setPagination = (meta: any) => {
+ if (meta) {
+ pagination.value = {
+ current_page: meta.current_page || 1,
+ last_page: meta.last_page || 1,
+ per_page: meta.per_page || 10,
+ total: meta.total || 0,
+ };
+ }
+ };
+
+ /**
+ * Fetch all client locations with optional pagination and filters
+ */
+ const fetchClientLocations = async (params?: {
+ page?: number;
+ per_page?: number;
+ client_id?: number;
+ is_default?: boolean;
+ search?: string;
+ }) => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const response = await ClientLocationService.getAllClientLocations(
+ params
+ );
+ setClientLocations(response.data);
+ if (response.meta) {
+ setPagination(response.meta);
+ }
+ return response;
+ } catch (err: any) {
+ const errorMessage =
+ err.response?.data?.message ||
+ err.message ||
+ "Failed to fetch client locations";
+ setError(errorMessage);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ /**
+ * Fetch locations for a specific client
+ */
+ const fetchClientLocationsByClient = async (
+ clientId: number,
+ params?: {
+ page?: number;
+ per_page?: number;
+ is_default?: boolean;
+ }
+ ) => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const response = await ClientLocationService.getClientLocations(
+ clientId,
+ params
+ );
+ setClientLocations(response.data);
+ if (response.meta) {
+ setPagination(response.meta);
+ }
+ return response;
+ } catch (err: any) {
+ const errorMessage =
+ err.response?.data?.message ||
+ err.message ||
+ "Failed to fetch client locations";
+ setError(errorMessage);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ /**
+ * Fetch a single client location by ID
+ */
+ const fetchClientLocation = async (id: number) => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const response = await ClientLocationService.getClientLocation(id);
+ setCurrentClientLocation(response.data);
+ return response.data;
+ } catch (err: any) {
+ const errorMessage =
+ err.response?.data?.message ||
+ err.message ||
+ "Failed to fetch client location";
+ setError(errorMessage);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ /**
+ * Create a new client location
+ */
+ const createClientLocation = async (payload: CreateClientLocationPayload) => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const response = await ClientLocationService.createClientLocation(
+ payload
+ );
+ const newLocation = response.data;
+
+ // Add the new location to the list
+ clientLocations.value.push(newLocation);
+ setCurrentClientLocation(newLocation);
+
+ // If this location is set as default, update other locations
+ if (newLocation.is_default) {
+ clientLocations.value = clientLocations.value.map((location) =>
+ location.client_id === newLocation.client_id &&
+ location.id !== newLocation.id
+ ? { ...location, is_default: false }
+ : location
+ );
+ }
+
+ return newLocation;
+ } catch (err: any) {
+ const errorMessage =
+ err.response?.data?.message ||
+ err.message ||
+ "Failed to create client location";
+ setError(errorMessage);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ /**
+ * Update an existing client location
+ */
+ const updateClientLocation = async (payload: UpdateClientLocationPayload) => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const response = await ClientLocationService.updateClientLocation(
+ payload
+ );
+ const updatedLocation = response.data;
+
+ // Update in the locations list
+ const index = clientLocations.value.findIndex(
+ (location) => location.id === updatedLocation.id
+ );
+ if (index !== -1) {
+ clientLocations.value[index] = updatedLocation;
+ }
+
+ // Update current location if it's the one being edited
+ if (
+ currentClientLocation.value &&
+ currentClientLocation.value.id === updatedLocation.id
+ ) {
+ setCurrentClientLocation(updatedLocation);
+ }
+
+ // If this location is set as default, update other locations
+ if (updatedLocation.is_default) {
+ clientLocations.value = clientLocations.value.map((location) =>
+ location.client_id === updatedLocation.client_id &&
+ location.id !== updatedLocation.id
+ ? { ...location, is_default: false }
+ : location
+ );
+ }
+
+ return updatedLocation;
+ } catch (err: any) {
+ const errorMessage =
+ err.response?.data?.message ||
+ err.message ||
+ "Failed to update client location";
+ setError(errorMessage);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ /**
+ * Delete a client location
+ */
+ const deleteClientLocation = async (id: number) => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const response = await ClientLocationService.deleteClientLocation(id);
+
+ // Remove from the locations list
+ clientLocations.value = clientLocations.value.filter(
+ (location) => location.id !== id
+ );
+
+ // Clear current location if it's the one being deleted
+ if (
+ currentClientLocation.value &&
+ currentClientLocation.value.id === id
+ ) {
+ setCurrentClientLocation(null);
+ }
+
+ return response;
+ } catch (err: any) {
+ const errorMessage =
+ err.response?.data?.message ||
+ err.message ||
+ "Failed to delete client location";
+ setError(errorMessage);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ /**
+ * Set a location as default for a client
+ */
+ const setAsDefaultLocation = async (id: number) => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const response = await ClientLocationService.setAsDefaultLocation(id);
+ const updatedLocation = response.data;
+
+ // Update all locations for this client
+ clientLocations.value = clientLocations.value.map((location) =>
+ location.client_id === updatedLocation.client_id
+ ? {
+ ...location,
+ is_default: location.id === updatedLocation.id,
+ }
+ : location
+ );
+
+ // Update current location if it's the one being set as default
+ if (
+ currentClientLocation.value &&
+ currentClientLocation.value.id === updatedLocation.id
+ ) {
+ setCurrentClientLocation(updatedLocation);
+ }
+
+ return updatedLocation;
+ } catch (err: any) {
+ const errorMessage =
+ err.response?.data?.message ||
+ err.message ||
+ "Failed to set default location";
+ setError(errorMessage);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ /**
+ * Get the default location for a client
+ */
+ const fetchDefaultClientLocation = async (clientId: number) => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const response = await ClientLocationService.getDefaultClientLocation(
+ clientId
+ );
+ return response ? response.data : null;
+ } catch (err: any) {
+ const errorMessage =
+ err.response?.data?.message ||
+ err.message ||
+ "Failed to fetch default location";
+ setError(errorMessage);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const getClientLocations = async (clientId: number) => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const response = await ClientLocationService.getClientLocations(clientId);
+ return response;
+ } catch (err: any) {
+ const errorMessage =
+ err.response?.data?.message ||
+ err.message ||
+ "Failed to fetch default location";
+ setError(errorMessage);
+ throw err;
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ /**
+ * Clear current client location
+ */
+ const clearCurrentClientLocation = () => {
+ setCurrentClientLocation(null);
+ };
+
+ /**
+ * Clear search results
+ */
+ const clearSearchResults = () => {
+ searchResults.value = [];
+ };
+
+ /**
+ * Clear all state
+ */
+ const clearStore = () => {
+ clientLocations.value = [];
+ currentClientLocation.value = null;
+ error.value = null;
+ searchResults.value = [];
+ pagination.value = {
+ current_page: 1,
+ last_page: 1,
+ per_page: 10,
+ total: 0,
+ };
+ };
+
+ return {
+ // State
+ clientLocations,
+ currentClientLocation,
+ loading,
+ error,
+ searchResults,
+
+ // Getters
+ allClientLocations,
+ defaultLocations,
+ nonDefaultLocations,
+ isLoading,
+ hasError,
+ getError,
+ getLocationById,
+ getDefaultLocationByClientId,
+ getPagination,
+
+ // Actions
+ fetchClientLocations,
+ fetchClientLocationsByClient,
+ fetchClientLocation,
+ createClientLocation,
+ updateClientLocation,
+ deleteClientLocation,
+ setAsDefaultLocation,
+ fetchDefaultClientLocation,
+ clearCurrentClientLocation,
+ clearSearchResults,
+ clearStore,
+ clearError,
+ getClientLocations,
+ };
+});
diff --git a/thanasoft-front/src/views/pages/CRM/ClientDetails.vue b/thanasoft-front/src/views/pages/CRM/ClientDetails.vue
index 55919ff..4393e9d 100644
--- a/thanasoft-front/src/views/pages/CRM/ClientDetails.vue
+++ b/thanasoft-front/src/views/pages/CRM/ClientDetails.vue
@@ -3,13 +3,18 @@
v-if="clientStore.currentClient"
:client="clientStore.currentClient"
:contacts="contacts_client"
+ :locations="locations_client"
:is-loading="clientStore.isLoading"
:client-avatar="clientAvatar"
:active-tab="activeTab"
:file-input="fileInput"
:contact-loading="contactStore.isLoading"
+ :location-loading="clientLocationStore.isLoading"
@update-the-client="updateClient"
@add-new-contact="createNewContact"
+ @add-new-location="createNewLocation"
+ @modify-location="modifyLocation"
+ @remove-location="removeLocation"
/>
@@ -18,14 +23,18 @@ import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import { useClientStore } from "@/stores/clientStore";
import { useContactStore } from "@/stores/contactStore";
+import { useClientLocationStore } from "@/stores/clientLocation";
import ClientDetailPresentation from "@/components/Organism/CRM/ClientDetailPresentation.vue";
+
const route = useRoute();
const clientStore = useClientStore();
const contactStore = useContactStore();
+const clientLocationStore = useClientLocationStore();
// Ensure client_id is a number
const client_id = Number(route.params.id);
const contacts_client = ref([]);
+const locations_client = ref([]);
const activeTab = ref("overview");
const clientAvatar = ref(null);
const fileInput = ref(null);
@@ -34,6 +43,10 @@ onMounted(async () => {
if (client_id) {
await clientStore.fetchClient(client_id);
contacts_client.value = await contactStore.getClientListContact(client_id);
+ const locationsResponse = await clientLocationStore.getClientLocations(
+ client_id
+ );
+ locations_client.value = locationsResponse || [];
}
});
@@ -61,4 +74,37 @@ const createNewContact = async (data) => {
console.error("Error creating contact:", error);
}
};
+
+const createNewLocation = async (data) => {
+ try {
+ await clientLocationStore.createClientLocation(data);
+ // Refresh locations list after creation
+ const response = await clientLocationStore.getClientLocations(client_id);
+ locations_client.value = response || [];
+ } catch (error) {
+ console.error("Error creating location:", error);
+ }
+};
+
+const modifyLocation = async (location) => {
+ try {
+ await clientLocationStore.updateClientLocation(location);
+ // Refresh locations list after modification
+ const response = await clientLocationStore.getClientLocations(client_id);
+ locations_client.value = response || [];
+ } catch (error) {
+ console.error("Error modifying location:", error);
+ }
+};
+
+const removeLocation = async (locationId) => {
+ try {
+ await clientLocationStore.deleteClientLocation(locationId);
+ // Refresh locations list after deletion
+ const response = await clientLocationStore.getClientLocations(client_id);
+ locations_client.value = response || [];
+ } catch (error) {
+ console.error("Error removing location:", error);
+ }
+};