| @@ -48,3 +48,18 @@ export const saveLogisticsBatchCreateAction = async ( | |||||
| headers: { "Content-Type": "application/json" }, | headers: { "Content-Type": "application/json" }, | ||||
| }); | }); | ||||
| }; | }; | ||||
| export type DeleteLogisticRequest = { | |||||
| id: number; | |||||
| }; | |||||
| export const deleteLogisticAction = async ( | |||||
| data: DeleteLogisticRequest, | |||||
| ): Promise<{ message?: string }> => { | |||||
| const endpoint = `${BASE_API_URL}/logistic/delete`; | |||||
| return serverFetchJson<{ message?: string }>(endpoint, { | |||||
| method: "POST", | |||||
| body: JSON.stringify(data), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }); | |||||
| }; | |||||
| @@ -4,6 +4,7 @@ import { | |||||
| findAllLogisticsAction, | findAllLogisticsAction, | ||||
| saveLogisticAction, | saveLogisticAction, | ||||
| saveLogisticsBatchCreateAction, | saveLogisticsBatchCreateAction, | ||||
| deleteLogisticAction, | |||||
| type LogisticRow, | type LogisticRow, | ||||
| type SaveLogisticRequest, | type SaveLogisticRequest, | ||||
| } from "./actions"; | } from "./actions"; | ||||
| @@ -25,3 +26,7 @@ export const saveLogisticClient = async ( | |||||
| ): Promise<LogisticRow> => { | ): Promise<LogisticRow> => { | ||||
| return await saveLogisticAction(data); | return await saveLogisticAction(data); | ||||
| }; | }; | ||||
| export const deleteLogisticClient = async (id: number): Promise<void> => { | |||||
| await deleteLogisticAction({ id }); | |||||
| }; | |||||
| @@ -519,6 +519,9 @@ export type TruckLaneVersionDiffLine = { | |||||
| truckRowId: number; | truckRowId: number; | ||||
| shopCode: string | null; | shopCode: string | null; | ||||
| changes: DiffFieldChange[]; | changes: DiffFieldChange[]; | ||||
| /** 快照車線(僅欄位異動時 changes 不含 truckLanceCode/remark) */ | |||||
| truckLanceCode?: string | null; | |||||
| remark?: string | null; | |||||
| }; | }; | ||||
| export type LogisticMasterDiffLine = { | export type LogisticMasterDiffLine = { | ||||
| @@ -27,9 +27,11 @@ function pickField( | |||||
| return changes.find((c) => c.field === field); | return changes.find((c) => c.field === field); | ||||
| } | } | ||||
| export const VERSION_LOG_LOADING_SEQUENCE_LABEL = "裝載順序"; | |||||
| const VERSION_LOG_FIELD_LABEL: Record<string, string> = { | const VERSION_LOG_FIELD_LABEL: Record<string, string> = { | ||||
| departureTime: "發車時段", | departureTime: "發車時段", | ||||
| loadingSequence: "裝載順序", | |||||
| loadingSequence: VERSION_LOG_LOADING_SEQUENCE_LABEL, | |||||
| branchName: "分店名稱", | branchName: "分店名稱", | ||||
| districtReference: "區域", | districtReference: "區域", | ||||
| shopCode: "店鋪代碼", | shopCode: "店鋪代碼", | ||||
| @@ -91,6 +93,19 @@ function buildFieldEditsForRow( | |||||
| return out.length > 0 ? out : undefined; | return out.length > 0 ? out : undefined; | ||||
| } | } | ||||
| /** 同車線編輯(如僅裝載順序)時用於 UI 顯示車線 */ | |||||
| export function resolveVersionLogLaneLabel( | |||||
| row: Pick<VersionLogShopRow, "fromLane" | "toLane">, | |||||
| ): string | undefined { | |||||
| const lane = | |||||
| row.toLane && row.toLane !== "—" | |||||
| ? row.toLane | |||||
| : row.fromLane && row.fromLane !== "—" | |||||
| ? row.fromLane | |||||
| : undefined; | |||||
| return lane; | |||||
| } | |||||
| export function formatLaneLabel( | export function formatLaneLabel( | ||||
| truckLanceCode?: string | null, | truckLanceCode?: string | null, | ||||
| remark?: string | null, | remark?: string | null, | ||||
| @@ -138,8 +153,17 @@ export function diffLineToShopRow( | |||||
| const code = line.shopCode != null ? String(line.shopCode) : ""; | const code = line.shopCode != null ? String(line.shopCode) : ""; | ||||
| const shopName = String(br?.to ?? br?.from ?? (code || "—")).trim() || "—"; | const shopName = String(br?.to ?? br?.from ?? (code || "—")).trim() || "—"; | ||||
| const fromLane = formatLaneLabel(tc?.from, rem?.from); | |||||
| const toLane = formatLaneLabel(tc?.to, rem?.to); | |||||
| const ctxCode = | |||||
| line.truckLanceCode != null && String(line.truckLanceCode).trim() !== "" | |||||
| ? String(line.truckLanceCode).trim() | |||||
| : undefined; | |||||
| const ctxRem = | |||||
| line.remark != null && String(line.remark).trim() !== "" | |||||
| ? String(line.remark).trim() | |||||
| : undefined; | |||||
| const fromLane = formatLaneLabel(tc?.from ?? ctxCode, rem?.from ?? ctxRem); | |||||
| const toLane = formatLaneLabel(tc?.to ?? ctxCode, rem?.to ?? ctxRem); | |||||
| const fromEmpty = fromLane === "—"; | const fromEmpty = fromLane === "—"; | ||||
| const toEmpty = toLane === "—"; | const toEmpty = toLane === "—"; | ||||
| @@ -248,6 +272,10 @@ export function buildStagedBoardLogEntries(input: { | |||||
| fromName?: string; | fromName?: string; | ||||
| fromPlate?: string; | fromPlate?: string; | ||||
| }>; | }>; | ||||
| pendingLogisticMasterDeletes?: Array<{ | |||||
| id: number; | |||||
| logisticName: string; | |||||
| }>; | |||||
| pendingImport?: { | pendingImport?: { | ||||
| fileName: string; | fileName: string; | ||||
| sheetCount: number; | sheetCount: number; | ||||
| @@ -283,6 +311,7 @@ export function buildStagedBoardLogEntries(input: { | |||||
| pendingNewLanes, | pendingNewLanes, | ||||
| pendingLogisticMasterAdds, | pendingLogisticMasterAdds, | ||||
| pendingLogisticMasterEdits, | pendingLogisticMasterEdits, | ||||
| pendingLogisticMasterDeletes, | |||||
| pendingImport, | pendingImport, | ||||
| laneLogisticChanges, | laneLogisticChanges, | ||||
| lanes, | lanes, | ||||
| @@ -349,6 +378,17 @@ export function buildStagedBoardLogEntries(input: { | |||||
| }); | }); | ||||
| } | } | ||||
| for (const p of pendingLogisticMasterDeletes ?? []) { | |||||
| const name = String(p.logisticName ?? "").trim() || "—"; | |||||
| out.push({ | |||||
| key: `plog-del-${p.id}`, | |||||
| tag: "unsaved", | |||||
| kind: "text", | |||||
| titleKey: "diff_staged_deleteLogisticMaster", | |||||
| titleParams: { name }, | |||||
| }); | |||||
| } | |||||
| const dirtyMoveShopIds = new Set<number>(); | const dirtyMoveShopIds = new Set<number>(); | ||||
| dirtyMoves.forEach((laneId, shopId) => { | dirtyMoves.forEach((laneId, shopId) => { | ||||
| @@ -416,6 +456,8 @@ export function buildStagedBoardLogEntries(input: { | |||||
| type: "edited", | type: "edited", | ||||
| shopName, | shopName, | ||||
| shopCode, | shopCode, | ||||
| fromLane: laneLabel, | |||||
| toLane: laneLabel, | |||||
| truckRowId: shopId, | truckRowId: shopId, | ||||
| fieldEdits, | fieldEdits, | ||||
| }, | }, | ||||
| @@ -87,6 +87,7 @@ | |||||
| "logistic_needMasterTpl": "\"{{name}}\" has no logistics master id—create it with \"Add logistics\" first.", | "logistic_needMasterTpl": "\"{{name}}\" has no logistics master id—create it with \"Add logistics\" first.", | ||||
| "diffField_logisticsCompany": "Logistics company", | "diffField_logisticsCompany": "Logistics company", | ||||
| "diffLogistic_unassigned": "Unassigned", | "diffLogistic_unassigned": "Unassigned", | ||||
| "diff_onLane": "Lane {{lane}}", | |||||
| "diff_moveTo": "Move to {{lane}}", | "diff_moveTo": "Move to {{lane}}", | ||||
| "diff_addedToLane": "Added to lane {{lane}}", | "diff_addedToLane": "Added to lane {{lane}}", | ||||
| "diff_removedFromLane": "Removed from {{lane}}", | "diff_removedFromLane": "Removed from {{lane}}", | ||||
| @@ -119,6 +120,7 @@ | |||||
| "version_ui_listAria": "Version history list", | "version_ui_listAria": "Version history list", | ||||
| "version_ui_snapshotBadge": "Current snapshot", | "version_ui_snapshotBadge": "Current snapshot", | ||||
| "version_ui_id": "Version #{{id}}", | "version_ui_id": "Version #{{id}}", | ||||
| "version_ui_none": "No snapshot yet", | |||||
| "version_ui_editedBy": "Editor: {{name}}", | "version_ui_editedBy": "Editor: {{name}}", | ||||
| "version_note_placeholder": "Note (saved on blur)", | "version_note_placeholder": "Note (saved on blur)", | ||||
| "version_note_saving": "Saving…", | "version_note_saving": "Saving…", | ||||
| @@ -187,7 +189,6 @@ | |||||
| "logistic_plate": "Plate", | "logistic_plate": "Plate", | ||||
| "logistic_driver": "Driver name", | "logistic_driver": "Driver name", | ||||
| "logistic_phone": "Phone", | "logistic_phone": "Phone", | ||||
| "logistic_phone_helper": "Server stores Int digits (e.g. 9811-5780); +852 8-digit local numbers are OK", | |||||
| "logistic_btn_save": "Save", | "logistic_btn_save": "Save", | ||||
| "logistic_btn_saveDb": "Save to database", | "logistic_btn_saveDb": "Save to database", | ||||
| "shop_autocomplete_label": "Select shop", | "shop_autocomplete_label": "Select shop", | ||||
| @@ -209,7 +210,6 @@ | |||||
| "seqDialog_hint": "Press \"Save changes\" to persist to truck rows.", | "seqDialog_hint": "Press \"Save changes\" to persist to truck rows.", | ||||
| "logistics_colLaneCount": "{{count}} lane(s)", | "logistics_colLaneCount": "{{count}} lane(s)", | ||||
| "logistics_masterNoLanes": "Master record exists but no lanes are bound yet; pick this company when adding/editing lanes on the route board.", | "logistics_masterNoLanes": "Master record exists but no lanes are bound yet; pick this company when adding/editing lanes on the route board.", | ||||
| "logistics_dataSource": "Data: board lanes (including left filter)", | |||||
| "tooltip_openLaneBoard": "Open this lane on the route board", | "tooltip_openLaneBoard": "Open this lane on the route board", | ||||
| "aria_openLaneBoard": "Open lane on route board", | "aria_openLaneBoard": "Open lane on route board", | ||||
| "tooltip_removeFromLane": "Remove from this lane", | "tooltip_removeFromLane": "Remove from this lane", | ||||
| @@ -218,8 +218,14 @@ | |||||
| "aria_pickLane": "Pick lane", | "aria_pickLane": "Pick lane", | ||||
| "aria_searchLanes": "Search lanes", | "aria_searchLanes": "Search lanes", | ||||
| "logistics_colShopCount": "{{count}} shop(s)", | "logistics_colShopCount": "{{count}} shop(s)", | ||||
| "tooltip_editLogisticsDb": "Edit logistics master (save to database)", | |||||
| "aria_editLogistics": "Edit logistics master", | |||||
| "tooltip_editLogisticsDb": "Edit logistics company (persists after Save changes)", | |||||
| "tooltip_deleteLogistics": "Delete logistics company (persists after Save changes)", | |||||
| "aria_editLogistics": "Edit logistics company", | |||||
| "aria_deleteLogistics": "Delete logistics company", | |||||
| "confirm_deleteLogistic": "Delete logistics company \"{{name}}\"? Press \"Save changes\" to persist.", | |||||
| "err_logisticDeleteHasLanes": "This company still has {{count}} lane(s). Reassign or remove them first.", | |||||
| "diff_staged_deleteLogisticMaster": "Delete logistics company (not saved): {{name}}", | |||||
| "logistic_btn_apply": "Apply", | |||||
| "tooltip_editDistrict": "Edit district name (press \"Save changes\" to persist)", | "tooltip_editDistrict": "Edit district name (press \"Save changes\" to persist)", | ||||
| "aria_editDistrict": "Edit district", | "aria_editDistrict": "Edit district", | ||||
| "tooltip_removeEmptyDistrict": "Remove this staged empty block (deletable before save)", | "tooltip_removeEmptyDistrict": "Remove this staged empty block (deletable before save)", | ||||
| @@ -78,7 +78,7 @@ | |||||
| "versionNote_saveFail": "備註儲存失敗", | "versionNote_saveFail": "備註儲存失敗", | ||||
| "diff_restoreFail": "恢復失敗", | "diff_restoreFail": "恢復失敗", | ||||
| "confirm_restoreDiscardsEdits": "排程版本還原會捨棄目前看板上其他未儲存變更(拖曳、刪除、新增店鋪/車線、物流欄等)。確定繼續?", | "confirm_restoreDiscardsEdits": "排程版本還原會捨棄目前看板上其他未儲存變更(拖曳、刪除、新增店鋪/車線、物流欄等)。確定繼續?", | ||||
| "diff_restoreScheduled": "已排程還原至版本 #{{versionId}};請按「儲存更改」才會寫入後端。", | |||||
| "diff_restoreScheduled": "已排程還原至版本 #{{versionId}};請按「儲存更改」寫入後端。", | |||||
| "diff_restoreAlreadyPending": "此版本已在排程還原中;請按「儲存更改」套用。", | "diff_restoreAlreadyPending": "此版本已在排程還原中;請按「儲存更改」套用。", | ||||
| "restore_applied": "已從 snapshot 還原並重新載入看板。", | "restore_applied": "已從 snapshot 還原並重新載入看板。", | ||||
| "restore_appliedDroppedStaging": "已套用 snapshot 還原;本次儲存略過其他暫存變更(請重新編輯)。", | "restore_appliedDroppedStaging": "已套用 snapshot 還原;本次儲存略過其他暫存變更(請重新編輯)。", | ||||
| @@ -87,6 +87,7 @@ | |||||
| "logistic_needMasterTpl": "「{{name}}」尚無對應物流公司,請先用「新增物流商」建立。", | "logistic_needMasterTpl": "「{{name}}」尚無對應物流公司,請先用「新增物流商」建立。", | ||||
| "diffField_logisticsCompany": "物流公司", | "diffField_logisticsCompany": "物流公司", | ||||
| "diffLogistic_unassigned": "未分配", | "diffLogistic_unassigned": "未分配", | ||||
| "diff_onLane": "車線 {{lane}}", | |||||
| "diff_moveTo": "移至 {{lane}}", | "diff_moveTo": "移至 {{lane}}", | ||||
| "diff_addedToLane": "新加入車線 {{lane}}", | "diff_addedToLane": "新加入車線 {{lane}}", | ||||
| "diff_removedFromLane": "自 {{lane}} 移除", | "diff_removedFromLane": "自 {{lane}} 移除", | ||||
| @@ -118,7 +119,8 @@ | |||||
| "version_ui_filterAria": "版本列表篩選", | "version_ui_filterAria": "版本列表篩選", | ||||
| "version_ui_listAria": "版本歷史列表", | "version_ui_listAria": "版本歷史列表", | ||||
| "version_ui_snapshotBadge": "目前快照", | "version_ui_snapshotBadge": "目前快照", | ||||
| "version_ui_id": "版本 #{{id}}", | |||||
| "version_ui_id": "版本#{{id}}", | |||||
| "version_ui_none": "尚無快照", | |||||
| "version_ui_editedBy": "編輯者:{{name}}", | "version_ui_editedBy": "編輯者:{{name}}", | ||||
| "version_note_placeholder": "備註(離開欄位即儲存)", | "version_note_placeholder": "備註(離開欄位即儲存)", | ||||
| "version_note_saving": "儲存中…", | "version_note_saving": "儲存中…", | ||||
| @@ -162,7 +164,7 @@ | |||||
| "diff_noShopDiffHasBoardStaged": "與上一版快照相比,店鋪列無差異;下列為看板上尚未按「儲存更改」寫入的變更(含新增物流公司)。", | "diff_noShopDiffHasBoardStaged": "與上一版快照相比,店鋪列無差異;下列為看板上尚未按「儲存更改」寫入的變更(含新增物流公司)。", | ||||
| "diff_export_blockedTooltip": "匯出檔為後端兩版快照比對,不含看板未儲存變更。請先按「儲存更改」或取消變更後再匯出。", | "diff_export_blockedTooltip": "匯出檔為後端兩版快照比對,不含看板未儲存變更。請先按「儲存更改」或取消變更後再匯出。", | ||||
| "diff_export_blockedError": "有看板未儲存變更時無法匯出(Excel 僅含已落庫快照)。", | "diff_export_blockedError": "有看板未儲存變更時無法匯出(Excel 僅含已落庫快照)。", | ||||
| "diff_markedCount": "已標記 {{count}} 筆 truck 異動(看板可對照)", | |||||
| "diff_markedCount": "已標記 {{count}} 筆車線異動(看板可對照)", | |||||
| "diff_noDiffFromPrev": "與上一版無差異", | "diff_noDiffFromPrev": "與上一版無差異", | ||||
| "diff_loadingEllipsis": "…", | "diff_loadingEllipsis": "…", | ||||
| "addShop_dialogTitle": "新增店鋪到車線", | "addShop_dialogTitle": "新增店鋪到車線", | ||||
| @@ -187,7 +189,6 @@ | |||||
| "logistic_plate": "車牌", | "logistic_plate": "車牌", | ||||
| "logistic_driver": "司機姓名", | "logistic_driver": "司機姓名", | ||||
| "logistic_phone": "聯絡電話", | "logistic_phone": "聯絡電話", | ||||
| "logistic_phone_helper": "後端為 Int:輸入數字即可(含 9811-5780);+852 八位本地號亦可", | |||||
| "logistic_btn_save": "儲存", | "logistic_btn_save": "儲存", | ||||
| "logistic_btn_saveDb": "儲存至資料庫", | "logistic_btn_saveDb": "儲存至資料庫", | ||||
| "shop_autocomplete_label": "選擇店鋪", | "shop_autocomplete_label": "選擇店鋪", | ||||
| @@ -209,7 +210,6 @@ | |||||
| "seqDialog_hint": "按「儲存更改」後寫入 truck 列。", | "seqDialog_hint": "按「儲存更改」後寫入 truck 列。", | ||||
| "logistics_colLaneCount": "{{count}} 條車線", | "logistics_colLaneCount": "{{count}} 條車線", | ||||
| "logistics_masterNoLanes": "主檔已建立,尚無綁定車線;至「車線看板」新增/編輯車線時可填此公司名稱。", | "logistics_masterNoLanes": "主檔已建立,尚無綁定車線;至「車線看板」新增/編輯車線時可填此公司名稱。", | ||||
| "logistics_dataSource": "資料來源:看板車線(含左欄篩選)", | |||||
| "tooltip_openLaneBoard": "在車線看板開此車線", | "tooltip_openLaneBoard": "在車線看板開此車線", | ||||
| "aria_openLaneBoard": "開啟車線看板", | "aria_openLaneBoard": "開啟車線看板", | ||||
| "tooltip_removeFromLane": "從此車線移除", | "tooltip_removeFromLane": "從此車線移除", | ||||
| @@ -218,8 +218,14 @@ | |||||
| "aria_pickLane": "選擇車線", | "aria_pickLane": "選擇車線", | ||||
| "aria_searchLanes": "搜尋車線", | "aria_searchLanes": "搜尋車線", | ||||
| "logistics_colShopCount": "{{count}} 家店鋪", | "logistics_colShopCount": "{{count}} 家店鋪", | ||||
| "tooltip_editLogisticsDb": "編輯物流公司(寫入資料庫)", | |||||
| "tooltip_editLogisticsDb": "編輯物流公司(須按「儲存更改」寫入)", | |||||
| "tooltip_deleteLogistics": "刪除物流公司(須按「儲存更改」寫入)", | |||||
| "aria_editLogistics": "編輯物流公司", | "aria_editLogistics": "編輯物流公司", | ||||
| "aria_deleteLogistics": "刪除物流公司", | |||||
| "confirm_deleteLogistic": "確定刪除物流公司「{{name}}」?須按「儲存更改」寫入。", | |||||
| "err_logisticDeleteHasLanes": "此物流公司尚有 {{count}} 條車線,請先移走或改派後再刪除。", | |||||
| "diff_staged_deleteLogisticMaster": "刪除物流公司(未落庫):{{name}}", | |||||
| "logistic_btn_apply": "套用", | |||||
| "tooltip_editDistrict": "編輯地區名稱(按「儲存更改」才寫入)", | "tooltip_editDistrict": "編輯地區名稱(按「儲存更改」才寫入)", | ||||
| "aria_editDistrict": "編輯地區", | "aria_editDistrict": "編輯地區", | ||||
| "tooltip_removeEmptyDistrict": "移除此暫存區塊(未儲存前可刪)", | "tooltip_removeEmptyDistrict": "移除此暫存區塊(未儲存前可刪)", | ||||