220 lines
6.0 KiB
Vue
220 lines
6.0 KiB
Vue
<template>
|
|
<div class="card location-list-card">
|
|
<div class="card-header pb-0">
|
|
<div class="d-flex align-items-center">
|
|
<h6 class="mb-0">Liste des localisations</h6>
|
|
<soft-button
|
|
class="btn btn-primary btn-sm ms-auto"
|
|
@click="locationModalIsVisible = true"
|
|
>
|
|
<i class="fas fa-plus me-1"></i>Ajouter une localisation
|
|
</soft-button>
|
|
</div>
|
|
<location-modal
|
|
:is-visible="locationModalIsVisible"
|
|
:client-id="clientId"
|
|
:location-is-loading="isLoading"
|
|
:is-modification="isModification"
|
|
:location="selectedLocation"
|
|
@close="locationModalIsVisible = false"
|
|
@location-created="handleLocationCreated"
|
|
@location-modified="handleLocationModified"
|
|
/>
|
|
</div>
|
|
<div class="card-body location-list-body p-0">
|
|
<div v-if="locations.length > 0" class="table-responsive">
|
|
<table class="table align-items-center table-flush mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th
|
|
class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7"
|
|
>
|
|
Nom
|
|
</th>
|
|
<th
|
|
class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7 ps-2"
|
|
>
|
|
Adresse
|
|
</th>
|
|
<th
|
|
class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7 ps-2"
|
|
>
|
|
GPS Latitude
|
|
</th>
|
|
<th
|
|
class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7 ps-2"
|
|
>
|
|
GPS Longitude
|
|
</th>
|
|
<th
|
|
class="text-uppercase text-secondary text-xxs font-weight-bolder opacity-7 text-center"
|
|
>
|
|
Actions
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="location in locations" :key="location.id">
|
|
<td>
|
|
<div class="d-flex px-2 py-1">
|
|
<div>
|
|
<i class="fas fa-map-marker-alt text-primary me-2"></i>
|
|
</div>
|
|
<div class="d-flex flex-column justify-content-center">
|
|
<h6 class="mb-0 text-sm">{{ location.name || "-" }}</h6>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<p class="text-xs font-weight-bold mb-0">
|
|
{{ formatAddress(location) }}
|
|
</p>
|
|
</td>
|
|
<td>
|
|
<p class="text-xs font-weight-bold mb-0">
|
|
{{
|
|
location.gps_lat ? Number(location.gps_lat).toFixed(6) : "-"
|
|
}}
|
|
</p>
|
|
</td>
|
|
<td>
|
|
<p class="text-xs font-weight-bold mb-0">
|
|
{{
|
|
location.gps_lng ? Number(location.gps_lng).toFixed(6) : "-"
|
|
}}
|
|
</p>
|
|
</td>
|
|
<td class="align-middle text-center">
|
|
<div class="d-flex justify-content-center gap-2">
|
|
<button
|
|
class="btn btn-link text-warning p-0 mb-0"
|
|
type="button"
|
|
title="Modifier"
|
|
@click="handleModifyLocation(location)"
|
|
>
|
|
<i class="fas fa-edit text-sm"></i>
|
|
</button>
|
|
<button
|
|
class="btn btn-link text-danger p-0 mb-0"
|
|
type="button"
|
|
title="Supprimer"
|
|
@click="handleRemoveLocation(location.id)"
|
|
>
|
|
<i class="fas fa-trash text-sm"></i>
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div v-else class="text-center py-5">
|
|
<i
|
|
class="fas fa-map-marked-alt fa-3x text-secondary opacity-5 mb-3"
|
|
></i>
|
|
<p class="text-sm text-secondary">Aucune localisation pour ce client</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { defineProps, defineEmits, ref } from "vue";
|
|
import LocationModal from "../Location/LocationModal.vue";
|
|
import SoftButton from "@/components/SoftButton.vue";
|
|
|
|
defineProps({
|
|
locations: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
clientId: {
|
|
type: Number,
|
|
required: true,
|
|
},
|
|
isLoading: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
});
|
|
|
|
const locationModalIsVisible = ref(false);
|
|
const isModification = ref(false);
|
|
const selectedLocation = ref(null);
|
|
|
|
const emit = defineEmits([
|
|
"location-created",
|
|
"location-modified",
|
|
"location-removed",
|
|
]);
|
|
|
|
const handleLocationModified = (modifiedLocation) => {
|
|
locationModalIsVisible.value = false;
|
|
emit("location-modified", modifiedLocation);
|
|
};
|
|
|
|
const handleLocationCreated = (newLocation) => {
|
|
locationModalIsVisible.value = false;
|
|
emit("location-created", newLocation);
|
|
};
|
|
|
|
const handleModifyLocation = (location) => {
|
|
selectedLocation.value = location;
|
|
isModification.value = true;
|
|
locationModalIsVisible.value = true;
|
|
};
|
|
|
|
const handleRemoveLocation = (locationId) => {
|
|
if (confirm("Êtes-vous sûr de vouloir supprimer cette localisation ?")) {
|
|
emit("location-removed", locationId);
|
|
}
|
|
};
|
|
|
|
const formatAddress = (location) => {
|
|
const parts = [
|
|
location.address.line1,
|
|
location.address.line2,
|
|
location.address.postal_code,
|
|
location.address.city,
|
|
].filter(Boolean);
|
|
|
|
return parts.length > 0 ? parts.join(", ") : "-";
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.location-list-card {
|
|
min-height: 500px;
|
|
}
|
|
|
|
.location-list-body {
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.table-flush {
|
|
border-spacing: 0;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.table-flush thead th {
|
|
border-bottom: 1px solid #e9ecef;
|
|
padding: 0.75rem 1rem;
|
|
}
|
|
|
|
.table-flush tbody tr {
|
|
border-bottom: 1px solid #e9ecef;
|
|
}
|
|
|
|
.table-flush tbody td {
|
|
padding: 0.75rem 1rem;
|
|
}
|
|
|
|
.btn-link:hover {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.gap-2 {
|
|
gap: 0.5rem;
|
|
}
|
|
</style>
|