feat(sync) : wire sync flow into category edit pages with confirmation modal
This commit is contained in:
@@ -41,6 +41,14 @@
|
|||||||
show-resolved
|
show-resolved
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<SyncConfirmationModal
|
||||||
|
:preview="syncPreviewData"
|
||||||
|
:open="showSyncModal"
|
||||||
|
:loading="syncLoading"
|
||||||
|
@confirm="handleSyncConfirm"
|
||||||
|
@cancel="handleSyncCancel"
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -48,7 +56,7 @@
|
|||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useHead, useRoute, useRouter } from '#imports'
|
import { useHead, useRoute, useRouter } from '#imports'
|
||||||
import ModelTypeForm from '~/components/model-types/ModelTypeForm.vue'
|
import ModelTypeForm from '~/components/model-types/ModelTypeForm.vue'
|
||||||
import { getModelType, updateModelType, type ModelTypePayload } from '~/services/modelTypes'
|
import { getModelType, updateModelType, syncPreview, syncExecute, type ModelTypePayload, type SyncPreviewResult } from '~/services/modelTypes'
|
||||||
import type { ComponentModelStructure } from '~/shared/types/inventory'
|
import type { ComponentModelStructure } from '~/shared/types/inventory'
|
||||||
import { useComponentTypes } from '~/composables/useComponentTypes'
|
import { useComponentTypes } from '~/composables/useComponentTypes'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
@@ -62,6 +70,10 @@ const { loadComponentTypes } = useComponentTypes()
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const initialData = ref<Partial<ModelTypePayload> | null>(null)
|
const initialData = ref<Partial<ModelTypePayload> | null>(null)
|
||||||
|
const showSyncModal = ref(false)
|
||||||
|
const syncLoading = ref(false)
|
||||||
|
const syncPreviewData = ref<SyncPreviewResult | null>(null)
|
||||||
|
const pendingPayload = ref<Partial<ModelTypePayload> | null>(null)
|
||||||
|
|
||||||
const title = computed(() =>
|
const title = computed(() =>
|
||||||
initialData.value?.name
|
initialData.value?.name
|
||||||
@@ -125,10 +137,29 @@ const handleSubmit = async (payload: Parameters<typeof updateModelType>[1]) => {
|
|||||||
...payload,
|
...payload,
|
||||||
description: payload?.notes ?? null,
|
description: payload?.notes ?? null,
|
||||||
}
|
}
|
||||||
await updateModelType(id, enrichedPayload)
|
|
||||||
await loadComponentTypes({ force: true })
|
// Get sync preview BEFORE saving
|
||||||
showSuccess('Catégorie de composant mise à jour avec succès.')
|
const preview = await syncPreview(id, enrichedPayload.structure || {})
|
||||||
await navigateBackToList()
|
|
||||||
|
const hasImpact = preview && (
|
||||||
|
Object.values(preview.additions || {}).some(v => v > 0)
|
||||||
|
|| Object.values(preview.deletions || {}).some(v => v > 0)
|
||||||
|
|| Object.values(preview.modifications || {}).some(v => v > 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (hasImpact) {
|
||||||
|
// Show modal for confirmation
|
||||||
|
pendingPayload.value = enrichedPayload
|
||||||
|
syncPreviewData.value = preview
|
||||||
|
showSyncModal.value = true
|
||||||
|
} else {
|
||||||
|
// No impact — save directly + sync
|
||||||
|
await updateModelType(id, enrichedPayload)
|
||||||
|
await syncExecute(id, { confirmDeletions: false, confirmTypeChanges: false })
|
||||||
|
await loadComponentTypes({ force: true })
|
||||||
|
showSuccess('Catégorie de composant mise à jour avec succès.')
|
||||||
|
await navigateBackToList()
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(normalizeError(error))
|
showError(normalizeError(error))
|
||||||
} finally {
|
} finally {
|
||||||
@@ -136,6 +167,39 @@ const handleSubmit = async (payload: Parameters<typeof updateModelType>[1]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSyncConfirm = async () => {
|
||||||
|
if (!pendingPayload.value) return
|
||||||
|
const id = String(route.params.id)
|
||||||
|
|
||||||
|
syncLoading.value = true
|
||||||
|
try {
|
||||||
|
const hasDeletions = syncPreviewData.value && Object.values(syncPreviewData.value.deletions || {}).some(v => v > 0)
|
||||||
|
const hasModifications = syncPreviewData.value && Object.values(syncPreviewData.value.modifications || {}).some(v => v > 0)
|
||||||
|
|
||||||
|
await updateModelType(id, pendingPayload.value)
|
||||||
|
await syncExecute(id, {
|
||||||
|
confirmDeletions: !!hasDeletions,
|
||||||
|
confirmTypeChanges: !!hasModifications,
|
||||||
|
})
|
||||||
|
await loadComponentTypes({ force: true })
|
||||||
|
showSuccess('Catégorie de composant mise à jour avec succès.')
|
||||||
|
await navigateBackToList()
|
||||||
|
} catch (error) {
|
||||||
|
showError(normalizeError(error))
|
||||||
|
} finally {
|
||||||
|
syncLoading.value = false
|
||||||
|
showSyncModal.value = false
|
||||||
|
pendingPayload.value = null
|
||||||
|
syncPreviewData.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSyncCancel = () => {
|
||||||
|
showSyncModal.value = false
|
||||||
|
pendingPayload.value = null
|
||||||
|
syncPreviewData.value = null
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadCategory()
|
loadCategory()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -41,6 +41,14 @@
|
|||||||
show-resolved
|
show-resolved
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<SyncConfirmationModal
|
||||||
|
:preview="syncPreviewData"
|
||||||
|
:open="showSyncModal"
|
||||||
|
:loading="syncLoading"
|
||||||
|
@confirm="handleSyncConfirm"
|
||||||
|
@cancel="handleSyncCancel"
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -48,7 +56,7 @@
|
|||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useHead, useRoute, useRouter } from '#imports'
|
import { useHead, useRoute, useRouter } from '#imports'
|
||||||
import ModelTypeForm from '~/components/model-types/ModelTypeForm.vue'
|
import ModelTypeForm from '~/components/model-types/ModelTypeForm.vue'
|
||||||
import { getModelType, updateModelType, type ModelTypePayload } from '~/services/modelTypes'
|
import { getModelType, updateModelType, syncPreview, syncExecute, type ModelTypePayload, type SyncPreviewResult } from '~/services/modelTypes'
|
||||||
import type { PieceModelStructure } from '~/shared/types/inventory'
|
import type { PieceModelStructure } from '~/shared/types/inventory'
|
||||||
import { usePieceTypes } from '~/composables/usePieceTypes'
|
import { usePieceTypes } from '~/composables/usePieceTypes'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
@@ -62,6 +70,10 @@ const { loadPieceTypes } = usePieceTypes()
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const initialData = ref<Partial<ModelTypePayload> | null>(null)
|
const initialData = ref<Partial<ModelTypePayload> | null>(null)
|
||||||
|
const showSyncModal = ref(false)
|
||||||
|
const syncLoading = ref(false)
|
||||||
|
const syncPreviewData = ref<SyncPreviewResult | null>(null)
|
||||||
|
const pendingPayload = ref<Partial<ModelTypePayload> | null>(null)
|
||||||
|
|
||||||
const title = computed(() =>
|
const title = computed(() =>
|
||||||
initialData.value?.name ? `Modifier « ${initialData.value.name} »` : 'Modifier une catégorie de pièce',
|
initialData.value?.name ? `Modifier « ${initialData.value.name} »` : 'Modifier une catégorie de pièce',
|
||||||
@@ -123,10 +135,29 @@ const handleSubmit = async (payload: Parameters<typeof updateModelType>[1]) => {
|
|||||||
...payload,
|
...payload,
|
||||||
description: payload?.notes ?? null,
|
description: payload?.notes ?? null,
|
||||||
}
|
}
|
||||||
await updateModelType(id, enrichedPayload)
|
|
||||||
await loadPieceTypes({ force: true })
|
// Get sync preview BEFORE saving
|
||||||
showSuccess('Catégorie de pièce mise à jour avec succès.')
|
const preview = await syncPreview(id, enrichedPayload.structure || {})
|
||||||
await navigateBackToList()
|
|
||||||
|
const hasImpact = preview && (
|
||||||
|
Object.values(preview.additions || {}).some(v => v > 0)
|
||||||
|
|| Object.values(preview.deletions || {}).some(v => v > 0)
|
||||||
|
|| Object.values(preview.modifications || {}).some(v => v > 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (hasImpact) {
|
||||||
|
// Show modal for confirmation
|
||||||
|
pendingPayload.value = enrichedPayload
|
||||||
|
syncPreviewData.value = preview
|
||||||
|
showSyncModal.value = true
|
||||||
|
} else {
|
||||||
|
// No impact — save directly + sync
|
||||||
|
await updateModelType(id, enrichedPayload)
|
||||||
|
await syncExecute(id, { confirmDeletions: false, confirmTypeChanges: false })
|
||||||
|
await loadPieceTypes({ force: true })
|
||||||
|
showSuccess('Catégorie de pièce mise à jour avec succès.')
|
||||||
|
await navigateBackToList()
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(normalizeError(error))
|
showError(normalizeError(error))
|
||||||
} finally {
|
} finally {
|
||||||
@@ -134,6 +165,39 @@ const handleSubmit = async (payload: Parameters<typeof updateModelType>[1]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSyncConfirm = async () => {
|
||||||
|
if (!pendingPayload.value) return
|
||||||
|
const id = String(route.params.id)
|
||||||
|
|
||||||
|
syncLoading.value = true
|
||||||
|
try {
|
||||||
|
const hasDeletions = syncPreviewData.value && Object.values(syncPreviewData.value.deletions || {}).some(v => v > 0)
|
||||||
|
const hasModifications = syncPreviewData.value && Object.values(syncPreviewData.value.modifications || {}).some(v => v > 0)
|
||||||
|
|
||||||
|
await updateModelType(id, pendingPayload.value)
|
||||||
|
await syncExecute(id, {
|
||||||
|
confirmDeletions: !!hasDeletions,
|
||||||
|
confirmTypeChanges: !!hasModifications,
|
||||||
|
})
|
||||||
|
await loadPieceTypes({ force: true })
|
||||||
|
showSuccess('Catégorie de pièce mise à jour avec succès.')
|
||||||
|
await navigateBackToList()
|
||||||
|
} catch (error) {
|
||||||
|
showError(normalizeError(error))
|
||||||
|
} finally {
|
||||||
|
syncLoading.value = false
|
||||||
|
showSyncModal.value = false
|
||||||
|
pendingPayload.value = null
|
||||||
|
syncPreviewData.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSyncCancel = () => {
|
||||||
|
showSyncModal.value = false
|
||||||
|
pendingPayload.value = null
|
||||||
|
syncPreviewData.value = null
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadCategory()
|
loadCategory()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -41,6 +41,14 @@
|
|||||||
show-resolved
|
show-resolved
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<SyncConfirmationModal
|
||||||
|
:preview="syncPreviewData"
|
||||||
|
:open="showSyncModal"
|
||||||
|
:loading="syncLoading"
|
||||||
|
@confirm="handleSyncConfirm"
|
||||||
|
@cancel="handleSyncCancel"
|
||||||
|
/>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -48,7 +56,7 @@
|
|||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useHead, useRoute, useRouter } from '#imports'
|
import { useHead, useRoute, useRouter } from '#imports'
|
||||||
import ModelTypeForm from '~/components/model-types/ModelTypeForm.vue'
|
import ModelTypeForm from '~/components/model-types/ModelTypeForm.vue'
|
||||||
import { getModelType, updateModelType, type ModelTypePayload } from '~/services/modelTypes'
|
import { getModelType, updateModelType, syncPreview, syncExecute, type ModelTypePayload, type SyncPreviewResult } from '~/services/modelTypes'
|
||||||
import type { ProductModelStructure } from '~/shared/types/inventory'
|
import type { ProductModelStructure } from '~/shared/types/inventory'
|
||||||
import { useProductTypes } from '~/composables/useProductTypes'
|
import { useProductTypes } from '~/composables/useProductTypes'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
@@ -62,6 +70,10 @@ const { loadProductTypes } = useProductTypes()
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const initialData = ref<Partial<ModelTypePayload> | null>(null)
|
const initialData = ref<Partial<ModelTypePayload> | null>(null)
|
||||||
|
const showSyncModal = ref(false)
|
||||||
|
const syncLoading = ref(false)
|
||||||
|
const syncPreviewData = ref<SyncPreviewResult | null>(null)
|
||||||
|
const pendingPayload = ref<Partial<ModelTypePayload> | null>(null)
|
||||||
|
|
||||||
const title = computed(() =>
|
const title = computed(() =>
|
||||||
initialData.value?.name ? `Modifier « ${initialData.value.name} »` : 'Modifier une catégorie de produit',
|
initialData.value?.name ? `Modifier « ${initialData.value.name} »` : 'Modifier une catégorie de produit',
|
||||||
@@ -123,10 +135,29 @@ const handleSubmit = async (payload: Parameters<typeof updateModelType>[1]) => {
|
|||||||
...payload,
|
...payload,
|
||||||
description: payload?.notes ?? null,
|
description: payload?.notes ?? null,
|
||||||
}
|
}
|
||||||
await updateModelType(id, enrichedPayload)
|
|
||||||
await loadProductTypes({ force: true })
|
// Get sync preview BEFORE saving
|
||||||
showSuccess('Catégorie de produit mise à jour avec succès.')
|
const preview = await syncPreview(id, enrichedPayload.structure || {})
|
||||||
await navigateBackToList()
|
|
||||||
|
const hasImpact = preview && (
|
||||||
|
Object.values(preview.additions || {}).some(v => v > 0)
|
||||||
|
|| Object.values(preview.deletions || {}).some(v => v > 0)
|
||||||
|
|| Object.values(preview.modifications || {}).some(v => v > 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (hasImpact) {
|
||||||
|
// Show modal for confirmation
|
||||||
|
pendingPayload.value = enrichedPayload
|
||||||
|
syncPreviewData.value = preview
|
||||||
|
showSyncModal.value = true
|
||||||
|
} else {
|
||||||
|
// No impact — save directly + sync
|
||||||
|
await updateModelType(id, enrichedPayload)
|
||||||
|
await syncExecute(id, { confirmDeletions: false, confirmTypeChanges: false })
|
||||||
|
await loadProductTypes({ force: true })
|
||||||
|
showSuccess('Catégorie de produit mise à jour avec succès.')
|
||||||
|
await navigateBackToList()
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(normalizeError(error))
|
showError(normalizeError(error))
|
||||||
} finally {
|
} finally {
|
||||||
@@ -134,6 +165,39 @@ const handleSubmit = async (payload: Parameters<typeof updateModelType>[1]) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSyncConfirm = async () => {
|
||||||
|
if (!pendingPayload.value) return
|
||||||
|
const id = String(route.params.id)
|
||||||
|
|
||||||
|
syncLoading.value = true
|
||||||
|
try {
|
||||||
|
const hasDeletions = syncPreviewData.value && Object.values(syncPreviewData.value.deletions || {}).some(v => v > 0)
|
||||||
|
const hasModifications = syncPreviewData.value && Object.values(syncPreviewData.value.modifications || {}).some(v => v > 0)
|
||||||
|
|
||||||
|
await updateModelType(id, pendingPayload.value)
|
||||||
|
await syncExecute(id, {
|
||||||
|
confirmDeletions: !!hasDeletions,
|
||||||
|
confirmTypeChanges: !!hasModifications,
|
||||||
|
})
|
||||||
|
await loadProductTypes({ force: true })
|
||||||
|
showSuccess('Catégorie de produit mise à jour avec succès.')
|
||||||
|
await navigateBackToList()
|
||||||
|
} catch (error) {
|
||||||
|
showError(normalizeError(error))
|
||||||
|
} finally {
|
||||||
|
syncLoading.value = false
|
||||||
|
showSyncModal.value = false
|
||||||
|
pendingPayload.value = null
|
||||||
|
syncPreviewData.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSyncCancel = () => {
|
||||||
|
showSyncModal.value = false
|
||||||
|
pendingPayload.value = null
|
||||||
|
syncPreviewData.value = null
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadCategory()
|
loadCategory()
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user