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