Compare commits
10 Commits
v0.0.43
...
43ec4e1d1b
| Author | SHA1 | Date | |
|---|---|---|---|
| 43ec4e1d1b | |||
| 785d3231dc | |||
| 8a4f135a3d | |||
| 2b1ee3be3b | |||
| 1e794bba00 | |||
| 83b339746e | |||
| d0a479d035 | |||
| 4a3114ca52 | |||
| b1c12138f1 | |||
| 4e2fe556be |
@@ -41,10 +41,6 @@ Ajouter dans le fichier .env du frontend
|
|||||||
* [#256] Créer une nouvelle réception (étape 3 - bovin)
|
* [#256] Créer une nouvelle réception (étape 3 - bovin)
|
||||||
* [#314] Création d'une page d'administration : listing des utilisateurs
|
* [#314] Création d'une page d'administration : listing des utilisateurs
|
||||||
* [#313] Admin modification creation fournisseur
|
* [#313] Admin modification creation fournisseur
|
||||||
* [#275] Lister les expéditions en attente
|
|
||||||
* [#276] Lister les expéditions terminées
|
|
||||||
* [#324] Creation page admin listing clients
|
|
||||||
* [#326] Admin modification creation client
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
parameters:
|
parameters:
|
||||||
app.version: '0.0.43'
|
app.version: '0.0.39'
|
||||||
|
|||||||
@@ -115,10 +115,6 @@
|
|||||||
"create": "Fournisseur créé avec succès.",
|
"create": "Fournisseur créé avec succès.",
|
||||||
"update": "Fournisseur mis à jour avec succès."
|
"update": "Fournisseur mis à jour avec succès."
|
||||||
},
|
},
|
||||||
"customer": {
|
|
||||||
"create": "Client créé avec succès.",
|
|
||||||
"update": "Client mis à jour avec succès."
|
|
||||||
},
|
|
||||||
"address": {
|
"address": {
|
||||||
"create": "Adresse créée avec succès.",
|
"create": "Adresse créée avec succès.",
|
||||||
"update": "Adresse mise à jour avec succès."
|
"update": "Adresse mise à jour avec succès."
|
||||||
|
|||||||
@@ -36,9 +36,6 @@
|
|||||||
<NuxtLink to="/admin/user/list">
|
<NuxtLink to="/admin/user/list">
|
||||||
Utilisateurs
|
Utilisateurs
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
<NuxtLink to="/admin/customer/customer-list">
|
|
||||||
Client
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
|
|||||||
@@ -1,192 +0,0 @@
|
|||||||
<template>
|
|
||||||
<form @submit.prevent="validate">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h1 class="text-3xl font-bold uppercase">
|
|
||||||
{{ customerId ? "Modifications du client" : "Ajout d'un client" }}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<button
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
type="submit"
|
|
||||||
:disabled="isLoading || !auth.isAdmin"
|
|
||||||
>
|
|
||||||
{{ customerId ? "Sauvegarder" : "Ajouter" }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-y-8 gap-x-80 mb-10 py-12">
|
|
||||||
<UiTextInput id="customer-label" v-model="form.label" label="Nom du client" :disabled="!auth.isAdmin"/>
|
|
||||||
<UiTextInput id="customer-code" v-model="form.code" label="Code" :disabled="!auth.isAdmin"/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mx-24 mb-4 py-6 border-t border-black"></div>
|
|
||||||
<div class="flex items-center justify-between mb-4">
|
|
||||||
<h2 class="text-3xl font-bold uppercase">Adresses client</h2>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
:disabled="customerId === null || !auth.isAdmin"
|
|
||||||
@click="goToAddAddress"
|
|
||||||
>
|
|
||||||
Ajouter
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="overflow-x-auto mb-10">
|
|
||||||
<table class="w-full border-collapse">
|
|
||||||
<thead>
|
|
||||||
<tr class="text-left border-b border-gray-200">
|
|
||||||
<th class="py-3 pr-4 text-sm uppercase">Libellé</th>
|
|
||||||
<th class="py-3 pr-4 text-sm uppercase">Rue</th>
|
|
||||||
<th class="py-3 pr-4 text-sm uppercase">Complément</th>
|
|
||||||
<th class="py-3 pr-4 text-sm uppercase">Code postal</th>
|
|
||||||
<th class="py-3 pr-4 text-sm uppercase">Ville</th>
|
|
||||||
<th class="py-3 pr-4 text-sm uppercase">Pays</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<template v-if="form.addresses.length === 0">
|
|
||||||
<tr>
|
|
||||||
<td colspan="6" class="py-4 text-slate-400">
|
|
||||||
Aucune adresse.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<tr
|
|
||||||
v-for="(address, index) in form.addresses"
|
|
||||||
:key="address.id ?? index"
|
|
||||||
class="border-b border-gray-100 hover:bg-slate-50"
|
|
||||||
:class="auth.isAdmin ? 'cursor-pointer' : 'cursor-not-allowed opacity-60'"
|
|
||||||
@click="goToEditAddress(address.id ?? null)"
|
|
||||||
>
|
|
||||||
<td class="py-3 pr-4">{{ address.label || "—" }}</td>
|
|
||||||
<td class="py-3 pr-4">{{ address.street || "—" }}</td>
|
|
||||||
<td class="py-3 pr-4">{{ address.street2 || "—" }}</td>
|
|
||||||
<td class="py-3 pr-4">{{ address.postalCode || "—" }}</td>
|
|
||||||
<td class="py-3 pr-4">{{ address.city || "—" }}</td>
|
|
||||||
<td class="py-3 pr-4">{{ address.countryCode || "—" }}</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {computed, reactive, ref, watch} from "vue"
|
|
||||||
import {createCustomer, getCustomer, updateCustomer} from "~/services/customer"
|
|
||||||
import type {CustomerData, CustomerFormData, CustomerPayload} from "~/services/dto/customer-data"
|
|
||||||
import {useAuthStore} from "~/stores/auth"
|
|
||||||
|
|
||||||
definePageMeta({layout: "admin"})
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
const auth = useAuthStore()
|
|
||||||
|
|
||||||
const resolveId = (param: unknown) => {
|
|
||||||
const idStr = Array.isArray(param) ? param[0] : param
|
|
||||||
if (!idStr) return null
|
|
||||||
const id = Number(idStr)
|
|
||||||
return Number.isFinite(id) ? id : null
|
|
||||||
}
|
|
||||||
const customerId = computed(() => resolveId(route.params.id))
|
|
||||||
const isLoading = ref(false)
|
|
||||||
const form = reactive<CustomerFormData>({
|
|
||||||
label: "",
|
|
||||||
code: "",
|
|
||||||
addresses: [],
|
|
||||||
})
|
|
||||||
|
|
||||||
const goToAddAddress = () => {
|
|
||||||
if (customerId.value === null || !auth.isAdmin) return
|
|
||||||
router.push({
|
|
||||||
path: "/admin/customer/address",
|
|
||||||
query: {
|
|
||||||
customerId: String(customerId.value),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToEditAddress = (addressId: number | null) => {
|
|
||||||
if (customerId.value === null || addressId === null || !auth.isAdmin) return
|
|
||||||
router.push({
|
|
||||||
path: "/admin/customer/address",
|
|
||||||
query: {
|
|
||||||
customerId: String(customerId.value),
|
|
||||||
addressId: String(addressId),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const hydrateFromCustomer = (customer: CustomerData | null) => {
|
|
||||||
if (!customer) return
|
|
||||||
form.label = customer.label ?? ""
|
|
||||||
form.code = customer.code ?? ""
|
|
||||||
if (!Array.isArray(customer.addresses) || customer.addresses.length === 0) {
|
|
||||||
form.addresses = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (typeof customer.addresses[0] === "string") {
|
|
||||||
form.addresses = []
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
form.addresses = customer.addresses.map((address) => ({
|
|
||||||
id: address.id ?? null,
|
|
||||||
label: address.label ?? "",
|
|
||||||
street: address.street ?? "",
|
|
||||||
street2: address.street2 ?? null,
|
|
||||||
postalCode: address.postalCode ?? "",
|
|
||||||
city: address.city ?? "",
|
|
||||||
countryCode: address.countryCode ?? "",
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => customerId.value,
|
|
||||||
async (id) => {
|
|
||||||
if (id === null) return
|
|
||||||
isLoading.value = true
|
|
||||||
try {
|
|
||||||
const customer = await getCustomer(id)
|
|
||||||
hydrateFromCustomer(customer)
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{immediate: true}
|
|
||||||
)
|
|
||||||
|
|
||||||
async function validate() {
|
|
||||||
if (isLoading.value) return
|
|
||||||
if (!auth.isAdmin) return
|
|
||||||
isLoading.value = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
const label = form.label.trim()
|
|
||||||
const code = form.code.trim()
|
|
||||||
|
|
||||||
const customerPayload: CustomerPayload = {
|
|
||||||
label,
|
|
||||||
code,
|
|
||||||
}
|
|
||||||
let targetId: number | null = null
|
|
||||||
|
|
||||||
if (customerId.value !== null) {
|
|
||||||
await updateCustomer(customerId.value, customerPayload)
|
|
||||||
targetId = customerId.value
|
|
||||||
} else {
|
|
||||||
const created = await createCustomer(customerPayload)
|
|
||||||
targetId = created.id
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetId !== null) {
|
|
||||||
await router.push(`/admin/customer/${targetId}`)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Address type="customer" :address="address" @validate="validate"/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { AddressData, AddressPayload } from "~/services/address"
|
|
||||||
import { createAddress, getAddress, updateAddress } from "~/services/address"
|
|
||||||
import { getCustomer, updateCustomer } from "~/services/customer"
|
|
||||||
import type { CustomerData } from "~/services/dto/customer-data"
|
|
||||||
|
|
||||||
definePageMeta({ layout: "admin" })
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
const customerId = computed(() => Number(route.query.customerId))
|
|
||||||
const customer = ref<CustomerData | null>(null)
|
|
||||||
const addressId = computed(() => (route.query.addressId !== undefined ? Number(route.query.addressId) : null))
|
|
||||||
const address = ref<AddressData | null>(null)
|
|
||||||
|
|
||||||
const validate = async (payload: AddressPayload) => {
|
|
||||||
try {
|
|
||||||
if (addressId.value !== null) {
|
|
||||||
await updateAddress(addressId.value, payload)
|
|
||||||
} else {
|
|
||||||
await addAddress(payload)
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
await router.push("/admin/customer/" + customerId.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const addAddress = async (payload: AddressPayload) => {
|
|
||||||
const response: AddressData = await createAddress(payload)
|
|
||||||
const addressIRI = `/api/addresses/${response.id}`
|
|
||||||
const existingIris = (customer.value?.addresses ?? [])
|
|
||||||
.map((item: any) => (typeof item === "string" ? item : `/api/addresses/${item.id}`))
|
|
||||||
.filter((iri: string | null) => Boolean(iri)) as string[]
|
|
||||||
const next = [...new Set([...existingIris, addressIRI])]
|
|
||||||
|
|
||||||
return await updateCustomer(customerId.value, { addresses: next })
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
customer.value = await getCustomer(customerId.value)
|
|
||||||
if (addressId.value !== null) {
|
|
||||||
address.value = await getAddress(addressId.value)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<h1 class="text-3xl font-bold uppercase">Liste des Clients</h1>
|
|
||||||
<NuxtLink
|
|
||||||
to="/admin/customer"
|
|
||||||
class="flex items-center justify-center text-xl uppercase bg-primary-500 text-white h-[50px] w-[272px]"
|
|
||||||
:class="auth.isAdmin ? '' : 'cursor-not-allowed opacity-60'"
|
|
||||||
@click="handleAddClick"
|
|
||||||
>
|
|
||||||
Ajouter
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="auth.isAdmin" class="mt-6 border border-slate-200 mb-16">
|
|
||||||
<div class="max-h-96 overflow-y-auto">
|
|
||||||
<div
|
|
||||||
class="sticky top-0 z-10 grid grid-cols-7 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"
|
|
||||||
>
|
|
||||||
<div>Nom</div>
|
|
||||||
<div>Code</div>
|
|
||||||
<div>Rue</div>
|
|
||||||
<div>Complément</div>
|
|
||||||
<div>Code Postal</div>
|
|
||||||
<div>Ville</div>
|
|
||||||
<div>Pays</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="customerList.length === 0" class="px-4 py-6 text-slate-400">
|
|
||||||
Aucun fournisseur.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-for="customer in customerList" :key="customer.id">
|
|
||||||
<div
|
|
||||||
v-if="!customer.addresses || customer.addresses.length === 0"
|
|
||||||
class="grid grid-cols-7 border-t gap-4 px-4 py-2 hover:bg-slate-50 cursor-pointer"
|
|
||||||
@click="goToCustomer(customer.id)"
|
|
||||||
>
|
|
||||||
<div class="truncate">{{ customer.label }}</div>
|
|
||||||
<div class="truncate">{{ customer.code }}</div>
|
|
||||||
<div class="col-span-1">Pas d'adresse</div>
|
|
||||||
<div class="uppercase truncate">{{"—"}}</div>
|
|
||||||
<div class="uppercase truncate">{{"—"}}</div>
|
|
||||||
<div class="uppercase truncate">{{"—"}}</div>
|
|
||||||
<div class="uppercase truncate">{{"—"}}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template v-else-if="customer.addresses.length > 0">
|
|
||||||
<div
|
|
||||||
v-for="(address, idx) in customer.addresses"
|
|
||||||
:key="address.id ?? `${customer.id}-${idx}-${address.street}-${address.postalCode}`"
|
|
||||||
class="grid grid-cols-7 hover:bg-slate-50 border-t gap-4 px-4 py-2 cursor-pointer"
|
|
||||||
:class="idx > 0 ? 'pl-4 border-l-4 border-l-slate-200 bg-slate-50' : ''"
|
|
||||||
@click="goToCustomer(customer.id)"
|
|
||||||
>
|
|
||||||
<div class="truncate">
|
|
||||||
{{ idx === 0 ? customer.label : "↳" }}
|
|
||||||
</div>
|
|
||||||
<div class="truncate">{{ idx === 0 ? customer.code : "" }}</div>
|
|
||||||
<div class="truncate">{{ address.street || "—" }}</div>
|
|
||||||
<div class="truncate">{{ address.street2 || "—" }}</div>
|
|
||||||
<div>{{ address.postalCode || "—" }}</div>
|
|
||||||
<div class="uppercase truncate">{{ address.city || "—" }}</div>
|
|
||||||
<div class="uppercase truncate">{{ address.countryCode || "—" }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<div
|
|
||||||
class="grid grid-cols-7 hover:bg-slate-50 border-t gap-4 px-4 py-2 cursor-pointer"
|
|
||||||
@click="goToCustomer(customer.id)"
|
|
||||||
>
|
|
||||||
<div class="truncate">{{ customer.label }}</div>
|
|
||||||
<div class="truncate">{{ customer.code }}</div>
|
|
||||||
<div class="col-span-5 text-slate-400">
|
|
||||||
Adresses non chargées
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="mt-6 border border-slate-200 mb-16 px-4 py-6 text-slate-400">
|
|
||||||
Accès réservé aux administrateurs.
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { getCustomerList } from "~/services/customer"
|
|
||||||
import type { CustomerData } from "~/services/dto/customer-data"
|
|
||||||
import { useAuthStore } from "~/stores/auth"
|
|
||||||
|
|
||||||
definePageMeta({ layout: "admin" })
|
|
||||||
|
|
||||||
const customerList = ref<CustomerData[]>([])
|
|
||||||
const router = useRouter()
|
|
||||||
const auth = useAuthStore()
|
|
||||||
|
|
||||||
const goToCustomer = (id: number) => {
|
|
||||||
if (!auth.isAdmin) return
|
|
||||||
router.push(`/admin/customer/${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAddClick = (event: Event) => {
|
|
||||||
if (auth.isAdmin) return
|
|
||||||
event.preventDefault()
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (!auth.isAdmin) return
|
|
||||||
customerList.value = await getCustomerList()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -6,10 +6,10 @@
|
|||||||
<card-link label="NOUVELLE EXPÉDITION" link="/shipment" iconName="mdi:truck-fast-outline" />
|
<card-link label="NOUVELLE EXPÉDITION" link="/shipment" iconName="mdi:truck-fast-outline" />
|
||||||
<card-link label="PLAN DE SITE" link="/" iconName="mdi:warehouse" />
|
<card-link label="PLAN DE SITE" link="/" iconName="mdi:warehouse" />
|
||||||
<card-link label="RÉCEPTIONS EN ATTENTE" link="/reception/waiting-reception" iconName="mdi:truck-remove-outline" />
|
<card-link label="RÉCEPTIONS EN ATTENTE" link="/reception/waiting-reception" iconName="mdi:truck-remove-outline" />
|
||||||
<card-link label="EXPÉDITIONS EN ATTENTE" link="/shipment/waiting-shipment" iconName="mdi:truck-cargo-container" />
|
<card-link label="EXPÉDITIONS EN ATTENTE" link="/" iconName="mdi:truck-cargo-container" />
|
||||||
<card-link label="CASES" link="/" iconName="mdi:cube-outline" />
|
<card-link label="CASES" link="/" iconName="mdi:cube-outline" />
|
||||||
<card-link label="RÉCEPTIONS FINIES" link="/reception/finish-reception" iconName="mdi:truck-check-outline" />
|
<card-link label="RÉCEPTIONS FINIES" link="/reception/finish-reception" iconName="mdi:truck-check-outline" />
|
||||||
<card-link label="EXPÉDITIONS FINIES" link="/shipment/finish-shipment" iconName="mdi:truck-delivery-outline" />
|
<card-link label="EXPÉDITIONS FINIES" link="/" iconName="mdi:truck-delivery-outline" />
|
||||||
<card-link label="PASSEPORT DU BOVIN" link="/" iconName="mdi:cow" />
|
<card-link label="PASSEPORT DU BOVIN" link="/" iconName="mdi:cow" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex items-center justify-start gap-10">
|
|
||||||
<Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44"/>
|
|
||||||
<h1 class="text-3xl font-bold uppercase">listes des expéditions finie</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ps-20 ">
|
|
||||||
<div class="mt-6 border border-slate-200 mb-16 ">
|
|
||||||
<div class="grid grid-cols-6 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
|
|
||||||
<div>Numéro</div>
|
|
||||||
<div>Date</div>
|
|
||||||
<div>Client</div>
|
|
||||||
<div>Adresse</div>
|
|
||||||
<div>Type d'expéditon</div>
|
|
||||||
<div>Poids</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-for="shipment in shipmentList"
|
|
||||||
:key="shipment
|
|
||||||
.id"
|
|
||||||
class="grid grid-cols-6 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
@click="goToshipment(shipment.id)"
|
|
||||||
>
|
|
||||||
<div>{{ shipment.identificationNumber }}</div>
|
|
||||||
<div>{{ shipment.shipmentDate }}</div>
|
|
||||||
<div>{{ shipment.customer?.label }}</div>
|
|
||||||
<div>{{ shipment.address?.fullAddress }}</div>
|
|
||||||
<div>
|
|
||||||
<template v-if="formatBovinShipmentLines(shipment).length">
|
|
||||||
<div
|
|
||||||
v-for="(line, index) in formatBovinShipmentLines(shipment)"
|
|
||||||
:key="index"
|
|
||||||
class="leading-5"
|
|
||||||
>
|
|
||||||
{{ line }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div>{{ formatWeighing(shipment, 'gross') }} | {{ formatWeighing(shipment, 'tare') }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type {ShipmentData} from "~/services/dto/shipment-data";
|
|
||||||
import {getShipmentList} from "~/services/shipment";
|
|
||||||
|
|
||||||
const shipmentList = ref<ShipmentData[]>()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const formatWeighing = (shipment: ShipmentData, type: 'gross' | 'tare') => {
|
|
||||||
const entry = shipment.weights?.find((weight) => weight.type === type)
|
|
||||||
if (!entry || entry.weight == null || entry.dsd == null) {
|
|
||||||
return '—'
|
|
||||||
}
|
|
||||||
return `${entry.weight} kg`
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatBovinShipmentLines = (shipment: ShipmentData) => {
|
|
||||||
if (!shipment.bovinShipments?.length) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return shipment.bovinShipments.map((entry) => {
|
|
||||||
const label = typeof entry.shipmentType === 'string'
|
|
||||||
? entry.shipmentType
|
|
||||||
: entry.shipmentType?.label
|
|
||||||
return `${label ?? '—'} : ${entry.nbBovinSend ?? '—'}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToshipment = (id: number) => {
|
|
||||||
//router.push(`/shipment/update/${id}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
shipmentList.value = await getShipmentList(true)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex items-center justify-between ">
|
|
||||||
<div class="flex items-center gap-10">
|
|
||||||
<Icon @click="router.push('/')" name="gg:arrow-left-o" style="color: black" size="44"/>
|
|
||||||
<h1 class="text-3xl font-bold uppercase">listes des expéditions en attente</h1>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ps-20 ">
|
|
||||||
<div class="mt-6 border border-slate-200 mb-16 ">
|
|
||||||
<div class="grid grid-cols-5 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
|
|
||||||
<div>Client</div>
|
|
||||||
<div>Adresse</div>
|
|
||||||
<div>Type d'expéditions</div>
|
|
||||||
<div>Transporteur</div>
|
|
||||||
<div>Immatriculation</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-for="shipment in shipmentList"
|
|
||||||
:key="shipment.id"
|
|
||||||
class="grid grid-cols-5 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
@click="goToShipment(shipment.id)"
|
|
||||||
@keydown.enter="goToShipment(shipment.id)"
|
|
||||||
>
|
|
||||||
<div>{{ shipment.customer?.label }}</div>
|
|
||||||
<div>{{ shipment.address?.fullAddress }}</div>
|
|
||||||
<div>
|
|
||||||
<template v-if="formatBovinShipmentLines(shipment).length">
|
|
||||||
<div
|
|
||||||
v-for="(line, index) in formatBovinShipmentLines(shipment)"
|
|
||||||
:key="index"
|
|
||||||
class="leading-5"
|
|
||||||
>
|
|
||||||
{{ line }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div>{{ shipment.carrier?.name }}</div>
|
|
||||||
<div>{{ shipment.licencePlate }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
|
|
||||||
import type {ShipmentData} from "~/services/dto/shipment-data";
|
|
||||||
import {getShipmentList} from "~/services/shipment";
|
|
||||||
|
|
||||||
const shipmentList = ref<ShipmentData[]>()
|
|
||||||
const router = useRouter()
|
|
||||||
|
|
||||||
const goToShipment = (id: number) => {
|
|
||||||
router.push(`/shipment/${id}`)
|
|
||||||
}
|
|
||||||
const formatBovinShipmentLines = (shipment: ShipmentData) => {
|
|
||||||
if (!shipment.bovinShipments?.length) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
return shipment.bovinShipments.map((entry) => {
|
|
||||||
const label = typeof entry.shipmentType === 'string'
|
|
||||||
? entry.shipmentType
|
|
||||||
: entry.shipmentType?.label
|
|
||||||
return `${label ?? '—'} : ${entry.nbBovinSend ?? '—'}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
shipmentList.value = await getShipmentList(false)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,43 +1,23 @@
|
|||||||
import { useApi } from "~/composables/useApi"
|
import { useApi } from '~/composables/useApi'
|
||||||
import type { CustomerData, CustomerPayload } from "~/services/dto/customer-data"
|
import type { CustomerData } from '~/services/dto/customer-data'
|
||||||
|
|
||||||
export type CustomerListResponse =
|
export type CustomerListResponse =
|
||||||
| CustomerData[]
|
| CustomerData[]
|
||||||
| { "hydra:member"?: CustomerData[] }
|
| { 'hydra:member'?: CustomerData[] }
|
||||||
|
|
||||||
export async function getCustomerList(): Promise<CustomerData[]> {
|
export async function getCustomerList(): Promise<CustomerData[]> {
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
const response = await api.get<CustomerListResponse>("customers", {}, {
|
const response = await api.get<CustomerListResponse>('customers', {}, {
|
||||||
toastErrorKey: "errors.customer.list",
|
toastErrorKey: 'errors.customer.list'
|
||||||
})
|
})
|
||||||
|
|
||||||
if (Array.isArray(response)) return response
|
if (Array.isArray(response)) {
|
||||||
if (response && typeof response === "object" && Array.isArray(response["hydra:member"])) {
|
return response
|
||||||
return response["hydra:member"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response && typeof response === 'object' && Array.isArray(response['hydra:member'])) {
|
||||||
|
return response['hydra:member']
|
||||||
|
}
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCustomer(id: number): Promise<CustomerData> {
|
|
||||||
const api = useApi()
|
|
||||||
return api.get<CustomerData>(`customers/${id}`, {}, {
|
|
||||||
toastErrorKey: "errors.customer.fetch",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function updateCustomer(id: number, payload: Partial<CustomerPayload>): Promise<CustomerData> {
|
|
||||||
const api = useApi()
|
|
||||||
return api.patch<CustomerData>(`customers/${id}`, payload, {
|
|
||||||
toastErrorKey: "errors.customer.update",
|
|
||||||
toastSuccessKey: "success.customer.update",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createCustomer(payload: CustomerPayload): Promise<CustomerData> {
|
|
||||||
const api = useApi()
|
|
||||||
return api.post<CustomerData>("customers", payload, {
|
|
||||||
toastErrorKey: "errors.customer.create",
|
|
||||||
toastSuccessKey: "success.customer.create",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,8 @@
|
|||||||
import type { AddressFormData } from "~/services/dto/address-data"
|
import type { AddressData } from "~/services/dto/address-data"
|
||||||
|
|
||||||
export type CustomerAddresses = AddressFormData[] | string[]
|
|
||||||
|
|
||||||
export interface CustomerData {
|
export interface CustomerData {
|
||||||
id: number
|
id: number
|
||||||
label: string
|
label: string
|
||||||
code?: string | null
|
code?: string | null
|
||||||
addresses: CustomerAddresses
|
addresses?: AddressData[] | null
|
||||||
}
|
|
||||||
|
|
||||||
export interface CustomerFormData {
|
|
||||||
label: string
|
|
||||||
code?: string
|
|
||||||
addresses: AddressFormData[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CustomerPayload = {
|
|
||||||
label: string
|
|
||||||
code?: string | null
|
|
||||||
addresses?: string[]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type {CarrierData} from '~/services/dto/carrier-data'
|
import type {CarrierData} from '~/services/dto/carrier-data'
|
||||||
import type {TruckData} from '~/services/dto/truck-data'
|
import type {TruckData} from '~/services/dto/truck-data'
|
||||||
import type {CustomerData} from '~/services/dto/customer-data'
|
import type {CustomerData} from '~/services/dto/customer-data'
|
||||||
import type {AddressData} from "~/services/dto/address-data";
|
|
||||||
|
|
||||||
export interface ShipmentTypeData {
|
export interface ShipmentTypeData {
|
||||||
id: number
|
id: number
|
||||||
@@ -22,7 +21,6 @@ export type ShipmentData = {
|
|||||||
shipmentDate: string
|
shipmentDate: string
|
||||||
currentStep: number
|
currentStep: number
|
||||||
isValid: boolean
|
isValid: boolean
|
||||||
address?: AddressData | null
|
|
||||||
carrier?: CarrierData | null
|
carrier?: CarrierData | null
|
||||||
truck?: TruckData | null
|
truck?: TruckData | null
|
||||||
customer?: CustomerData | null
|
customer?: CustomerData | null
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ use ApiPlatform\Metadata\ApiProperty;
|
|||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
use ApiPlatform\Metadata\Patch;
|
|
||||||
use ApiPlatform\Metadata\Post;
|
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
@@ -26,16 +24,6 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
new GetCollection(
|
new GetCollection(
|
||||||
normalizationContext: ['groups' => ['customer:read']],
|
normalizationContext: ['groups' => ['customer:read']],
|
||||||
),
|
),
|
||||||
new Post(
|
|
||||||
normalizationContext: ['groups' => ['customer:read']],
|
|
||||||
denormalizationContext: ['groups' => ['customer:write']],
|
|
||||||
security: "is_granted('ROLE_ADMIN')",
|
|
||||||
),
|
|
||||||
new Patch(
|
|
||||||
normalizationContext: ['groups' => ['customer:read']],
|
|
||||||
denormalizationContext: ['groups' => ['customer:write']],
|
|
||||||
security: "is_granted('ROLE_ADMIN')",
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
security: "is_granted('ROLE_USER')",
|
security: "is_granted('ROLE_USER')",
|
||||||
)]
|
)]
|
||||||
@@ -48,11 +36,11 @@ class Customer
|
|||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
#[Groups(['customer:read', 'customer:write', 'shipment:read'])]
|
#[Groups(['customer:read', 'shipment:read'])]
|
||||||
private ?string $label = null;
|
private ?string $label = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 255)]
|
#[ORM\Column(length: 255)]
|
||||||
#[Groups(['customer:read', 'customer:write', 'shipment:read'])]
|
#[Groups(['customer:read', 'shipment:read'])]
|
||||||
private ?string $code = null;
|
private ?string $code = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,7 +48,7 @@ class Customer
|
|||||||
*/
|
*/
|
||||||
#[ORM\ManyToMany(targetEntity: Address::class, inversedBy: 'customers')]
|
#[ORM\ManyToMany(targetEntity: Address::class, inversedBy: 'customers')]
|
||||||
#[ORM\JoinTable(name: 'customer_address')]
|
#[ORM\JoinTable(name: 'customer_address')]
|
||||||
#[Groups(['customer:read', 'customer:write'])]
|
#[Groups(['customer:read'])]
|
||||||
#[ApiProperty(readableLink: true)]
|
#[ApiProperty(readableLink: true)]
|
||||||
private Collection $addresses;
|
private Collection $addresses;
|
||||||
|
|
||||||
@@ -99,29 +87,8 @@ class Customer
|
|||||||
return $this->addresses;
|
return $this->addresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setAddresses(iterable $addresses): self
|
public function setAddresses(Collection $addresses): void
|
||||||
{
|
{
|
||||||
$this->addresses->clear();
|
$this->addresses = $addresses;
|
||||||
foreach ($addresses as $address) {
|
|
||||||
$this->addAddress($address);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addAddress(Address $address): self
|
|
||||||
{
|
|
||||||
if (!$this->addresses->contains($address)) {
|
|
||||||
$this->addresses->add($address);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function removeAddress(Address $address): self
|
|
||||||
{
|
|
||||||
$this->addresses->removeElement($address);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
|
|
||||||
use ApiPlatform\Metadata\ApiFilter;
|
|
||||||
use ApiPlatform\Metadata\ApiProperty;
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
@@ -28,7 +26,6 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
|||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\HasLifecycleCallbacks]
|
#[ORM\HasLifecycleCallbacks]
|
||||||
#[ORM\Table(name: 'shipment')]
|
#[ORM\Table(name: 'shipment')]
|
||||||
#[ApiFilter(BooleanFilter::class, properties: ['isValid'])]
|
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(
|
new Get(
|
||||||
|
|||||||
Reference in New Issue
Block a user