372 lines
11 KiB
Vue
372 lines
11 KiB
Vue
<template>
|
|
<main class="container">
|
|
<h1>{{ t('pages.objetDetail') }}</h1>
|
|
<p v-if="errorMessage">{{ errorMessage }}</p>
|
|
<p v-else-if="pending">{{ t('states.loading') }}</p>
|
|
<section v-else class="card">
|
|
<h2>{{ item?.nom }}</h2>
|
|
<p v-if="item?.description">{{ item?.description }}</p>
|
|
<p>{{ t('form.quantite') }}: {{ item?.quantite ?? 0 }}</p>
|
|
<p>{{ t('form.statut') }}: {{ item?.statut }}</p>
|
|
</section>
|
|
|
|
<section v-if="!pending" class="card" style="margin-top: 16px;">
|
|
<h3>{{ t('sections.piecesJointes') }}</h3>
|
|
<div style="margin: 8px 0 12px;">
|
|
<FileUploader
|
|
:disabled="isUploading"
|
|
:label="t('fileUploader.label')"
|
|
@upload="uploadFiles"
|
|
/>
|
|
</div>
|
|
<p v-if="uploadMessage">{{ uploadMessage }}</p>
|
|
<p v-if="piecesPending">{{ t('states.loading') }}</p>
|
|
<p v-else-if="piecesError">{{ piecesErrorMessage }}</p>
|
|
<ul v-else>
|
|
<li v-for="pj in piecesJointes" :key="pj.id">
|
|
{{ pj.nom_fichier }} ({{ pj.categorie }})
|
|
<span v-if="pj.est_principale" style="margin-left: 8px;">[Principale]</span>
|
|
<button
|
|
class="card"
|
|
type="button"
|
|
style="margin-left: 8px;"
|
|
@click="setPrincipale(pj.id)"
|
|
>
|
|
{{ t('actions.setPrimary') }}
|
|
</button>
|
|
<button
|
|
class="card"
|
|
type="button"
|
|
style="margin-left: 8px;"
|
|
@click="confirmDeletePieceJointe(pj.id)"
|
|
>
|
|
{{ t('actions.delete') }}
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<section v-if="!pending" class="card" style="margin-top: 16px;">
|
|
<h3>{{ t('sections.champs') }}</h3>
|
|
<p v-if="champMessage">{{ champMessage }}</p>
|
|
<div style="margin: 8px 0 12px; display: grid; gap: 8px;">
|
|
<input v-model="newChamp.nom_champ" :placeholder="t('placeholders.name')" />
|
|
<input v-model="newChamp.valeur" :placeholder="t('placeholders.value')" />
|
|
<input v-model="newChamp.unite" :placeholder="t('placeholders.unit')" />
|
|
<select v-model="newChamp.type_champ">
|
|
<option value="string">string</option>
|
|
<option value="int">int</option>
|
|
<option value="bool">bool</option>
|
|
<option value="date">date</option>
|
|
</select>
|
|
<button class="card" type="button" @click="createChamp">{{ t('actions.add') }}</button>
|
|
</div>
|
|
<p v-if="champsPending">{{ t('states.loading') }}</p>
|
|
<p v-else-if="champsError">{{ champsErrorMessage }}</p>
|
|
<ul v-else>
|
|
<li v-for="champ in champsEditable" :key="champ.id">
|
|
<div style="display: grid; gap: 6px; margin-bottom: 10px;">
|
|
<input v-model="champ.nom_champ" />
|
|
<input v-model="champ.valeur" />
|
|
<input v-model="champ.unite" />
|
|
<select v-model="champ.type_champ">
|
|
<option value="string">string</option>
|
|
<option value="int">int</option>
|
|
<option value="bool">bool</option>
|
|
<option value="date">date</option>
|
|
</select>
|
|
<div style="display: flex; gap: 8px;">
|
|
<button class="card" type="button" @click="updateChamp(champ)">{{ t('actions.save') }}</button>
|
|
<button class="card" type="button" @click="confirmDeleteChamp(champ.id)">{{ t('actions.delete') }}</button>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<section v-if="!pending" class="card" style="margin-top: 16px;">
|
|
<h3>{{ t('sections.liensEmplacements') }}</h3>
|
|
<p v-if="lienMessage">{{ lienMessage }}</p>
|
|
<div style="margin: 8px 0 12px; display: grid; gap: 8px;">
|
|
<EmplacementPicker v-model="newLien.emplacement_id" :items="emplacements" />
|
|
<select v-model="newLien.type">
|
|
<option value="stocke">stocke</option>
|
|
<option value="utilise_dans">utilise_dans</option>
|
|
</select>
|
|
<button class="card" type="button" @click="createLien">{{ t('actions.add') }}</button>
|
|
</div>
|
|
<p v-if="liensPending">{{ t('states.loading') }}</p>
|
|
<p v-else-if="liensError">{{ liensErrorMessage }}</p>
|
|
<ul v-else>
|
|
<li v-for="lien in liens" :key="lien.id">
|
|
{{ emplacementLabel(lien.emplacement_id) }} ({{ lien.type }})
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
|
|
<ConfirmDialog
|
|
:open="confirmOpen"
|
|
:title="confirmTitle"
|
|
:message="confirmMessage"
|
|
@confirm="runConfirm"
|
|
@cancel="closeConfirm"
|
|
/>
|
|
</main>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
type Objet = {
|
|
id: string
|
|
nom: string
|
|
description?: string | null
|
|
quantite: number
|
|
statut: string
|
|
}
|
|
|
|
type PieceJointe = {
|
|
id: string
|
|
nom_fichier: string
|
|
categorie: string
|
|
est_principale?: boolean
|
|
}
|
|
|
|
type ChampPersonnalise = {
|
|
id: string
|
|
nom_champ: string
|
|
valeur?: string | null
|
|
unite?: string | null
|
|
type_champ: string
|
|
}
|
|
|
|
type LienEmplacement = {
|
|
id: string
|
|
emplacement_id: string
|
|
type: string
|
|
}
|
|
|
|
type Emplacement = {
|
|
id: string
|
|
nom: string
|
|
parent_id?: string | null
|
|
}
|
|
|
|
const { apiBase, getErrorMessage } = useApi()
|
|
const { t } = useI18n()
|
|
const route = useRoute()
|
|
const objetId = route.params.id
|
|
|
|
const { data, pending, error } = await useFetch<Objet>(
|
|
`${apiBase}/objets/${objetId}`
|
|
)
|
|
|
|
const { data: piecesData, pending: piecesPending, error: piecesError, refresh: refreshPieces } =
|
|
await useFetch<{ items: PieceJointe[] }>(
|
|
`${apiBase}/objets/${objetId}/pieces_jointes?limit=50`
|
|
)
|
|
|
|
const { data: champsData, pending: champsPending, error: champsError, refresh: refreshChamps } =
|
|
await useFetch<{ items: ChampPersonnalise[] }>(
|
|
`${apiBase}/objets/${objetId}/champs_personnalises?limit=50`
|
|
)
|
|
|
|
const { data: liensData, pending: liensPending, error: liensError, refresh: refreshLiens } =
|
|
await useFetch<{ items: LienEmplacement[] }>(
|
|
`${apiBase}/objets/${objetId}/liens_emplacements?limit=50`
|
|
)
|
|
|
|
const { data: emplacementsData } = await useFetch<{ items: Emplacement[] }>(
|
|
`${apiBase}/emplacements?limit=200`
|
|
)
|
|
|
|
const item = computed(() => data.value ?? null)
|
|
const errorMessage = computed(() =>
|
|
error.value ? getErrorMessage(error.value, t('messages.loadError')) : ""
|
|
)
|
|
const piecesErrorMessage = computed(() =>
|
|
piecesError.value ? getErrorMessage(piecesError.value, t('messages.loadError')) : ""
|
|
)
|
|
const champsErrorMessage = computed(() =>
|
|
champsError.value ? getErrorMessage(champsError.value, t('messages.loadError')) : ""
|
|
)
|
|
const liensErrorMessage = computed(() =>
|
|
liensError.value ? getErrorMessage(liensError.value, t('messages.loadError')) : ""
|
|
)
|
|
const piecesJointes = computed(() => piecesData.value?.items ?? [])
|
|
const champsPersonnalises = computed(() => champsData.value?.items ?? [])
|
|
const liens = computed(() => liensData.value?.items ?? [])
|
|
const emplacements = computed(() => emplacementsData.value?.items ?? [])
|
|
|
|
const isUploading = ref(false)
|
|
const uploadMessage = ref('')
|
|
const champMessage = ref('')
|
|
const lienMessage = ref('')
|
|
const newChamp = ref({
|
|
nom_champ: "",
|
|
valeur: "",
|
|
unite: "",
|
|
type_champ: "string"
|
|
})
|
|
const champsEditable = ref<ChampPersonnalise[]>([])
|
|
const newLien = ref({
|
|
emplacement_id: "",
|
|
type: "stocke"
|
|
})
|
|
|
|
watch(champsPersonnalises, (items) => {
|
|
champsEditable.value = items.map((champ) => ({ ...champ }))
|
|
})
|
|
|
|
const uploadFiles = async (files: FileList) => {
|
|
uploadMessage.value = ''
|
|
if (!files || files.length === 0) {
|
|
uploadMessage.value = t('messages.noFiles')
|
|
return
|
|
}
|
|
isUploading.value = true
|
|
const formData = new FormData()
|
|
Array.from(files).forEach((file) => {
|
|
formData.append("fichiers", file)
|
|
})
|
|
try {
|
|
await $fetch(`${apiBase}/objets/${objetId}/pieces_jointes`, {
|
|
method: "POST",
|
|
body: formData
|
|
})
|
|
uploadMessage.value = t('messages.uploadDone')
|
|
await refreshPieces()
|
|
} catch (err) {
|
|
uploadMessage.value = getErrorMessage(err, t('messages.uploadError'))
|
|
} finally {
|
|
isUploading.value = false
|
|
}
|
|
}
|
|
|
|
const createChamp = async () => {
|
|
champMessage.value = ''
|
|
if (!newChamp.value.nom_champ) {
|
|
champMessage.value = t('messages.requiredName')
|
|
return
|
|
}
|
|
try {
|
|
await $fetch(`${apiBase}/objets/${objetId}/champs_personnalises`, {
|
|
method: "POST",
|
|
body: newChamp.value
|
|
})
|
|
newChamp.value = { nom_champ: "", valeur: "", unite: "", type_champ: "string" }
|
|
champMessage.value = t('messages.created')
|
|
await refreshChamps()
|
|
} catch (err) {
|
|
champMessage.value = getErrorMessage(err, t('messages.saveError'))
|
|
}
|
|
}
|
|
|
|
const updateChamp = async (champ: ChampPersonnalise) => {
|
|
champMessage.value = ''
|
|
try {
|
|
await $fetch(`${apiBase}/champs_personnalises/${champ.id}`, {
|
|
method: "PUT",
|
|
body: champ
|
|
})
|
|
champMessage.value = t('messages.updated')
|
|
await refreshChamps()
|
|
} catch (err) {
|
|
champMessage.value = getErrorMessage(err, t('messages.saveError'))
|
|
}
|
|
}
|
|
|
|
const deleteChamp = async (id: string) => {
|
|
champMessage.value = ''
|
|
try {
|
|
await $fetch(`${apiBase}/champs_personnalises/${id}`, {
|
|
method: "DELETE"
|
|
})
|
|
champMessage.value = t('messages.deleted')
|
|
await refreshChamps()
|
|
} catch (err) {
|
|
champMessage.value = getErrorMessage(err, t('messages.deleteError'))
|
|
}
|
|
}
|
|
|
|
const createLien = async () => {
|
|
lienMessage.value = ''
|
|
if (!newLien.value.emplacement_id) {
|
|
lienMessage.value = t('messages.requiredName')
|
|
return
|
|
}
|
|
try {
|
|
await $fetch(`${apiBase}/objets/${objetId}/liens_emplacements`, {
|
|
method: "POST",
|
|
body: newLien.value
|
|
})
|
|
newLien.value = { emplacement_id: "", type: "stocke" }
|
|
lienMessage.value = t('messages.created')
|
|
await refreshLiens()
|
|
} catch (err) {
|
|
lienMessage.value = getErrorMessage(err, t('messages.saveError'))
|
|
}
|
|
}
|
|
|
|
const deletePieceJointe = async (id: string) => {
|
|
uploadMessage.value = ''
|
|
try {
|
|
await $fetch(`${apiBase}/pieces_jointes/${id}`, {
|
|
method: 'DELETE'
|
|
})
|
|
uploadMessage.value = t('messages.deleted')
|
|
await refreshPieces()
|
|
} catch (err) {
|
|
uploadMessage.value = getErrorMessage(err, t('messages.deleteError'))
|
|
}
|
|
}
|
|
|
|
const setPrincipale = async (id: string) => {
|
|
uploadMessage.value = ''
|
|
try {
|
|
await $fetch(`${apiBase}/pieces_jointes/${id}/principale`, {
|
|
method: 'PUT'
|
|
})
|
|
uploadMessage.value = t('messages.updated')
|
|
await refreshPieces()
|
|
} catch (err) {
|
|
uploadMessage.value = getErrorMessage(err, t('messages.saveError'))
|
|
}
|
|
}
|
|
|
|
const confirmOpen = ref(false)
|
|
const confirmTitle = ref('Confirmation')
|
|
const confirmMessage = ref('Confirmer la suppression ?')
|
|
const confirmAction = ref<null | (() => Promise<void>)>(null)
|
|
|
|
const confirmDeletePieceJointe = (id: string) => {
|
|
confirmTitle.value = t('confirm.deletePieceTitle')
|
|
confirmMessage.value = t('confirm.deletePieceMessage')
|
|
confirmAction.value = () => deletePieceJointe(id)
|
|
confirmOpen.value = true
|
|
}
|
|
|
|
const confirmDeleteChamp = (id: string) => {
|
|
confirmTitle.value = t('confirm.deleteChampTitle')
|
|
confirmMessage.value = t('confirm.deleteChampMessage')
|
|
confirmAction.value = () => deleteChamp(id)
|
|
confirmOpen.value = true
|
|
}
|
|
|
|
const closeConfirm = () => {
|
|
confirmOpen.value = false
|
|
confirmAction.value = null
|
|
}
|
|
|
|
const runConfirm = async () => {
|
|
if (confirmAction.value) {
|
|
await confirmAction.value()
|
|
}
|
|
closeConfirm()
|
|
}
|
|
|
|
const emplacementLabel = (id: string) => {
|
|
const item = emplacements.value.find((e) => e.id === id)
|
|
if (!item) return id
|
|
return item.nom
|
|
}
|
|
</script>
|