From 92bdd32f8679c64645a7e5f62bd58a505f2a1477 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 12 Oct 2026 11:45:00 +0800 Subject: [PATCH] fix(appointments): unify calendar layout and cabinet numbers --- .../AppointmentCalendarPanel.test.ts | 13 +++++++ .../AppointmentCalendarPanel.vue | 9 ++++- .../AppointmentCalendarView.test.ts | 37 ++++++++++++++----- .../AppointmentCalendarView.vue | 27 +++++++++----- .../AppointmentCargoPanel.test.ts | 29 +++++++++++++-- .../AppointmentCargoPanel.vue | 13 ++++--- .../AppointmentCargoTabs.vue | 28 ++++++++++++++ .../AppointmentExternalCandidates.vue | 7 +++- .../AppointmentTableRendering.test.ts | 4 ++ .../appointment-calendar/PROTOTYPE_MATRIX.md | 3 ++ src/modules/appointment-calendar/api.ts | 4 +- .../api/platformContainerLoading.test.ts | 2 +- .../components/ContainerOverviewPanel.test.ts | 2 +- .../components/OceanBookingLinkDialog.test.ts | 2 +- .../pages/ContainersView.test.ts | 22 +++++------ .../pages/ContainersView.vue | 2 +- .../pages/GlobalSmartLoadingView.test.ts | 4 +- .../pages/SmartLoadingWorkbenchView.test.ts | 2 +- src/modules/container-loading/types.ts | 4 +- .../pages/OutboundDispatchesView.test.ts | 2 +- .../pages/OutboundDispatchesView.vue | 2 +- .../components/FbaAppointmentFields.vue | 18 +++++---- .../ContainerPortSchedulePanel.test.ts | 2 +- .../components/ContainerPortSchedulePanel.vue | 4 +- src/styles/adminDensity.test.ts | 1 + 25 files changed, 178 insertions(+), 65 deletions(-) create mode 100644 src/modules/appointment-calendar/AppointmentCargoTabs.vue diff --git a/src/modules/appointment-calendar/AppointmentCalendarPanel.test.ts b/src/modules/appointment-calendar/AppointmentCalendarPanel.test.ts index b252e473..753a569b 100644 --- a/src/modules/appointment-calendar/AppointmentCalendarPanel.test.ts +++ b/src/modules/appointment-calendar/AppointmentCalendarPanel.test.ts @@ -3,6 +3,19 @@ import { describe, expect, it } from 'vitest' import AppointmentCalendarPanel from './AppointmentCalendarPanel.vue' describe('appointment calendar interactions', () => { + it('keeps creation beside the title and record actions beside data notices', () => { + const wrapper = mount(AppointmentCalendarPanel, { + props: { canCreate: true, events: [], notices: ['数据范围不完整'] }, + slots: { 'toolbar-actions': '' }, + global: { stubs: { ElButton: { template: '' } }, directives: { loading: {} } }, + }) + expect(wrapper.get('.calendar-label').text()).toContain('新增预约') + const actions = wrapper.get('[aria-label="预约记录与数据提示"]') + expect(actions.text()).toContain('未定时安排') + expect(actions.text()).toContain('取消记录') + expect(actions.get('summary').text()).toContain('数据提示') + wrapper.unmount() + }) const render = () => mount(AppointmentCalendarPanel, { props: { initialDate: '2026-09-24', canCreate: true, events: [ { key: 'a', type: 'delivery', date: '2026-09-24', time: '09:00-11:00', title: 'ONT8 预约送货', fields: [], canEdit: true, canDelete: true }, diff --git a/src/modules/appointment-calendar/AppointmentCalendarPanel.vue b/src/modules/appointment-calendar/AppointmentCalendarPanel.vue index cd4d492f..013de53d 100644 --- a/src/modules/appointment-calendar/AppointmentCalendarPanel.vue +++ b/src/modules/appointment-calendar/AppointmentCalendarPanel.vue @@ -84,10 +84,9 @@ defineExpose({ selectedDate, mode })
预约日历 + + 新增预约
-
数据提示 · {{ notices.length }}

{{ notice }}

- + 新增预约
@@ -97,6 +96,10 @@ defineExpose({ selectedDate, mode }) 今天
+
+ +
数据提示 · {{ notices.length }}

{{ notice }}

+
船舶到港预约送仓预约提柜用车计划
@@ -149,6 +152,8 @@ defineExpose({ selectedDate, mode }) .calendar-main, .calendar-detail { background: #fff; border: 1px solid #e4e9f2; border-radius: 10px; overflow: hidden; } .calendar-toolbar { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: var(--admin-space-3); padding: var(--admin-space-5); border-bottom: 1px solid #edf0f6; } .calendar-label, .calendar-controls { display: flex; align-items: center; gap: 10px; } +.calendar-record-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--admin-space-3); } +.calendar-record-actions :deep(.el-button + .el-button) { margin-left: 0; } .calendar-label > svg { color: #617ce9; } .calendar-label > strong { font-size: var(--admin-font-section); } .calendar-options { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--admin-space-3); padding: var(--admin-space-3) var(--admin-space-5); border-bottom: 1px solid var(--admin-table-border); } diff --git a/src/modules/appointment-calendar/AppointmentCalendarView.test.ts b/src/modules/appointment-calendar/AppointmentCalendarView.test.ts index 8c19a985..201a7c6d 100644 --- a/src/modules/appointment-calendar/AppointmentCalendarView.test.ts +++ b/src/modules/appointment-calendar/AppointmentCalendarView.test.ts @@ -40,7 +40,7 @@ function render(openCalendar = true) { const unregister = register(() => !routing.containerPending) onBeforeUnmount(unregister) }, template: '
整柜跟进工作区
' }, - AppointmentCalendarPanel: { name: 'AppointmentCalendarPanel', mounted() { this.$emit('layers', { arrivals: true, trips: true }) }, props: ['events', 'incomplete', 'notices'], template: '
' }, + AppointmentCalendarPanel: { name: 'AppointmentCalendarPanel', mounted() { this.$emit('layers', { arrivals: true, trips: true }) }, props: ['events', 'incomplete', 'notices'], template: '
' }, AppDialog: { name: 'AppDialog', props: ['modelValue', 'showConfirm', 'beforeClose'], template: '
' }, ElForm: { template: '
' }, ElFormItem: { template: '' }, @@ -70,6 +70,19 @@ describe('appointment calendar data integration', () => { vi.mocked(createFbaAppointment).mockResolvedValue({ version: 0, id: '1', appointmentNo: 'APT-AB', plannedDate: '2026-10-09', appointmentAt: null, originWarehouseId: '100', appointmentStatus: 'PENDING', selectedCount: 2, fbaWarehouseCode: 'ONT8', items: [] }) }) + it('displays the existing D cabinet number in both arrival and pickup events', async () => { + vi.mocked(listArrivalCalendar).mockResolvedValueOnce([{ containerBookingId: '1', cabinetNo: 'D0007', arrivalAt: '2026-10-09T08:00:00', arrivalType: 'ESTIMATED' }]) + vi.mocked(listPickupCalendar).mockResolvedValueOnce({ records: [{ containerBookingId: '1', cabinetNo: 'D0007', containerNo: null, destinationPortName: null, destinationWarehouseCode: null, fleetSupplierName: null, pickupAppointmentAt: '2026-10-09T09:00:00', actualPickupAt: null }], truncated: false, unclassifiedCount: 0, scopeBasis: 'US_DESTINATION_WAREHOUSE', timeBasis: 'SOURCE_LOCAL_UNSPECIFIED' }) + const wrapper = render(); await flushPromises() + const events = wrapper.getComponent({ name: 'AppointmentCalendarPanel' }).props('events') + expect(events).toHaveLength(2) + for (const event of events) { + expect(event.fields).toContainEqual({ label: '柜次号', value: 'D0007' }) + expect(event.title).toContain('D0007') + } + wrapper.unmount() + }) + it('opens a dated calendar appointment read-only for list-only users', async () => { routing.canWrite=false const record={version:0,id:'1',appointmentNo:'AP-DATED',plannedDate:'2026-10-15',originWarehouseId:'100',appointmentStatus:'PENDING',selectedCount:1,fbaWarehouseCode:'ABE3',items:[]} @@ -90,6 +103,7 @@ describe('appointment calendar data integration', () => { vi.mocked(listUnscheduledAppointments).mockResolvedValue({records: [record], total: 1}) vi.mocked(getFbaAppointment).mockResolvedValue(record) const wrapper=render(); await flushPromises() + await wrapper.get('[data-unscheduled]').trigger('click') await wrapper.findAll('button').find(button=>button.text()==='查看 / 继续')!.trigger('click'); await flushPromises() expect(wrapper.find('option[value="CANCELLED"]').exists()).toBe(false) await wrapper.get('[data-cancel-editing]').trigger('click'); await flushPromises() @@ -97,13 +111,13 @@ describe('appointment calendar data integration', () => { expect(wrapper.find('[data-save]').exists()).toBe(false) expect(updateFbaAppointment).not.toHaveBeenCalled(); wrapper.unmount() }) - it('loads cancellation records only after expansion and opens cancelled appointments read-only', async () => { + it('loads cancellation records only after opening the dialog and opens cancelled appointments read-only', async () => { const record = { version: 1, id: '1', appointmentNo: 'AP-CANCEL', originWarehouseId: '100', appointmentStatus: 'CANCELLED', selectedCount: 1, fbaWarehouseCode: 'ABE3', items: [] } vi.mocked(getFbaAppointment).mockResolvedValue(record) const wrapper=render();await flushPromises() expect(wrapper.findComponent({name:'AppointmentCancellationRecords'}).exists()).toBe(false) - const records=wrapper.findAll('details').find(d=>d.find('summary').text()==='取消记录')! - ;(records.element as HTMLDetailsElement).open=true;await records.trigger('toggle') + const records=wrapper.get('[data-cancellations]') + await records.trigger('click') await wrapper.get('[data-history-view]').trigger('click');await flushPromises() expect(getFbaAppointment).toHaveBeenCalledWith('AP-CANCEL') expect(wrapper.find('[data-save]').exists()).toBe(false) @@ -119,8 +133,8 @@ describe('appointment calendar data integration', () => { it('refreshes an expanded cancellation list after a successful cancellation', async () => { vi.mocked(listFbaAppointmentCalendar).mockResolvedValue([{version:0,id:'1',appointmentNo:'AP-CANCEL',originWarehouseId:'100',appointmentStatus:'PENDING',selectedCount:1,fbaWarehouseCode:'ABE3',items:[],plannedDate:'2026-10-15'}]) const wrapper=render();await flushPromises() - const records=wrapper.findAll('details').find(d=>d.find('summary').text()==='取消记录')! - ;(records.element as HTMLDetailsElement).open=true;await records.trigger('toggle') + const records=wrapper.get('[data-cancellations]') + await records.trigger('click') const previous=wrapper.getComponent({name:'AppointmentCancellationRecords'}).vm wrapper.getComponent({name:'AppointmentCalendarPanel'}).vm.$emit('delete',{key:'AP-CANCEL'});await flushPromises() wrapper.getComponent({name:'AppointmentCancellationDialog'}).vm.$emit('cancelled');await flushPromises() @@ -150,6 +164,7 @@ describe('appointment calendar data integration', () => { vi.mocked(listUnscheduledAppointments).mockResolvedValue({ records: [record], total: 1 }) vi.mocked(getFbaAppointment).mockResolvedValue(record) const wrapper = render(); await flushPromises() + await wrapper.get('[data-unscheduled]').trigger('click') await wrapper.findAll('button').find(b => b.text() === '查看 / 继续')!.trigger('click'); await flushPromises() expect(wrapper.find('[data-save]').exists()).toBe(false) const dialog = wrapper.findAllComponents({ name: 'AppDialog' }).at(-1)! @@ -179,7 +194,7 @@ describe('appointment calendar data integration', () => { expect(wrapper.text()).not.toContain('缩小日期范围') }) it('shows the original pickup time once without claiming a port time zone and retains partial-source warnings', async () => { - vi.mocked(listPickupCalendar).mockResolvedValueOnce({records: [{containerBookingId: '123',bookingNo: 'CB-TEST',containerNo: 'TEST-CONT',destinationPortName: 'Port',destinationWarehouseCode: 'US-W',fleetSupplierName: 'Fleet',pickupAppointmentAt: '2026-10-09T11:00:00',actualPickupAt: null}],truncated: false,unclassifiedCount: 1,scopeBasis: 'US_DESTINATION_WAREHOUSE',timeBasis: 'SOURCE_LOCAL_UNSPECIFIED'}) + vi.mocked(listPickupCalendar).mockResolvedValueOnce({records: [{containerBookingId: '123',cabinetNo: 'D0001',containerNo: 'TEST-CONT',destinationPortName: 'Port',destinationWarehouseCode: 'US-W',fleetSupplierName: 'Fleet',pickupAppointmentAt: '2026-10-09T11:00:00',actualPickupAt: null}],truncated: false,unclassifiedCount: 1,scopeBasis: 'US_DESTINATION_WAREHOUSE',timeBasis: 'SOURCE_LOCAL_UNSPECIFIED'}) const wrapper=render(); await flushPromises() const panel=wrapper.findComponent({name:'AppointmentCalendarPanel'}) expect(panel.props('events')).toEqual([expect.objectContaining({type:'pickup',date:'2026-10-09',time:'11:00',sourcePath:'/container-loading/overseas/123'})]) @@ -196,7 +211,7 @@ describe('appointment calendar data integration', () => { }) it('keeps successful arrival data visible when the pickup source fails', async () => { vi.mocked(listPickupCalendar).mockRejectedValueOnce(new Error('pickup unavailable')) - vi.mocked(listArrivalCalendar).mockResolvedValueOnce([{containerBookingId:'1',bookingNo:'CB-1',arrivalAt:'2026-10-09T09:00:00',arrivalType:'ESTIMATED'}]) + vi.mocked(listArrivalCalendar).mockResolvedValueOnce([{containerBookingId:'1',cabinetNo:'D0001',arrivalAt:'2026-10-09T09:00:00',arrivalType:'ESTIMATED'}]) const wrapper=render(); await flushPromises() const panel=wrapper.findComponent({name:'AppointmentCalendarPanel'}) expect(panel.props('events')).toEqual([expect.objectContaining({type:'arrival',key:'arrival-1'})]) @@ -248,7 +263,7 @@ describe('appointment calendar data integration', () => { expect(listPlatformWarehouses).toHaveBeenCalledWith(expect.objectContaining({ warehouseType: 'FBA', status: 'ACTIVE', size: 50 })) expect(wrapper.find('input[placeholder="例如 ONT8"]').exists()).toBe(false) expect(wrapper.text()).not.toContain('查询可预约货物') - expect(wrapper.find('details').attributes('open')).toBeUndefined() + expect(wrapper.findComponent({name:'AppointmentCancellationRecords'}).exists()).toBe(false) await wrapper.get('select[aria-label="预约仓库"]').setValue('ONT8') await flushPromises() expect(listInboundOrders).toHaveBeenLastCalledWith({ page: 1, size: 10, fbaWarehouseCode: 'ONT8' }) @@ -283,6 +298,8 @@ describe('appointment calendar data integration', () => { const wrapper=render(); await flushPromises() expect(wrapper.text()).toContain('未定时安排(1)') expect(wrapper.findComponent({name:'AppointmentCalendarPanel'}).props('events')).toEqual([]) + expect(wrapper.findComponent({name:'DataTable'}).exists()).toBe(false) + await wrapper.get('[data-unscheduled]').trigger('click') expect(wrapper.findComponent({name:'DataTable'}).props('data')[0]).not.toHaveProperty('appointmentAt') }) @@ -335,7 +352,7 @@ describe('appointment calendar data integration', () => { it('keeps available arrivals and marks the result incomplete when appointments fail', async () => { vi.mocked(listFbaAppointmentCalendar).mockRejectedValueOnce(new Error('预约暂不可用')) - vi.mocked(listArrivalCalendar).mockResolvedValueOnce([{ containerBookingId: '1', bookingNo: 'C-1', arrivalAt: '2026-10-09T09:00:00', arrivalType: 'ESTIMATED' }]) + vi.mocked(listArrivalCalendar).mockResolvedValueOnce([{ containerBookingId: '1', cabinetNo: 'D0001', arrivalAt: '2026-10-09T09:00:00', arrivalType: 'ESTIMATED' }]) const wrapper = render() await flushPromises() const calendar = wrapper.findComponent({ name: 'AppointmentCalendarPanel' }) diff --git a/src/modules/appointment-calendar/AppointmentCalendarView.vue b/src/modules/appointment-calendar/AppointmentCalendarView.vue index 843de4e2..ed87fa2e 100644 --- a/src/modules/appointment-calendar/AppointmentCalendarView.vue +++ b/src/modules/appointment-calendar/AppointmentCalendarView.vue @@ -31,6 +31,7 @@ import { listArrivalCalendar, listPickupCalendar, type ArrivalCalendarEntry, typ import { listTripCalendar, projectTripCalendar, tripIssues, type TripCalendarEntry } from './tripCalendar' const cancellationTarget = ref(null) +const unscheduledOpen = ref(false) const cancellationsOpen = ref(false), cancellationsRevision = ref(0), cancellationHistoryOpen = ref(false), forceReadOnly = ref(false) const appointmentReadOnly = computed(() => forceReadOnly.value || editing.value?.appointmentStatus === 'CANCELLED' || !access.can(permission.inboundOrderAppointment)) const access = useAccessPreviewStore() @@ -130,22 +131,22 @@ const columns: DataTableColumn[] = [ const events = computed(() => [...arrivals.value.map(arrival => ({ key: `arrival-${arrival.containerBookingId}`, type: 'arrival' as const, date: arrival.arrivalAt.slice(0, 10), time: arrival.arrivalAt.slice(11, 16), - title: `${arrival.voyageNo || arrival.bookingNo} ${arrival.arrivalType === 'ACTUAL' ? '实际到港' : '预计到港'}`, + title: `${arrival.voyageNo || arrival.cabinetNo} ${arrival.arrivalType === 'ACTUAL' ? '实际到港' : '预计到港'}`, fields: [ { label: '船名 / 航次', value: [arrival.vesselName, arrival.voyageNo].filter(Boolean).join(' / ') }, { label: '目的港', value: arrival.destinationPortName }, { label: '已装货柜号', value: arrival.containerNo }, - { label: '柜次号', value: arrival.bookingNo }, + { label: '柜次号', value: arrival.cabinetNo }, { label: '货物去向', value: arrival.destinationWarehouseCode }, ], })), ...pickups.value.map(pickup => ({ key: `pickup-${pickup.containerBookingId}`, type: 'pickup' as const, date: pickup.pickupAppointmentAt.slice(0, 10), time: pickup.pickupAppointmentAt.slice(11, 16), - title: `${pickup.containerNo || pickup.bookingNo || '未关联柜号'} 预约提柜`, + title: `${pickup.containerNo || pickup.cabinetNo || '未关联柜号'} 预约提柜`, sourcePath: `/container-loading/overseas/${encodeURIComponent(pickup.containerBookingId)}`, subtitle: '美国目的仓相关提柜安排', fields: [ - { label: '柜次号', value: pickup.bookingNo }, { label: '目的港', value: pickup.destinationPortName }, + { label: '柜次号', value: pickup.cabinetNo }, { label: '目的港', value: pickup.destinationPortName }, { label: '目的仓', value: pickup.destinationWarehouseCode }, { label: '车队', value: pickup.fleetSupplierName }, { label: '实际提柜', value: pickup.actualPickupAt || '尚未记录' }, { label: '时间口径', value: '原提柜业务时间,港口时区待核实' }, @@ -416,13 +417,23 @@ onBeforeUnmount(() => { ++unscheduledRequest; ++calendarRequest; ++candidateRequ
- -
未定时安排({{ unscheduledTotal }})
-
取消记录
+
+ + + + + + + + +

这些车次缺少地点或时区,暂未计入日历。原计划时间保留 UTC。

@@ -470,7 +481,5 @@ onBeforeUnmount(() => { ++unscheduledRequest; ++calendarRequest; ++candidateRequ .calendar-form { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0 var(--admin-space-6); } .calendar-form :deep(.el-select), .calendar-form :deep(.el-date-editor) { width: 100%; } .appointment-cargo { padding: 12px 0; line-height: 1.7; overflow-wrap: anywhere; font-size: 13px; } -.appointment-extra { margin-bottom: var(--admin-space-5); } -.appointment-extra summary { cursor: pointer; margin-bottom: var(--admin-space-3); font-size: var(--admin-font-body); } .appointment-hint { color: var(--el-text-color-secondary); font-size: var(--admin-font-body); margin: var(--admin-space-5) 0; } diff --git a/src/modules/appointment-calendar/AppointmentCargoPanel.test.ts b/src/modules/appointment-calendar/AppointmentCargoPanel.test.ts index f4be5639..8fed4418 100644 --- a/src/modules/appointment-calendar/AppointmentCargoPanel.test.ts +++ b/src/modules/appointment-calendar/AppointmentCargoPanel.test.ts @@ -7,15 +7,38 @@ vi.mock('./planningApi', () => ({ listPlanningCargo: vi.fn(), resolvePlanningSou vi.mock('vue-router', () => ({ onBeforeRouteLeave: vi.fn(), onBeforeRouteUpdate: vi.fn(), useRouter: () => ({ push: vi.fn() }) })) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) })) const empty = { records: [], page: 1, size: 20, total: 0, pages: 0 } -function render() { - return mount(AppointmentCargoPanel, { global: { stubs: { +function render(attach = false) { + return mount(AppointmentCargoPanel, { attachTo: attach ? document.body : undefined, global: { stubs: { DataTable: { name: 'DataTable', props: ['data', 'total', 'resultsVisible'], emits: ['update:page'], template: '
' }, QueryToolbar: { name: 'QueryToolbar', emits: ['search'], template: '' }, - LoadErrorState: true, ApiErrorMeta: true, RowActions: true, + LoadErrorState: true, ApiErrorMeta: true, RowActions: true, AppointmentExternalCandidates: { name: 'AppointmentExternalCandidates', template: '
' }, } } }) } describe('appointment planning cargo query', () => { beforeEach(() => { vi.resetAllMocks(); vi.mocked(listPlanningCargo).mockResolvedValue(empty) }) + it('switches cargo tabs by click and keyboard without reloading first-leg cargo', async () => { + const wrapper = render(true); await flushPromises() + const tabs = () => wrapper.findAll('[role="tab"]') + expect(tabs().map(tab => tab.text())).toEqual(['头程货物', '外接货物']) + expect(wrapper.find('.integrated-query-group [role="tablist"]').exists()).toBe(true) + expect(tabs()[0]!.attributes('aria-selected')).toBe('true') + await tabs()[1]!.trigger('click') + expect(tabs()[1]!.attributes('aria-selected')).toBe('true') + expect(wrapper.findComponent({ name: 'AppointmentExternalCandidates' }).exists()).toBe(true) + await tabs()[1]!.trigger('keydown', { key: 'ArrowRight' }) + await flushPromises() + expect(tabs()[0]!.attributes('aria-selected')).toBe('true') + expect(document.activeElement).toBe(tabs()[0]!.element) + expect(tabs()[1]!.attributes('tabindex')).toBe('-1') + await tabs()[0]!.trigger('keydown', { key: 'End' }) + await flushPromises() + expect(tabs()[1]!.attributes('aria-selected')).toBe('true') + expect(document.activeElement).toBe(tabs()[1]!.element) + await tabs()[1]!.trigger('keydown', { key: 'Home' }) + expect(tabs()[0]!.attributes('aria-selected')).toBe('true') + expect(listPlanningCargo).toHaveBeenCalledTimes(1) + wrapper.unmount() + }) it('uses one server-page request per entry, page change and reset', async () => { const wrapper = render(); await flushPromises() expect(listPlanningCargo).toHaveBeenCalledTimes(1) diff --git a/src/modules/appointment-calendar/AppointmentCargoPanel.vue b/src/modules/appointment-calendar/AppointmentCargoPanel.vue index 42484968..824bf14b 100644 --- a/src/modules/appointment-calendar/AppointmentCargoPanel.vue +++ b/src/modules/appointment-calendar/AppointmentCargoPanel.vue @@ -13,6 +13,7 @@ import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http' import { listPlanningCargo, resolvePlanningSource, previewPlanningBatch, createSourceAppointment, type SourceAppointmentPayload, type PlanningBatchPreview, type ResolvedPlanningSource, type PlanningCargo, type PlanningSource } from './planningApi' import AppointmentFollowupDialog from './AppointmentFollowupDialog.vue' import AppointmentExternalCandidates from './AppointmentExternalCandidates.vue' +import AppointmentCargoTabs from './AppointmentCargoTabs.vue' import { useAccessPreviewStore } from '@/core/access/accessPreview' const router = useRouter() @@ -149,11 +150,8 @@ onBeforeUnmount(() => { ++request; ++followupRequest; ++batchRequest; controller