// 【重要】必须使用 const Component 作为组件变量名
// 台账管理 - 车辆维修/保养明细（运维批量导入/手填；待保存→保存→确认提交归档；主管仅可改已提交记录）
// 原型：运维人员仅操作本人记录，他人记录只读；联调后对接登录用户与角色权限

import { DateRangeFilterField } from '../vehicle-management/components/DateRangeFilterField';
import { TablePagination, DEFAULT_PAGE_SIZE } from '../../common/TablePagination';
import { OperationActions } from '../../common/OperationActions';

var EMPTY_DATE_RANGE = { startDate: '', endDate: '' };

const Component = function () {
	var useState = React.useState;
	var useMemo = React.useMemo;
	var useCallback = React.useCallback;
	var useRef = React.useRef;

	var antd = window.antd;
	var App = antd.App;
	var Button = antd.Button;
	var Table = antd.Table;
	var Select = antd.Select;
	var DatePicker = antd.DatePicker;
	var Input = antd.Input;
	var InputNumber = antd.InputNumber;
	var Popconfirm = antd.Popconfirm;
	var Popover = antd.Popover;
	var Tooltip = antd.Tooltip;
	var Dropdown = antd.Dropdown;
	var Modal = antd.Modal;
	var Upload = antd.Upload;
	var Alert = antd.Alert;
	var message = antd.message;

	var ATTACHMENT_ACCEPT =
		'image/*,.jpg,.jpeg,.png,.gif,.webp,.bmp,.heic,.doc,.docx,.xls,.xlsx,.xlsm,.csv,.pdf,' +
		'application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,' +
		'application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,' +
		'application/vnd.ms-excel.sheet.macroEnabled.12,text/csv';

	var ATTACHMENT_EXT_KIND = {
		jpg: 'image',
		jpeg: 'image',
		png: 'image',
		gif: 'image',
		webp: 'image',
		bmp: 'image',
		heic: 'image',
		doc: 'word',
		docx: 'word',
		xls: 'excel',
		xlsx: 'excel',
		xlsm: 'excel',
		csv: 'excel',
		pdf: 'pdf'
	};

	var ATTACHMENT_KIND_LABEL = {
		image: '图片',
		word: 'Word',
		excel: 'Excel',
		pdf: 'PDF',
		other: '文件'
	};

	function getFileExt(name) {
		var parts = String(name == null ? '' : name).toLowerCase().split('.');
		return parts.length > 1 ? parts[parts.length - 1] : '';
	}

	function getAttachmentKind(name, mime) {
		var ext = getFileExt(name);
		if (ATTACHMENT_EXT_KIND[ext]) return ATTACHMENT_EXT_KIND[ext];
		var m = String(mime == null ? '' : mime).toLowerCase();
		if (m.indexOf('image/') === 0) return 'image';
		if (m.indexOf('pdf') >= 0) return 'pdf';
		if (m.indexOf('word') >= 0 || m.indexOf('document') >= 0) return 'word';
		if (m.indexOf('sheet') >= 0 || m.indexOf('excel') >= 0 || m === 'text/csv') return 'excel';
		return 'other';
	}

	function isAttachmentAllowed(file) {
		if (!file) return false;
		return getAttachmentKind(file.name, file.type) !== 'other';
	}

	function formatFileSize(bytes) {
		var n = Number(bytes) || 0;
		if (n < 1024) return n + ' B';
		if (n < 1024 * 1024) return (n / 1024).toFixed(1) + ' KB';
		return (n / (1024 * 1024)).toFixed(1) + ' MB';
	}

	function buildAttachmentFromFile(file) {
		var url = null;
		try {
			url = URL.createObjectURL(file);
		} catch (eUrl) {}
		return {
			id: 'att-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8),
			name: file.name,
			size: file.size || 0,
			type: file.type || '',
			kind: getAttachmentKind(file.name, file.type),
			url: url,
			uploadedAt: new Date().toISOString()
		};
	}

	function formatAttachmentListSummary(list) {
		if (!list || !list.length) return '—';
		if (list.length === 1) return list[0].name;
		return list.length + ' 个附件';
	}

	function attachmentNamesSignature(list) {
		return (list || [])
			.map(function (a) { return a && a.name; })
			.filter(Boolean)
			.join('|');
	}

	var CURRENT_USER = { id: 'u_zhang', name: '张运维', role: 'staff' };

	var SUBMIT_CONFIRM_TEXT = '提交后数据将无法修改，如需修改请联系运维主管进行修改';

	var RECORD_TYPE_OPTIONS = [
		{ value: '维修', label: '维修' },
		{ value: '保养', label: '保养' }
	];

	var IMPORT_RECORD_TYPE_ENUM = RECORD_TYPE_OPTIONS.map(function (o) {
		return o.value;
	});

	var BEAR_BY_OPTIONS = [
		{ value: '客户承担', label: '客户承担' },
		{ value: '我司承担', label: '我司承担' }
	];

	var IMPORT_TEMPLATE_HEADERS = [
		'进修日期',
		'车牌号',
		'类型',
		'项目/材料',
		'人工费单位',
		'人工费数量',
		'人工费单价(含税)',
		'配件费单位',
		'配件费数量',
		'配件费单价(含税)',
		'承担方式'
	];

	var IMPORT_HEADER_MAP = {
		'进修日期': 'repairDate',
		'车牌号': 'plateNo',
		'类型': 'recordType',
		'项目/材料': 'itemDesc',
		'人工费单位': 'laborUnit',
		'人工费数量': 'laborQty',
		'人工费单价(含税)': 'laborUnitPrice',
		'配件费单位': 'partsUnit',
		'配件费数量': 'partsQty',
		'配件费单价(含税)': 'partsUnitPrice',
		'承担方式': 'bearBy'
	};

	var IMPORT_TEMPLATE_SAMPLE = [
		['2025-06-01', '沪A12345', '维修', '更换空压机滤芯', '工时', '2', '150', '件', '1', '320', '客户承担']
	];

	function buildImportTemplateAoa() {
		var typeColIdx = IMPORT_TEMPLATE_HEADERS.indexOf('类型');
		var enumRows = IMPORT_RECORD_TYPE_ENUM.map(function (label, idx) {
			var row = [];
			var ci;
			for (ci = 0; ci < IMPORT_TEMPLATE_HEADERS.length; ci++) {
				row.push(ci === typeColIdx ? String(idx + 1) + '.' + label : '');
			}
			return row;
		});
		return [IMPORT_TEMPLATE_HEADERS].concat(enumRows).concat(IMPORT_TEMPLATE_SAMPLE);
	}

	function isImportTemplateMetaRow(cells) {
		var nonEmpty = 0;
		var typeEnumLike = false;
		var ci;
		for (ci = 0; ci < (cells || []).length; ci++) {
			var v = String(cells[ci] == null ? '' : cells[ci]).trim();
			if (!v) continue;
			nonEmpty++;
			if (/^[12][\.、．]?\s*(维修|保养)$/.test(v)) typeEnumLike = true;
		}
		if (!nonEmpty) return true;
		return typeEnumLike && nonEmpty <= 2;
	}

	function importParsedRowToLine(row) {
		return [
			row.repairDate,
			row.plateNo,
			row.recordType,
			row.itemDesc,
			row.laborUnit,
			row.laborQty,
			row.laborUnitPrice,
			row.partsUnit,
			row.partsQty,
			row.partsUnitPrice,
			row.bearBy
		].map(function (v) {
			return v == null ? '' : v;
		});
	}

	var EXPORT_HEADERS = IMPORT_TEMPLATE_HEADERS.concat([
		'人工费金额(含税)',
		'配件费金额(含税)',
		'费用总计（含税）',
		'提报人',
		'提交状态',
		'附件'
	]);

	var CHANGE_LOG_FIELD_LABELS = {
		repairDate: '进修日期',
		plateNo: '车牌号',
		recordType: '类型',
		itemDesc: '项目/材料',
		laborUnit: '人工费单位',
		laborQty: '人工费数量',
		laborUnitPrice: '人工费单价(含税)',
		partsUnit: '配件费单位',
		partsQty: '配件费数量',
		partsUnitPrice: '配件费单价(含税)',
		bearBy: '承担方式',
		attachments: '附件',
		submitStatus: '提交状态',
		saveStatus: '保存状态',
		_copy: '复制新增',
		_create: '新增记录'
	};

	/**
	 * 系统全部车辆车牌（原型枚举，与车辆管理模块一致；联调后由车辆清单接口加载）
	 */
	var SYSTEM_VEHICLE_PLATES = [
		'京C12345',
		'京CN88771F',
		'京E88888',
		'京F33333',
		'京G12345',
		'京H88888',
		'川AL55602F',
		'沪A12345',
		'沪AD12345F',
		'沪B99999',
		'沪C11111',
		'沪D66666',
		'浙AD12345F',
		'浙AH55660F',
		'浙BK33210F',
		'苏EF99887F',
		'粤A11B22',
		'粤A12345',
		'粤A66D66',
		'粤A77F77',
		'粤A88K88',
		'粤A99A99',
		'粤B12345',
		'粤B22C33',
		'粤B44E44',
		'粤B55B55',
		'粤B67890',
		'粤BK33210F'
	];

	function normPlateKey(plate) {
		return String(plate == null ? '' : plate)
			.replace(/[\s\-·]/g, '')
			.toUpperCase();
	}

	function isPlateInVehicleTable(plate) {
		if (!plate) return false;
		var key = normPlateKey(plate);
		for (var i = 0; i < SYSTEM_VEHICLE_PLATES.length; i++) {
			if (normPlateKey(SYSTEM_VEHICLE_PLATES[i]) === key) return true;
		}
		return false;
	}

	function isEmptyStr(v) {
		return !String(v == null ? '' : v).trim();
	}

	function isEmptyNum(v) {
		return v === null || v === undefined || v === '';
	}

	function getRowInvalidMap(row) {
		var inv = {};
		if (!row.repairDate) inv.repairDate = true;
		if (!row.plateNo) inv.plateNo = 'empty';
		else if (!isPlateInVehicleTable(row.plateNo)) inv.plateNo = 'not_exist';
		if (isEmptyStr(row.recordType)) inv.recordType = true;
		if (isEmptyStr(row.itemDesc)) inv.itemDesc = true;
		if (isEmptyStr(row.laborUnit)) inv.laborUnit = true;
		if (isEmptyNum(row.laborQty)) inv.laborQty = true;
		if (isEmptyNum(row.laborUnitPrice)) inv.laborUnitPrice = true;
		if (isEmptyStr(row.partsUnit)) inv.partsUnit = true;
		if (isEmptyNum(row.partsQty)) inv.partsQty = true;
		if (isEmptyNum(row.partsUnitPrice)) inv.partsUnitPrice = true;
		if (isEmptyStr(row.bearBy)) inv.bearBy = true;
		return inv;
	}

	/** 列表展示顺序：0 已确认提交 → 1 已保存 → 2 新增未保存 */
	function rowDisplaySortTier(row) {
		if (row && row.submitStatus === 'submitted') return 0;
		if (row && row.saveStatus === 'saved') return 1;
		return 2;
	}

	function rowStatusLabel(row) {
		if (row && row.submitStatus === 'submitted') return '已提交';
		if (row && row.saveStatus === 'saved') return '已保存';
		return '待保存';
	}

	function isSavedDraftRow(row) {
		return row && row.submitStatus !== 'submitted' && row.saveStatus === 'saved';
	}

	/** 状态为「已保存」：仅本人草稿在操作列展示「编辑」「删除」（主管不处理未提交草稿） */
	function canShowSavedActions(row, isSupervisorRole) {
		if (!isSavedDraftRow(row)) return false;
		if (isSupervisorRole) return false;
		return row.createdBy === CURRENT_USER.id;
	}

	/** 状态为「已提交」：仅运维主管在操作列展示「编辑」「删除」 */
	function canShowSubmittedActions(row, isSupervisorRole) {
		return !!isSupervisorRole && row && row.submitStatus === 'submitted';
	}

	function canDeleteRow(row, isSupervisorRole) {
		if (row && row.submitStatus === 'submitted') {
			return !!isSupervisorRole;
		}
		return row && row.createdBy === CURRENT_USER.id;
	}

	/** 已提交不可复制；仅本人未提交记录可复制 */
	function canCopyRow(row, isSupervisorRole) {
		if (!row || row.submitStatus === 'submitted' || isSupervisorRole) return false;
		return row.createdBy === CURRENT_USER.id;
	}

	/** 本人未提交记录可随时补传附件；已提交仅主管进入编辑后可管理 */
	function canManageAttachments(row, isSupervisorRole, editingKeysArr) {
		if (!row) return false;
		if (row.submitStatus === 'submitted') {
			if (!isSupervisorRole) return false;
			return editingKeysArr.indexOf(row.key) >= 0;
		}
		if (isSupervisorRole) return false;
		return row.createdBy === CURRENT_USER.id;
	}

	function cloneRowTimeValue(t) {
		if (!t) return t;
		try {
			if (window.dayjs && window.dayjs.isDayjs && window.dayjs.isDayjs(t)) return t.clone();
			if (window.dayjs) {
				var d = window.dayjs(t);
				return d.isValid() ? d : t;
			}
		} catch (eClone) {}
		return t;
	}

	function buildCopiedRow(sourceRow) {
		var now = nowDayjs();
		return recalcRow({
			key: nextRowKey(),
			createdBy: CURRENT_USER.id,
			reporterName: CURRENT_USER.name,
			repairDate: cloneRowTimeValue(sourceRow.repairDate),
			plateNo: sourceRow.plateNo,
			reportTime: now,
			recordType: sourceRow.recordType,
			bearBy: sourceRow.bearBy,
			itemDesc: sourceRow.itemDesc || '',
			laborUnit: sourceRow.laborUnit || '',
			laborQty: sourceRow.laborQty,
			laborUnitPrice: sourceRow.laborUnitPrice,
			laborAmount: 0,
			partsUnit: sourceRow.partsUnit || '',
			partsQty: sourceRow.partsQty,
			partsUnitPrice: sourceRow.partsUnitPrice,
			partsAmount: 0,
			totalCost: 0,
			attachments: [],
			submitStatus: 'draft',
			saveStatus: 'unsaved',
			submittedAt: null
		});
	}

	function valuesEqualForLog(field, a, b) {
		if (a === b) return true;
		if ((a == null || a === '') && (b == null || b === '')) return true;
		if (field === 'repairDate' && window.dayjs) {
			try {
				return window.dayjs(a).valueOf() === window.dayjs(b).valueOf();
			} catch (eLog) {
				return false;
			}
		}
		if (
			field === 'laborQty' ||
			field === 'laborUnitPrice' ||
			field === 'partsQty' ||
			field === 'partsUnitPrice'
		) {
			if (isEmptyNum(a) && isEmptyNum(b)) return true;
			if (field === 'laborQty' || field === 'partsQty') return toIntQty(a) === toIntQty(b);
			return roundMoney(a) === roundMoney(b);
		}
		if (field === 'attachments') {
			return attachmentNamesSignature(a) === attachmentNamesSignature(b);
		}
		return String(a) === String(b);
	}

	function formatLogValue(field, value, rowCtx) {
		if (value == null || value === '') return '—';
		if (field === 'repairDate') return formatDate(value) || formatDateTime(value) || String(value);
		if (field === 'submitStatus') {
			if (value === 'submitted') return '已提交';
			return '未提交';
		}
		if (field === 'saveStatus') {
			if (value === 'saved') return '已保存';
			return '待保存';
		}
		if (
			field === 'laborQty' ||
			field === 'partsQty'
		) {
			return isEmptyNum(value) ? '—' : String(toIntQty(value));
		}
		if (field === 'laborUnitPrice' || field === 'partsUnitPrice') {
			return isEmptyNum(value) ? '—' : fmtMoney(value);
		}
		if (field === 'attachments') {
			return formatAttachmentListSummary(value);
		}
		return String(value);
	}

	function rowTimeValue(row, field) {
		var v = row && row[field];
		if (!v || !window.dayjs) return 0;
		try {
			return window.dayjs(v).valueOf();
		} catch (e1) {
			return 0;
		}
	}

	function sortRowsWithinTier(rows) {
		return rows.slice().sort(function (a, b) {
			var ta = rowTimeValue(a, 'submittedAt') || rowTimeValue(a, 'reportTime');
			var tb = rowTimeValue(b, 'submittedAt') || rowTimeValue(b, 'reportTime');
			if (tb !== ta) return tb - ta;
			return String(a.key || '').localeCompare(String(b.key || ''));
		});
	}

	/** 分区合并，避免同层数据按日期与另一层穿插 */
	function sortRowsByDisplayTier(rows) {
		var submitted = [];
		var saved = [];
		var unsaved = [];
		(rows || []).forEach(function (r) {
			var tier = rowDisplaySortTier(r);
			if (tier === 0) submitted.push(r);
			else if (tier === 1) saved.push(r);
			else unsaved.push(r);
		});
		return sortRowsWithinTier(submitted).concat(sortRowsWithinTier(saved)).concat(sortRowsWithinTier(unsaved));
	}

	function firstInvalidMessage(row, inv, seq) {
		var prefix = '第' + seq + '行：';
		if (inv.repairDate) return prefix + '请填写进修日期';
		if (inv.plateNo === 'empty') return prefix + '请填写车牌号';
		if (inv.plateNo === 'not_exist') return prefix + '车辆不存在';
		if (inv.recordType) return prefix + '请选择类型';
		if (inv.itemDesc) return prefix + '请填写项目/材料';
		if (inv.laborUnit) return prefix + '请填写人工费单位';
		if (inv.laborQty) return prefix + '请填写人工费数量';
		if (inv.laborUnitPrice) return prefix + '请填写人工费单价（含税）';
		if (inv.partsUnit) return prefix + '请填写配件费单位';
		if (inv.partsQty) return prefix + '请填写配件费数量';
		if (inv.partsUnitPrice) return prefix + '请填写配件费单价（含税）';
		return prefix + '请完善必填项';
	}

	var OTHER_REPORTERS = [
		{ id: 'u_zhou', name: '周敏' },
		{ id: 'u_li_h', name: '李航' },
		{ id: 'u_wang', name: '王磊' }
	];

	var rowIdSeed = 0;
	function nextRowKey() {
		rowIdSeed += 1;
		return 'mr-' + Date.now() + '-' + rowIdSeed;
	}

	function filterOption(input, option) {
		var label = (option && (option.label || option.children)) || '';
		return String(label).toLowerCase().indexOf(String(input || '').toLowerCase()) >= 0;
	}

	/** 车牌号下拉：支持输入模糊匹配（忽略空格、大小写） */
	function filterPlateOption(input, option) {
		var q = normPlateKey(input);
		if (!q) return true;
		var label = normPlateKey(option && (option.label != null ? option.label : option.value));
		return label.indexOf(q) >= 0;
	}

	function numOrZero(v) {
		if (v === null || v === undefined || v === '') return 0;
		var n = Number(v);
		return isNaN(n) ? 0 : n;
	}

	function roundMoney(n) {
		return Math.round(numOrZero(n) * 100) / 100;
	}

	function toIntQty(v) {
		if (v === null || v === undefined || v === '') return 0;
		var n = Math.floor(Number(v));
		return isNaN(n) || n < 0 ? 0 : n;
	}

	function fmtMoney(n) {
		if (n === null || n === undefined || n === '') return '0.00';
		return roundMoney(n).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
	}

	function nowDayjs() {
		try {
			if (window.dayjs) return window.dayjs();
		} catch (e1) {}
		return null;
	}

	function formatDate(d) {
		if (!d || !window.dayjs) return '';
		try {
			return window.dayjs(d).format('YYYY-MM-DD');
		} catch (e2) {
			return '';
		}
	}

	function formatDateTime(d) {
		if (!d || !window.dayjs) return '';
		try {
			return window.dayjs(d).format('YYYY-MM-DD HH:mm');
		} catch (e3) {
			return '';
		}
	}

	function downloadWorkbookFile(filename, sheetName, aoa) {
		var XLSX = window.XLSX;
		if (!XLSX) {
			message.error('导出组件未加载，请刷新页面后重试');
			return false;
		}
		var ws = XLSX.utils.aoa_to_sheet(aoa);
		var wb = XLSX.utils.book_new();
		XLSX.utils.book_append_sheet(wb, ws, sheetName || '数据');
		XLSX.writeFile(wb, filename);
		return true;
	}

	function parseCsvLine(line) {
		var out = [];
		var cur = '';
		var inQuote = false;
		var i;
		for (i = 0; i < line.length; i++) {
			var ch = line.charAt(i);
			if (inQuote) {
				if (ch === '"') {
					if (line.charAt(i + 1) === '"') {
						cur += '"';
						i++;
					} else {
						inQuote = false;
					}
				} else {
					cur += ch;
				}
			} else if (ch === '"') {
				inQuote = true;
			} else if (ch === ',') {
				out.push(cur.trim());
				cur = '';
			} else {
				cur += ch;
			}
		}
		out.push(cur.trim());
		return out;
	}

	function csvTextToAoa(text) {
		var raw = String(text || '').replace(/^\uFEFF/, '');
		var lines = raw.split(/\r?\n/).filter(function (ln) { return ln.trim().length > 0; });
		return lines.map(parseCsvLine);
	}

	function normalizeHeaderLabel(h) {
		return String(h == null ? '' : h).trim().replace(/^\uFEFF/, '');
	}

	function buildImportFieldIndex(headers) {
		var fieldIdx = {};
		var hi;
		for (hi = 0; hi < headers.length; hi++) {
			var key = IMPORT_HEADER_MAP[normalizeHeaderLabel(headers[hi])];
			if (key && fieldIdx[key] == null) fieldIdx[key] = hi;
		}
		return fieldIdx;
	}

	function parseImportDate(val) {
		if (val == null || val === '') return null;
		if (window.dayjs) {
			if (typeof val === 'number' && window.XLSX && window.XLSX.SSF) {
				try {
					var parsed = window.XLSX.SSF.parse_date_code(val);
					if (parsed) {
						var excelDate = window.dayjs(new Date(parsed.y, parsed.m - 1, parsed.d));
						if (excelDate.isValid()) return excelDate;
					}
				} catch (eExcel) {}
			}
			var d = window.dayjs(val);
			if (d.isValid()) return d;
		}
		return null;
	}

	function parseImportNum(val) {
		if (val == null || val === '') return null;
		var n = Number(String(val).replace(/,/g, '').trim());
		return isNaN(n) ? null : n;
	}

	function parseImportSheetAoa(aoa) {
		if (!aoa || aoa.length < 2) {
			return { rows: [], headerError: '文件至少需要表头行与一行数据' };
		}
		var headers = (aoa[0] || []).map(normalizeHeaderLabel);
		var fieldIdx = buildImportFieldIndex(headers);
		if (fieldIdx.plateNo == null) {
			return { rows: [], headerError: '缺少必填列「车牌号」，请使用系统提供的导入模板' };
		}
		var rows = [];
		var li;
		for (li = 1; li < aoa.length; li++) {
			var cells = aoa[li] || [];
			var hasValue = false;
			var ci;
			for (ci = 0; ci < cells.length; ci++) {
				if (String(cells[ci] == null ? '' : cells[ci]).trim()) {
					hasValue = true;
					break;
				}
			}
			if (!hasValue) continue;
			if (isImportTemplateMetaRow(cells)) continue;
			var get = function (field) {
				var idx = fieldIdx[field];
				return idx == null || idx < 0 ? '' : String(cells[idx] == null ? '' : cells[idx]).trim();
			};
			rows.push({
				lineNo: li + 1,
				repairDate: get('repairDate'),
				plateNo: get('plateNo'),
				recordType: get('recordType'),
				itemDesc: get('itemDesc'),
				laborUnit: get('laborUnit'),
				laborQty: get('laborQty'),
				laborUnitPrice: get('laborUnitPrice'),
				partsUnit: get('partsUnit'),
				partsQty: get('partsQty'),
				partsUnitPrice: get('partsUnitPrice'),
				bearBy: get('bearBy')
			});
		}
		return { rows: rows, headerError: null };
	}

	function validateImportMaintenanceRow(row) {
		var errors = [];
		if (!parseImportDate(row.repairDate)) errors.push('进修日期无效');
		if (!row.plateNo) errors.push('车牌号不能为空');
		else if (!isPlateInVehicleTable(row.plateNo)) errors.push('车牌号不在车辆清单中');
		if (!row.recordType) errors.push('类型不能为空');
		else if (RECORD_TYPE_OPTIONS.every(function (o) { return o.value !== row.recordType; })) {
			errors.push('类型须为「维修」或「保养」');
		}
		if (!row.itemDesc) errors.push('项目/材料不能为空');
		if (!row.laborUnit) errors.push('人工费单位不能为空');
		if (row.laborQty === '' || row.laborQty == null) errors.push('人工费数量不能为空');
		else if (parseImportNum(row.laborQty) == null) errors.push('人工费数量须为数字');
		if (row.laborUnitPrice === '' || row.laborUnitPrice == null) errors.push('人工费单价不能为空');
		else if (parseImportNum(row.laborUnitPrice) == null) errors.push('人工费单价须为数字');
		if (!row.partsUnit) errors.push('配件费单位不能为空');
		if (row.partsQty === '' || row.partsQty == null) errors.push('配件费数量不能为空');
		else if (parseImportNum(row.partsQty) == null) errors.push('配件费数量须为整数');
		if (row.partsUnitPrice === '' || row.partsUnitPrice == null) errors.push('配件费单价不能为空');
		else if (parseImportNum(row.partsUnitPrice) == null) errors.push('配件费单价须为数字');
		if (!row.bearBy) errors.push('承担方式不能为空');
		else if (BEAR_BY_OPTIONS.every(function (o) { return o.value !== row.bearBy; })) {
			errors.push('承担方式须为「客户承担」或「我司承担」');
		}
		return errors;
	}

	function importRowToMaintenanceRow(row) {
		var now = nowDayjs();
		return recalcRow({
			key: nextRowKey(),
			createdBy: CURRENT_USER.id,
			reporterName: CURRENT_USER.name,
			repairDate: parseImportDate(row.repairDate),
			plateNo: row.plateNo,
			reportTime: now,
			recordType: row.recordType,
			bearBy: row.bearBy,
			itemDesc: row.itemDesc,
			laborUnit: row.laborUnit,
			laborQty: toIntQty(parseImportNum(row.laborQty)),
			laborUnitPrice: parseImportNum(row.laborUnitPrice),
			laborAmount: 0,
			partsUnit: row.partsUnit,
			partsQty: toIntQty(parseImportNum(row.partsQty)),
			partsUnitPrice: parseImportNum(row.partsUnitPrice),
			partsAmount: 0,
			totalCost: 0,
			attachments: [],
			submitStatus: 'draft',
			saveStatus: 'unsaved',
			submittedAt: null
		});
	}

	function rowToExportLine(row) {
		var attachNames = (row.attachments || [])
			.map(function (a) { return a && a.name; })
			.filter(Boolean)
			.join('；');
		return [
			formatDate(row.repairDate),
			row.plateNo || '',
			row.recordType || '',
			row.itemDesc || '',
			row.laborUnit || '',
			row.laborQty == null ? '' : String(toIntQty(row.laborQty)),
			row.laborUnitPrice == null ? '' : roundMoney(row.laborUnitPrice),
			row.partsUnit || '',
			row.partsQty == null ? '' : String(toIntQty(row.partsQty)),
			row.partsUnitPrice == null ? '' : roundMoney(row.partsUnitPrice),
			row.bearBy || '',
			roundMoney(row.laborAmount),
			roundMoney(row.partsAmount),
			roundMoney(row.totalCost),
			row.reporterName || '',
			row.submitStatus === 'submitted' ? '已提交' : '未提交',
			attachNames
		];
	}

	function recalcRow(row) {
		var laborQty = toIntQty(row.laborQty);
		var partsQty = toIntQty(row.partsQty);
		var laborAmt = roundMoney(laborQty * numOrZero(row.laborUnitPrice));
		var partsAmt = roundMoney(partsQty * numOrZero(row.partsUnitPrice));
		var total = roundMoney(laborAmt + partsAmt);
		return Object.assign({}, row, {
			laborQty: laborQty,
			partsQty: partsQty,
			laborAmount: laborAmt,
			partsAmount: partsAmt,
			totalCost: total
		});
	}

	function buildEmptyRow(user) {
		var now = nowDayjs();
		return recalcRow({
			key: nextRowKey(),
			createdBy: user.id,
			reporterName: user.name,
			repairDate: now,
			plateNo: undefined,
			reportTime: now,
			recordType: undefined,
			bearBy: undefined,
			itemDesc: '',
			laborUnit: '',
			laborQty: null,
			laborUnitPrice: null,
			laborAmount: 0,
			partsUnit: '',
			partsQty: null,
			partsUnitPrice: null,
			partsAmount: 0,
			totalCost: 0,
			attachments: [],
			submitStatus: 'draft',
			saveStatus: 'unsaved'
		});
	}

	function buildMockAttachments(i) {
		if (i === 0) {
			return [
				{ id: 'mock-att-1', name: '维修工单.pdf', size: 102400, kind: 'pdf', type: 'application/pdf' },
				{ id: 'mock-att-2', name: '现场照片.jpg', size: 356000, kind: 'image', type: 'image/jpeg' }
			];
		}
		if (i === 1) {
			return [{ id: 'mock-att-3', name: '费用明细.xlsx', size: 45000, kind: 'excel', type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }];
		}
		return [];
	}

	function buildMockAllRows() {
		var plates = SYSTEM_VEHICLE_PLATES;
		var items = ['更换空压机滤芯', '制动片检修', '冷却液补充', '高压线束检查', '轮胎换位'];
		var rows = [];
		var reporters = [{ id: CURRENT_USER.id, name: CURRENT_USER.name }].concat(OTHER_REPORTERS);
		var i;
		for (i = 0; i < 8; i++) {
			var rep = reporters[i % reporters.length];
			var submitStatus = 'draft';
			var saveStatus = 'unsaved';
			if (rep.id === CURRENT_USER.id) {
				if (i < 3) {
					submitStatus = 'submitted';
					saveStatus = 'saved';
				} else if (i === 3) {
					saveStatus = 'saved';
				}
			} else {
				saveStatus = 'saved';
			}
			var laborQty = 1 + (i % 4);
			var laborPrice = 120 + i * 15;
			var partsQty = i % 2 === 0 ? 2 : 0;
			var partsPrice = 80 + i * 20;
			var d = nowDayjs();
			var repairDay = d && d.subtract ? d.subtract(i, 'day') : d;
			rows.push(
				recalcRow({
					key: 'mock-' + (i + 1),
					createdBy: rep.id,
					reporterName: rep.name,
					repairDate: repairDay,
					plateNo: plates[i % plates.length],
					reportTime: repairDay,
					recordType: i % 3 === 0 ? '保养' : '维修',
					bearBy: i % 2 === 0 ? '客户承担' : '我司承担',
					itemDesc: items[i % items.length],
					laborUnit: '工时',
					laborQty: laborQty,
					laborUnitPrice: laborPrice,
					laborAmount: 0,
					partsUnit: '件',
					partsQty: partsQty,
					partsUnitPrice: partsPrice,
					partsAmount: 0,
					totalCost: 0,
					attachments: buildMockAttachments(i),
					submitStatus: submitStatus,
					saveStatus: saveStatus,
					submittedAt: submitStatus === 'submitted' ? repairDay : null
				})
			);
		}
		return rows;
	}

	var rowsState = useState(buildMockAllRows);
	var allRows = rowsState[0];
	var setAllRows = rowsState[1];

	var vehiclePlateOptions = useMemo(function () {
		return SYSTEM_VEHICLE_PLATES.map(function (p) {
			return { value: p, label: p };
		});
	}, []);

	var plateDraftState = useState(undefined);
	var plateDraft = plateDraftState[0];
	var setPlateDraft = plateDraftState[1];

	/** 提报人多选：空数组表示全部 */
	var reporterDraftState = useState([]);
	var reporterDraft = reporterDraftState[0];
	var setReporterDraft = reporterDraftState[1];

	var dateRangeDraftState = useState(function () {
		return { startDate: '', endDate: '' };
	});
	var dateRangeDraft = dateRangeDraftState[0];
	var setDateRangeDraft = dateRangeDraftState[1];

	/** 类型多选：空数组表示全部（保养、维修） */
	var typeDraftState = useState([]);
	var typeDraft = typeDraftState[0];
	var setTypeDraft = typeDraftState[1];

	var plateAppliedState = useState(undefined);
	var plateApplied = plateAppliedState[0];
	var setPlateApplied = plateAppliedState[1];

	var reporterAppliedState = useState([]);
	var reporterApplied = reporterAppliedState[0];
	var setReporterApplied = reporterAppliedState[1];

	var dateRangeAppliedState = useState(function () {
		return { startDate: '', endDate: '' };
	});
	var dateRangeApplied = dateRangeAppliedState[0];
	var setDateRangeApplied = dateRangeAppliedState[1];

	var typeAppliedState = useState([]);
	var typeApplied = typeAppliedState[0];
	var setTypeApplied = typeAppliedState[1];

	var pageState = useState(1);
	var page = pageState[0];
	var setPage = pageState[1];

	var pageSizeState = useState(DEFAULT_PAGE_SIZE);
	var pageSize = pageSizeState[0];
	var setPageSize = pageSizeState[1];

	/** 联调后由账号权限判断是否运维主管 */
	/** 原型默认运维人员视角；预览主管权限可在 URL 加 ?role=supervisor */
	var isSupervisor =
		typeof window !== 'undefined' &&
		new URLSearchParams(window.location.search).get('role') === 'supervisor';
	var editingKeysState = useState([]);
	var editingKeys = editingKeysState[0];
	var setEditingKeys = editingKeysState[1];

	var rowInvalidState = useState({});
	var rowInvalid = rowInvalidState[0];
	var setRowInvalid = rowInvalidState[1];

	var changeLogsByKeyState = useState({});
	var changeLogsByKey = changeLogsByKeyState[0];
	var setChangeLogsByKey = changeLogsByKeyState[1];

	var changeLogModalState = useState({ open: false, rowKey: null, rowLabel: '' });
	var changeLogModal = changeLogModalState[0];
	var setChangeLogModal = changeLogModalState[1];

	var attachmentModalState = useState({ open: false, rowKey: null, readOnly: false });
	var attachmentModal = attachmentModalState[0];
	var setAttachmentModal = attachmentModalState[1];

	var importModalOpenState = useState(false);
	var importModalOpen = importModalOpenState[0];
	var setImportModalOpen = importModalOpenState[1];
	var importFileListState = useState([]);
	var importFileList = importFileListState[0];
	var setImportFileList = importFileListState[1];
	var importPreviewState = useState(null);
	var importPreview = importPreviewState[0];
	var setImportPreview = importPreviewState[1];

	var changeLogsSeededRef = useRef(false);

	var appendChangeLog = useCallback(function (rowKey, field, before, after, rowCtx, meta) {
		if (!rowKey) return;
		meta = meta || {};
		var fieldLabel = meta.fieldLabel || CHANGE_LOG_FIELD_LABELS[field] || field;
		var beforeText = meta.beforeText != null ? meta.beforeText : formatLogValue(field, before, rowCtx);
		var afterText = meta.afterText != null ? meta.afterText : formatLogValue(field, after, rowCtx);
		if (!meta.force && beforeText === afterText) return;
		setChangeLogsByKey(function (prev) {
			var list = (prev[rowKey] || []).slice();
			list.unshift({
				id: 'clog-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8),
				at: nowDayjs(),
				userId: CURRENT_USER.id,
				userName: CURRENT_USER.name,
				field: field,
				fieldLabel: fieldLabel,
				before: beforeText,
				after: afterText
			});
			var next = Object.assign({}, prev);
			next[rowKey] = list;
			return next;
		});
	}, []);

	var openChangeLogModal = useCallback(function (record) {
		if (!record) return;
		var label = (record.plateNo || '') + (record.repairDate ? ' · ' + formatDate(record.repairDate) : '');
		setChangeLogModal({
			open: true,
			rowKey: record.key,
			rowLabel: label.trim()
		});
	}, []);

	var closeChangeLogModal = useCallback(function () {
		setChangeLogModal({ open: false, rowKey: null, rowLabel: '' });
	}, []);

	var closeAttachmentModal = useCallback(function () {
		setAttachmentModal({ open: false, rowKey: null, readOnly: false });
	}, []);

	var updateRowAttachments = useCallback(
		function (rowKey, nextList) {
			if (!rowKey) return;
			setAllRows(function (prev) {
				return prev.map(function (r) {
					if (r.key !== rowKey) return r;
					var before = r.attachments || [];
					if (attachmentNamesSignature(before) !== attachmentNamesSignature(nextList)) {
						appendChangeLog(rowKey, 'attachments', before, nextList, r, {
							beforeText: formatAttachmentListSummary(before),
							afterText: formatAttachmentListSummary(nextList)
						});
					}
					return Object.assign({}, r, { attachments: nextList });
				});
			});
		},
		[appendChangeLog]
	);

	var copyPopoverKeyState = useState(null);
	var copyPopoverKey = copyPopoverKeyState[0];
	var setCopyPopoverKey = copyPopoverKeyState[1];
	var copyRowCountState = useState(1);
	var copyRowCount = copyRowCountState[0];
	var setCopyRowCount = copyRowCountState[1];

	var applyCopyRows = useCallback(function (sourceKey, count) {
		var n = Math.floor(Number(count));
		if (!isFinite(n) || n < 1) {
			message.warning('请输入大于0的复制行数');
			return;
		}
		if (n > 100) {
			message.warning('单次最多复制100行');
			return;
		}
		var didCopy = false;
		setAllRows(function (prev) {
			var source = null;
			var i;
			for (i = 0; i < prev.length; i++) {
				if (prev[i].key === sourceKey) {
					source = prev[i];
					break;
				}
			}
			if (!source || !canCopyRow(source, isSupervisor)) return prev;
			var copies = [];
			for (i = 0; i < n; i++) {
				copies.push(buildCopiedRow(source));
			}
			var ci;
			var sourceLabel = (source.plateNo || '当前行') + (source.itemDesc ? ' · ' + source.itemDesc : '');
			for (ci = 0; ci < copies.length; ci++) {
				appendChangeLog(copies[ci].key, '_copy', null, null, source, {
					fieldLabel: '复制新增',
					beforeText: '—',
					afterText: '复制自「' + sourceLabel + '」',
					force: true
				});
			}
			didCopy = true;
			return prev.concat(copies);
		});
		setCopyPopoverKey(null);
		if (didCopy) message.success('已复制 ' + n + ' 行');
		else message.warning('无法复制该条记录');
	}, [appendChangeLog, isSupervisor]);

	var latestRefs = useRef({});
	latestRefs.current = {
		setAllRows: setAllRows,
		setEditingKeys: setEditingKeys,
		setRowInvalid: setRowInvalid,
		editingKeys: editingKeys,
		rowInvalid: rowInvalid,
		isSupervisor: isSupervisor,
		vehicleOptions: vehiclePlateOptions,
		appendChangeLog: appendChangeLog
	};

	React.useEffect(function () {
		if (changeLogsSeededRef.current) return;
		changeLogsSeededRef.current = true;
		var demoAt = nowDayjs();
		var demoAt2 = demoAt && demoAt.subtract ? demoAt.subtract(2, 'hour') : demoAt;
		setChangeLogsByKey({
			'mock-1': [
				{
					id: 'clog-demo-2',
					at: demoAt,
					userId: CURRENT_USER.id,
					userName: CURRENT_USER.name,
					field: 'itemDesc',
					fieldLabel: '项目/材料',
					before: '更换空压机滤芯',
					after: '制动片检修'
				},
				{
					id: 'clog-demo-1',
					at: demoAt2,
					userId: CURRENT_USER.id,
					userName: CURRENT_USER.name,
					field: 'recordType',
					fieldLabel: '类型',
					before: '维修',
					after: '保养'
				}
			]
		});
	}, []);

	var isRowSubmitted = useCallback(function (row) {
		return row && row.submitStatus === 'submitted';
	}, []);

	var isRowSaved = useCallback(function (row) {
		return row && row.saveStatus === 'saved';
	}, []);

	/** 是否可编辑单元格：本人未保存直接编辑；已保存需点「编辑」；已提交仅运维主管点「编辑」后可改；他人记录只读 */
	var canEditRow = useCallback(
		function (row) {
			if (!row) return false;
			if (isRowSubmitted(row)) {
				if (!isSupervisor) return false;
				return editingKeys.indexOf(row.key) >= 0;
			}
			if (row.createdBy !== CURRENT_USER.id) return false;
			if (isSavedDraftRow(row)) {
				return editingKeys.indexOf(row.key) >= 0;
			}
			return true;
		},
		[isSupervisor, editingKeys, isRowSubmitted]
	);

	var canManageAttachmentsForRow = useCallback(
		function (row) {
			return canManageAttachments(row, isSupervisor, editingKeys);
		},
		[isSupervisor, editingKeys]
	);

	var visibleRows = useMemo(function () {
		var list = allRows.slice();
		if (plateApplied) {
			list = list.filter(function (r) { return r.plateNo === plateApplied; });
		}
		if (reporterApplied && reporterApplied.length > 0) {
			list = list.filter(function (r) { return reporterApplied.indexOf(r.reporterName) >= 0; });
		}
		if (typeApplied && typeApplied.length > 0) {
			list = list.filter(function (r) { return typeApplied.indexOf(r.recordType) >= 0; });
		}
		if (dateRangeApplied.startDate || dateRangeApplied.endDate) {
			list = list.filter(function (r) {
				var d = formatDate(r.repairDate);
				if (!d) return false;
				if (dateRangeApplied.startDate && d < dateRangeApplied.startDate) return false;
				if (dateRangeApplied.endDate && d > dateRangeApplied.endDate) return false;
				return true;
			});
		}
		list = sortRowsByDisplayTier(list);
		return list.map(function (r, idx) {
			return Object.assign({}, r, { seq: idx + 1, displayTier: rowDisplaySortTier(r) });
		});
	}, [allRows, plateApplied, reporterApplied, typeApplied, dateRangeApplied]);

	var listTotalPages = Math.max(1, Math.ceil(visibleRows.length / pageSize));
	var listSafePage = Math.min(page, listTotalPages);

	var pagedVisibleRows = useMemo(function () {
		var start = (listSafePage - 1) * pageSize;
		return visibleRows.slice(start, start + pageSize);
	}, [visibleRows, listSafePage, pageSize]);

	React.useEffect(function () {
		var maxPage = Math.max(1, Math.ceil(visibleRows.length / pageSize));
		if (page > maxPage) setPage(maxPage);
	}, [visibleRows.length, pageSize, page]);

	/** 合计行仅汇总三个人含税金额字段 */
	var totals = useMemo(function () {
		return visibleRows.reduce(
			function (acc, r) {
				acc.laborAmount += numOrZero(r.laborAmount);
				acc.partsAmount += numOrZero(r.partsAmount);
				acc.totalCost += numOrZero(r.totalCost);
				return acc;
			},
			{ laborAmount: 0, partsAmount: 0, totalCost: 0 }
		);
	}, [visibleRows]);

	var reporterOptions = useMemo(function () {
		var names = {};
		names[CURRENT_USER.name] = true;
		OTHER_REPORTERS.forEach(function (rep) {
			if (rep.name) names[rep.name] = true;
		});
		allRows.forEach(function (r) {
			if (r.reporterName) names[r.reporterName] = true;
		});
		return Object.keys(names).map(function (n) { return { value: n, label: n }; });
	}, [allRows]);

	var updateRow = useCallback(function (key, patch) {
		setAllRows(function (prev) {
			return prev.map(function (r) {
				if (r.key !== key) return r;
				return recalcRow(Object.assign({}, r, patch));
			});
		});
	}, []);

	var addRow = useCallback(function () {
		var row = buildEmptyRow(CURRENT_USER);
		appendChangeLog(row.key, '_create', null, null, row, {
			fieldLabel: '新增记录',
			beforeText: '—',
			afterText: '新增空白行',
			force: true
		});
		setAllRows(function (prev) {
			return prev.concat([row]);
		});
		message.success('已新增一行，请填写维修/保养信息');
	}, [appendChangeLog]);

	var handleQuery = useCallback(function () {
		setPlateApplied(plateDraft);
		setReporterApplied(reporterDraft);
		setTypeApplied(typeDraft);
		setDateRangeApplied(dateRangeDraft);
		setPage(1);
		message.success('查询成功');
	}, [plateDraft, reporterDraft, typeDraft, dateRangeDraft]);

	var handleReset = useCallback(function () {
		setPlateDraft(undefined);
		setReporterDraft([]);
		setTypeDraft([]);
		setDateRangeDraft({ startDate: '', endDate: '' });
		setPlateApplied(undefined);
		setReporterApplied([]);
		setTypeApplied([]);
		setDateRangeApplied(EMPTY_DATE_RANGE);
		setPage(1);
	}, []);

	function validateMaintainableRows(rows) {
		var allInvalid = {};
		var firstMsg = '';
		(rows || []).forEach(function (r, idx) {
			var inv = getRowInvalidMap(r);
			if (Object.keys(inv).length > 0) {
				allInvalid[r.key] = inv;
				if (!firstMsg) firstMsg = firstInvalidMessage(r, inv, r.seq || idx + 1);
			}
		});
		if (firstMsg) {
			setRowInvalid(allInvalid);
			message.warning(firstMsg);
			return false;
		}
		setRowInvalid({});
		return true;
	}

	var pendingSubmitRows = useMemo(function () {
		return allRows.filter(function (r) {
			return r.createdBy === CURRENT_USER.id && r.submitStatus !== 'submitted' && r.saveStatus === 'saved';
		});
	}, [allRows]);

	var handleSave = useCallback(function () {
		var toSave = allRows.filter(function (r) {
			return r.createdBy === CURRENT_USER.id && r.submitStatus !== 'submitted';
		});
		if (!toSave.length) {
			message.info('暂无待保存的草稿记录');
			return;
		}
		if (!validateMaintainableRows(toSave)) return;
		setAllRows(function (prev) {
			return prev.map(function (r) {
				if (r.createdBy === CURRENT_USER.id && r.submitStatus !== 'submitted') {
					return Object.assign({}, r, { saveStatus: 'saved' });
				}
				return r;
			});
		});
		setEditingKeys([]);
		message.success('保存成功（原型）');
	}, [allRows]);

	var handleConfirmSubmit = useCallback(function () {
		if (isSupervisor) {
			message.info('运维主管无需提交，请使用编辑修改已提交记录');
			return;
		}
		if (!pendingSubmitRows.length) {
			var hasUnsaved = allRows.some(function (r) {
				return r.createdBy === CURRENT_USER.id && r.submitStatus !== 'submitted' && r.saveStatus !== 'saved';
			});
			message.info(hasUnsaved ? '请先保存后再确认提交' : '暂无待提交的已保存记录');
			return;
		}
		if (!validateMaintainableRows(pendingSubmitRows)) return;
		Modal.confirm({
			title: '确认提交',
			content: SUBMIT_CONFIRM_TEXT,
			okText: '确认',
			cancelText: '取消',
			onOk: function () {
				setAllRows(function (prev) {
					return prev.map(function (r) {
						if (r.createdBy === CURRENT_USER.id && r.submitStatus !== 'submitted' && r.saveStatus === 'saved') {
							if (!valuesEqualForLog('submitStatus', r.submitStatus, 'submitted')) {
								appendChangeLog(r.key, 'submitStatus', r.submitStatus, 'submitted', r, { force: true });
							}
							return Object.assign({}, r, {
								submitStatus: 'submitted',
								saveStatus: 'saved',
								submittedAt: nowDayjs()
							});
						}
						return r;
					});
				});
				setEditingKeys([]);
				message.success('提交成功，数据已锁定');
			}
		});
	}, [isSupervisor, pendingSubmitRows, allRows, appendChangeLog]);

	var cellInputStyle = { width: '100%' };
	var cellNumStyle = { width: '100%' };

	var columns = useMemo(function () {
		function patch(key, field, val) {
			var ref = latestRefs.current;
			var patchObj = {};
			patchObj[field] = val;
			ref.setAllRows(function (prev) {
				return prev.map(function (r) {
					if (r.key !== key) return r;
					var next = recalcRow(Object.assign({}, r, patchObj));
					if (ref.appendChangeLog && !valuesEqualForLog(field, r[field], val)) {
						ref.appendChangeLog(key, field, r[field], val, next);
					}
					return next;
				});
			});
			ref.setRowInvalid(function (prev) {
				if (!prev || !prev[key] || !prev[key][field]) return prev;
				var next = Object.assign({}, prev);
				var rowInv = Object.assign({}, next[key]);
				delete rowInv[field];
				if (Object.keys(rowInv).length === 0) {
					delete next[key];
				} else {
					next[key] = rowInv;
				}
				return next;
			});
		}

		function readOnlyCell(v, align) {
			return React.createElement(
				'div',
				{
					className: 'ldb-cell-readonly',
					style: { textAlign: align || 'center' }
				},
				v === 0 || v === '0' ? v : v || '-'
			);
		}

		function moneyColumnProps() {
			return {
				className: 'ldb-col-money tabular-nums',
				onHeaderCell: function () {
					return { className: 'ldb-col-money' };
				},
				onCell: function () {
					return { className: 'ldb-col-money tabular-nums' };
				}
			};
		}

		function fixedLeftColumnProps() {
			return {
				onHeaderCell: function () {
					return { className: 'sticky-col-left' };
				},
				onCell: function () {
					return { className: 'sticky-col-left' };
				}
			};
		}

		function fixedRightColumnProps(extraClass) {
			return {
				onHeaderCell: function () {
					return { className: 'sticky-col-right' + (extraClass ? ' ' + extraClass : '') };
				},
				onCell: function () {
					return { className: 'sticky-col-right' + (extraClass ? ' ' + extraClass : '') };
				}
			};
		}

		function renderStatusTag(record) {
			var label = rowStatusLabel(record);
			var statusClass =
				label === '已提交'
					? 'ldb-status--archived'
					: label === '已保存'
						? 'ldb-status--partial'
						: 'ldb-status--draft';
			return React.createElement(
				'span',
				{ className: 'ldb-status-tag mldb-status-tag ' + statusClass },
				label
			);
		}

		function fieldInvalid(record, field) {
			var ref = latestRefs.current;
			var rowInv = (ref.rowInvalid && ref.rowInvalid[record.key]) || {};
			return rowInv[field];
		}

		function reqTitle(text) {
			return React.createElement(
				'span',
				null,
				React.createElement('span', { style: { color: '#ff4d4f', marginRight: 2 } }, '*'),
				text
			);
		}

		return [
			{
				title: '序号',
				dataIndex: 'seq',
				key: 'seq',
				width: 56,
				align: 'center',
				fixed: 'left',
				onHeaderCell: fixedLeftColumnProps().onHeaderCell,
				onCell: fixedLeftColumnProps().onCell
			},
			{
				title: '状态',
				dataIndex: 'displayTier',
				key: 'recordStatus',
				width: 88,
				align: 'center',
				fixed: 'left',
				onHeaderCell: fixedLeftColumnProps().onHeaderCell,
				onCell: function () {
					return { className: 'sticky-col-left mldb-col-status' };
				},
				render: function (_, record) {
					return renderStatusTag(record);
				}
			},
			{
				title: reqTitle('进修/保养日期'),
				dataIndex: 'repairDate',
				key: 'repairDate',
				width: 152,
				align: 'center',
				render: function (v, record) {
					if (!canEditRow(record)) return readOnlyCell(formatDate(v));
					return React.createElement(DatePicker, {
						className: 'maint-repair-date-picker',
						style: { width: '100%', minWidth: 132 },
						format: 'YYYY-MM-DD',
						placeholder: '请选择进修日期',
						allowClear: true,
						value: v,
						status: fieldInvalid(record, 'repairDate') ? 'error' : undefined,
						onChange: function (d) { patch(record.key, 'repairDate', d); }
					});
				}
			},
			{
				title: reqTitle('车牌号'),
				dataIndex: 'plateNo',
				key: 'plateNo',
				width: 140,
				align: 'center',
				render: function (v, record) {
					var ref = latestRefs.current;
					if (!canEditRow(record)) return readOnlyCell(v);
					var plateInv = fieldInvalid(record, 'plateNo');
					return React.createElement(Select, {
						style: cellInputStyle,
						showSearch: true,
						allowClear: true,
						placeholder: '请输入车牌号搜索',
						value: v,
						status: plateInv ? 'error' : undefined,
						options: ref.vehicleOptions,
						optionFilterProp: 'label',
						filterOption: filterPlateOption,
						notFoundContent: '未找到匹配车辆',
						onChange: function (val) { patch(record.key, 'plateNo', val); }
					});
				}
			},
			{
				title: reqTitle('类型'),
				dataIndex: 'recordType',
				key: 'recordType',
				width: 100,
				align: 'center',
				render: function (v, record) {
					if (!canEditRow(record)) return readOnlyCell(v);
					return React.createElement(Select, {
						style: cellInputStyle,
						allowClear: true,
						placeholder: '请选择类型',
						value: v,
						status: fieldInvalid(record, 'recordType') ? 'error' : undefined,
						options: RECORD_TYPE_OPTIONS,
						onChange: function (val) { patch(record.key, 'recordType', val); }
					});
				}
			},
			{
				title: reqTitle('项目/材料'),
				dataIndex: 'itemDesc',
				key: 'itemDesc',
				width: 160,
				align: 'center',
				render: function (v, record) {
					if (!canEditRow(record)) return readOnlyCell(v);
					return React.createElement(Input, {
						className: 'maint-inline-input',
						value: v,
						placeholder: '请输入项目/材料',
						status: fieldInvalid(record, 'itemDesc') ? 'error' : undefined,
						onChange: function (e) { patch(record.key, 'itemDesc', e.target.value); }
					});
				}
			},
			{
				title: '人工费',
				key: 'laborGroup',
				children: [
					{
						title: reqTitle('单位'),
						dataIndex: 'laborUnit',
						key: 'laborUnit',
						width: 72,
						align: 'center',
						render: function (v, record) {
							if (!canEditRow(record)) return readOnlyCell(v);
							return React.createElement(Input, {
								className: 'maint-inline-input',
								value: v,
								placeholder: '单位',
								status: fieldInvalid(record, 'laborUnit') ? 'error' : undefined,
								onChange: function (e) { patch(record.key, 'laborUnit', e.target.value); }
							});
						}
					},
					{
						title: reqTitle('数量'),
						dataIndex: 'laborQty',
						key: 'laborQty',
						width: 88,
						align: 'left',
						render: function (v, record) {
							if (!canEditRow(record)) return readOnlyCell(toIntQty(v), 'right');
							return React.createElement(InputNumber, {
								style: cellNumStyle,
								min: 0,
								step: 1,
								precision: 0,
								placeholder: '数量',
								value: isEmptyNum(v) ? null : toIntQty(v),
								status: fieldInvalid(record, 'laborQty') ? 'error' : undefined,
								onChange: function (val) { patch(record.key, 'laborQty', val == null ? null : toIntQty(val)); }
							});
						}
					},
					{
						title: reqTitle('单价(含税)'),
						dataIndex: 'laborUnitPrice',
						key: 'laborUnitPrice',
						width: 100,
						align: 'left',
						render: function (v, record) {
							if (!canEditRow(record)) return readOnlyCell(fmtMoney(v), 'right');
							return React.createElement(InputNumber, {
								style: cellNumStyle,
								min: 0,
								precision: 2,
								placeholder: '单价',
								value: isEmptyNum(v) ? null : v,
								status: fieldInvalid(record, 'laborUnitPrice') ? 'error' : undefined,
								onChange: function (val) { patch(record.key, 'laborUnitPrice', val == null ? null : val); }
							});
						}
					},
					{
						title: '金额(含税)',
						dataIndex: 'laborAmount',
						key: 'laborAmount',
						width: 100,
						align: 'left',
						onHeaderCell: moneyColumnProps().onHeaderCell,
						onCell: moneyColumnProps().onCell,
						render: function (v) { return fmtMoney(v); }
					}
				]
			},
			{
				title: '配件费',
				key: 'partsGroup',
				children: [
					{
						title: reqTitle('单位'),
						dataIndex: 'partsUnit',
						key: 'partsUnit',
						width: 72,
						align: 'center',
						render: function (v, record) {
							if (!canEditRow(record)) return readOnlyCell(v);
							return React.createElement(Input, {
								className: 'maint-inline-input',
								value: v,
								placeholder: '单位',
								status: fieldInvalid(record, 'partsUnit') ? 'error' : undefined,
								onChange: function (e) { patch(record.key, 'partsUnit', e.target.value); }
							});
						}
					},
					{
						title: reqTitle('数量'),
						dataIndex: 'partsQty',
						key: 'partsQty',
						width: 88,
						align: 'left',
						render: function (v, record) {
							if (!canEditRow(record)) return readOnlyCell(toIntQty(v), 'right');
							return React.createElement(InputNumber, {
								style: cellNumStyle,
								min: 0,
								step: 1,
								precision: 0,
								placeholder: '数量',
								value: isEmptyNum(v) ? null : toIntQty(v),
								status: fieldInvalid(record, 'partsQty') ? 'error' : undefined,
								onChange: function (val) { patch(record.key, 'partsQty', val == null ? null : toIntQty(val)); }
							});
						}
					},
					{
						title: reqTitle('单价(含税)'),
						dataIndex: 'partsUnitPrice',
						key: 'partsUnitPrice',
						width: 100,
						align: 'left',
						render: function (v, record) {
							if (!canEditRow(record)) return readOnlyCell(fmtMoney(v), 'right');
							return React.createElement(InputNumber, {
								style: cellNumStyle,
								min: 0,
								precision: 2,
								placeholder: '单价',
								value: isEmptyNum(v) ? null : v,
								status: fieldInvalid(record, 'partsUnitPrice') ? 'error' : undefined,
								onChange: function (val) { patch(record.key, 'partsUnitPrice', val == null ? null : val); }
							});
						}
					},
					{
						title: '金额(含税)',
						dataIndex: 'partsAmount',
						key: 'partsAmount',
						width: 100,
						align: 'left',
						onHeaderCell: moneyColumnProps().onHeaderCell,
						onCell: moneyColumnProps().onCell,
						render: function (v) { return fmtMoney(v); }
					}
				]
			},
			{
				title: '费用总计（含税）',
				dataIndex: 'totalCost',
				key: 'totalCost',
				width: 120,
				align: 'left',
				fixed: 'right',
				onHeaderCell: function () {
					return { className: 'sticky-col-right ldb-col-money' };
				},
				onCell: function () {
					return { className: 'sticky-col-right ldb-col-money tabular-nums' };
				},
				render: function (v) {
					return React.createElement('span', { className: 'ldb-computed-val' }, fmtMoney(v));
				}
			},
			{
				title: '附件',
				key: 'attachments',
				dataIndex: 'attachments',
				width: 76,
				align: 'center',
				fixed: 'right',
				onHeaderCell: fixedRightColumnProps().onHeaderCell,
				onCell: fixedRightColumnProps().onCell,
				render: function (v, record) {
					var count = (v && v.length) || 0;
					var clipIcon = React.createElement(
						'svg',
						{
							viewBox: '0 0 24 24',
							width: 16,
							height: 16,
							fill: 'none',
							stroke: 'currentColor',
							strokeWidth: 2,
							'aria-hidden': true
						},
						React.createElement('path', {
							d: 'M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48',
							strokeLinecap: 'round',
							strokeLinejoin: 'round'
						})
					);
					var btn = React.createElement(
						'button',
						{
							type: 'button',
							className: 'mldb-attach-btn' + (count > 0 ? ' has-files' : ''),
							onClick: function () {
								setAttachmentModal({
									open: true,
									rowKey: record.key,
									readOnly: !canManageAttachmentsForRow(record)
								});
							},
							'aria-label': count ? '查看 ' + count + ' 个附件' : '管理附件',
							title: count ? count + ' 个附件' : '上传附件'
						},
						clipIcon,
						count > 0
							? React.createElement('span', { className: 'mldb-attach-count' }, String(count))
							: null
					);
					return btn;
				}
			},
			{
				title: '提报人',
				dataIndex: 'reporterName',
				key: 'reporterName',
				width: 88,
				align: 'center',
				fixed: 'right',
				onHeaderCell: fixedRightColumnProps().onHeaderCell,
				onCell: fixedRightColumnProps().onCell,
				render: function (v) { return readOnlyCell(v); }
			},
			{
				title: '提报时间',
				dataIndex: 'reportTime',
				key: 'reportTime',
				width: 168,
				align: 'center',
				fixed: 'right',
				onHeaderCell: fixedRightColumnProps().onHeaderCell,
				onCell: fixedRightColumnProps().onCell,
				render: function (v) {
					return React.createElement(
						'div',
						{ className: 'maint-cell-readonly', style: { padding: '4px 8px', textAlign: 'center', whiteSpace: 'nowrap', minWidth: 152 } },
						formatDateTime(v) || '-'
					);
				}
			},
			{
				title: '操作',
				key: 'action',
				width: 168,
				align: 'center',
				fixed: 'right',
				onHeaderCell: fixedRightColumnProps('ldb-col-actions').onHeaderCell,
				onCell: fixedRightColumnProps('ldb-col-actions').onCell,
				render: function (_, record) {
					var ref = latestRefs.current;
					var submitted = record.submitStatus === 'submitted';
					var isOwn = record.createdBy === CURRENT_USER.id;
					var allowCopy = canCopyRow(record, ref.isSupervisor);

					function renderEditIcon() {
						return React.createElement(
							'svg',
							{ viewBox: '64 64 896 896', width: 14, height: 14, fill: 'currentColor', 'aria-hidden': true },
							React.createElement('path', {
								d: 'M257.7 752c2 0 4-.2 6-.5L431.9 722c2-.4 3.9-1.3 5.3-2.8l423.9-423.9a9.96 9.96 0 0 0 0-14.1L694.9 114.9c-1.9-1.9-4.4-2.9-7.1-2.9s-5.2 1-7.1 2.9L256.8 538.8c-1.5 1.5-2.4 3.3-2.8 5.3l-29.5 168.2a33.5 33.5 0 0 0 9.4 29.8c6.6 6.4 14.9 9.9 23.8 9.9z'
							})
						);
					}

					function renderCopyIcon() {
						return React.createElement(
							'svg',
							{ viewBox: '64 64 896 896', width: 14, height: 14, fill: 'currentColor', 'aria-hidden': true },
							React.createElement('path', {
								d: 'M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530c0 17.7 14.3 32 32 32h512c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32z'
							})
						);
					}

					function renderDeleteIcon() {
						return React.createElement(
							'svg',
							{ viewBox: '64 64 896 896', width: 14, height: 14, fill: 'currentColor', 'aria-hidden': true },
							React.createElement('path', {
								d: 'M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60v520c0 17.7 14.3 32 32 32h632c17.7 0 32-14.3 32-32V336h60c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM512 464c-17.7 0-32 14.3-32 32v288c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32h-64zm-192 0c-17.7 0-32 14.3-32 32v288c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32h-64zm-192 0c-17.7 0-32 14.3-32 32v288c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32V496c0-17.7-14.3-32-32-32h-64z'
							})
						);
					}

					function renderMoreIcon() {
						return React.createElement(
							'svg',
							{ viewBox: '0 0 16 16', width: 16, height: 16, fill: 'currentColor', 'aria-hidden': true },
							React.createElement('circle', { cx: '8', cy: '3', r: '1.5' }),
							React.createElement('circle', { cx: '8', cy: '8', r: '1.5' }),
							React.createElement('circle', { cx: '8', cy: '13', r: '1.5' })
						);
					}

					function renderIconAction(title, iconNode, onClick, extraClass, disabled) {
						var cls =
							'maint-action-icon-btn' + (extraClass ? ' ' + extraClass : '') + (disabled ? ' is-disabled' : '');
						var node = React.createElement(
							'span',
							{
								className: cls,
								role: 'button',
								tabIndex: disabled ? -1 : 0,
								'aria-label': title,
								onClick:
									disabled || !onClick
										? undefined
										: function (e) {
												e.stopPropagation();
												onClick(e);
											}
							},
							iconNode
						);
						return React.createElement(Tooltip, { title: title }, node);
					}

					function toggleEditKeys() {
						ref.setEditingKeys(function (prev) {
							if (prev.indexOf(record.key) >= 0) {
								return prev.filter(function (k) { return k !== record.key; });
							}
							return prev.concat([record.key]);
						});
					}

					function renderMoreAction() {
						return React.createElement(OperationActions, {
							more: [{
								key: 'changelog',
								label: '变更日志',
								onClick: function () { openChangeLogModal(record); }
							}]
						});
					}

					function renderEditDeletePair(onEditClick, opts) {
						opts = opts || {};
						var showCopy = opts.showCopy !== false && allowCopy;
						var allowEdit = opts.allowEdit !== false;
						var allowDelete =
							opts.allowDelete !== false && canDeleteRow(record, ref.isSupervisor);
						var isEditing = ref.editingKeys.indexOf(record.key) >= 0;
						var editTitle = isEditing ? '完成编辑' : '编辑';
						var more = [];
						if (showCopy) {
							more.push({
								key: 'copy',
								label: '复制',
								onClick: function () {
									setCopyPopoverKey(record.key);
									setCopyRowCount(1);
								}
							});
						}
						if (allowDelete) {
							more.push({
								key: 'delete',
								label: '删除',
								danger: true,
								onClick: function () {
									Modal.confirm({
										title: '确认删除该条维修/保养记录？',
										okText: '删除',
										okType: 'danger',
										cancelText: '取消',
										onOk: function () {
											if (!allowDelete) {
												if (record.submitStatus === 'submitted') {
													message.info('已提交记录仅运维主管可删除');
												}
												return;
											}
											ref.setAllRows(function (prev) {
												return prev.filter(function (r) { return r.key !== record.key; });
											});
											ref.setEditingKeys(function (prev) {
												return prev.filter(function (k) { return k !== record.key; });
											});
											message.success('已删除');
										}
									});
								}
							});
						}
						more.push({
							key: 'changelog',
							label: '变更日志',
							onClick: function () { openChangeLogModal(record); }
						});
						var popOpen = copyPopoverKey === record.key;
						var popContent = React.createElement(
							'div',
							{ style: { width: 220 } },
							React.createElement(
								'div',
								{ style: { marginBottom: 8, fontSize: 13, color: 'rgba(15,23,42,0.65)' } },
								'复制当前行已填写的全部数据'
							),
							React.createElement(
								'div',
								{ style: { marginBottom: 6, fontSize: 13, color: 'rgba(15,23,42,0.55)' } },
								'复制行数'
							),
							React.createElement(InputNumber, {
								style: { width: '100%', marginBottom: 10 },
								min: 1,
								max: 100,
								precision: 0,
								value: popOpen ? copyRowCount : 1,
								onChange: function (val) {
									setCopyRowCount(val == null ? 1 : val);
								}
							}),
							React.createElement(
								Button,
								{
									type: 'primary',
									block: true,
									size: 'small',
									onClick: function () {
										applyCopyRows(record.key, copyRowCount);
									}
								},
								'确认复制'
							)
						);
						var actionsNode = React.createElement(OperationActions, {
							edit: allowEdit ? { label: editTitle, onClick: onEditClick } : undefined,
							more: more
						});
						if (!showCopy) return actionsNode;
						return React.createElement(
							Popover,
							{
								trigger: 'click',
								placement: 'leftTop',
								open: popOpen,
								content: popContent,
								onOpenChange: function (open) {
									if (open) {
										setCopyPopoverKey(record.key);
										setCopyRowCount(1);
									} else if (copyPopoverKey === record.key) {
										setCopyPopoverKey(null);
									}
								}
							},
							actionsNode
						);
					}

					if (submitted) {
						if (!canShowSubmittedActions(record, ref.isSupervisor)) {
							return renderMoreAction();
						}
						return renderEditDeletePair(
							function () {
								if (!ref.isSupervisor) {
									message.info('已提交记录仅运维主管可编辑');
									return;
								}
								toggleEditKeys();
							},
							{ showCopy: false }
						);
					}

					if (isSavedDraftRow(record)) {
						if (!canShowSavedActions(record, ref.isSupervisor)) return renderMoreAction();
						return renderEditDeletePair(function () {
							toggleEditKeys();
						});
					}

					if (ref.isSupervisor || !isOwn) return renderMoreAction();

					return renderEditDeletePair(function () {}, { allowEdit: false, showCopy: true });
				}
			}
		];
	}, [
		canEditRow,
		canManageAttachmentsForRow,
		editingKeys,
		isSupervisor,
		rowInvalid,
		copyPopoverKey,
		copyRowCount,
		applyCopyRows,
		openChangeLogModal,
		setAttachmentModal
	]);

	var changeLogRows = useMemo(
		function () {
			if (!changeLogModal.rowKey) return [];
			return changeLogsByKey[changeLogModal.rowKey] || [];
		},
		[changeLogModal.rowKey, changeLogsByKey]
	);

	var attachmentModalRow = useMemo(
		function () {
			if (!attachmentModal.rowKey) return null;
			for (var i = 0; i < allRows.length; i++) {
				if (allRows[i].key === attachmentModal.rowKey) return allRows[i];
			}
			return null;
		},
		[attachmentModal.rowKey, allRows]
	);

	var attachmentModalFiles = (attachmentModalRow && attachmentModalRow.attachments) || [];

	var handleAttachmentBeforeUpload = useCallback(
		function (file) {
			if (!attachmentModal.rowKey) return Upload.LIST_IGNORE;
			if (!isAttachmentAllowed(file)) {
				message.error('不支持「' + file.name + '」，请上传照片、Word、Excel 或 PDF 格式');
				return Upload.LIST_IGNORE;
			}
			var att = buildAttachmentFromFile(file);
			updateRowAttachments(attachmentModal.rowKey, attachmentModalFiles.concat([att]));
			message.success('已添加：' + file.name);
			return false;
		},
		[attachmentModal.rowKey, attachmentModalFiles, updateRowAttachments]
	);

	var removeAttachmentItem = useCallback(
		function (attId) {
			if (!attachmentModal.rowKey || attachmentModal.readOnly) return;
			var removed = null;
			var next = attachmentModalFiles.filter(function (a) {
				if (a.id === attId) {
					removed = a;
					return false;
				}
				return true;
			});
			if (removed && removed.url) {
				try {
					URL.revokeObjectURL(removed.url);
				} catch (eRevoke) {}
			}
			updateRowAttachments(attachmentModal.rowKey, next);
			message.success('已移除附件');
		},
		[attachmentModal.rowKey, attachmentModal.readOnly, attachmentModalFiles, updateRowAttachments]
	);

	var previewAttachmentItem = useCallback(function (att) {
		if (!att) return;
		if (att.url) {
			window.open(att.url, '_blank', 'noopener,noreferrer');
			return;
		}
		message.info('原型演示：「' + att.name + '」预览需联调文件服务');
	}, []);

	var closeImportModal = useCallback(function () {
		setImportModalOpen(false);
		setImportFileList([]);
		setImportPreview(null);
	}, []);

	var downloadImportTemplate = useCallback(function () {
		var ok = downloadWorkbookFile(
			'车辆维修明细导入模板.xlsx',
			'导入模板',
			buildImportTemplateAoa()
		);
		if (ok) message.success('已下载导入模板');
	}, []);

	var downloadImportFailedRecords = useCallback(function () {
		if (!importPreview || !importPreview.errors || !importPreview.errors.length) return;
		var headers = IMPORT_TEMPLATE_HEADERS.concat(['备注']);
		var body = importPreview.errors.map(function (err) {
			return importParsedRowToLine(err.row).concat([err.reasons.join('；')]);
		});
		var ok = downloadWorkbookFile(
			'车辆维修明细导入失败记录_' + Date.now() + '.xlsx',
			'失败记录',
			[headers].concat(body)
		);
		if (ok) message.success('已下载失败记录 ' + body.length + ' 条');
	}, [importPreview]);

	var parseImportFile = useCallback(function (file) {
		if (!file) return;
		var name = String(file.name || '').toLowerCase();
		if (!name.endsWith('.csv') && !name.endsWith('.xls') && !name.endsWith('.xlsx')) {
			message.error('仅支持 .csv、.xls、.xlsx 格式');
			return;
		}
		setImportFileList([file]);

		function finishParse(result) {
			if (result.headerError) {
				message.error(result.headerError);
				setImportPreview(null);
				return;
			}
			if (!result.rows.length) {
				message.warning('未解析到有效数据，请检查文件内容');
				setImportPreview(null);
				return;
			}
			var valid = [];
			var errors = [];
			var previewRows = [];
			var ri;
			for (ri = 0; ri < result.rows.length; ri++) {
				var row = result.rows[ri];
				var rowErrors = validateImportMaintenanceRow(row);
				previewRows.push({
					key: 'import-preview-' + row.lineNo,
					lineNo: row.lineNo,
					repairDate: row.repairDate,
					plateNo: row.plateNo,
					recordType: row.recordType,
					itemDesc: row.itemDesc,
					remark: rowErrors.length ? rowErrors.join('；') : '',
					isError: rowErrors.length > 0
				});
				if (rowErrors.length) {
					errors.push({
						lineNo: row.lineNo,
						label: (row.plateNo || '—') + ' · ' + (row.itemDesc || '—'),
						reasons: rowErrors,
						row: row
					});
				} else {
					valid.push(row);
				}
			}
			setImportPreview({ valid: valid, errors: errors, previewRows: previewRows, fileName: file.name });
		}

		if (name.endsWith('.csv')) {
			var csvReader = new FileReader();
			csvReader.onload = function (ev) {
				var text = (ev && ev.target && ev.target.result) || '';
				finishParse(parseImportSheetAoa(csvTextToAoa(text)));
			};
			csvReader.onerror = function () {
				message.error('文件读取失败');
			};
			csvReader.readAsText(file, 'UTF-8');
			return;
		}

		var XLSX = window.XLSX;
		if (!XLSX) {
			message.error('Excel 解析组件未加载，请使用 CSV 模板或刷新页面');
			setImportPreview(null);
			return;
		}
		var xlsxReader = new FileReader();
		xlsxReader.onload = function (ev) {
			try {
				var data = new Uint8Array((ev && ev.target && ev.target.result) || []);
				var wb = XLSX.read(data, { type: 'array' });
				var sheetName = wb.SheetNames[0];
				var sheet = wb.Sheets[sheetName];
				var aoa = XLSX.utils.sheet_to_json(sheet, { header: 1, defval: '' });
				finishParse(parseImportSheetAoa(aoa));
			} catch (eParse) {
				message.error('Excel 文件解析失败，请检查格式');
				setImportPreview(null);
			}
		};
		xlsxReader.onerror = function () {
			message.error('文件读取失败');
		};
		xlsxReader.readAsArrayBuffer(file);
	}, []);

	var handleConfirmImport = useCallback(
		function () {
			if (!importPreview || !importPreview.valid.length) {
				message.warning('没有可导入的有效数据');
				return;
			}
			var imported = importPreview.valid.map(function (row) {
				return importRowToMaintenanceRow(row);
			});
			imported.forEach(function (row) {
				appendChangeLog(row.key, '_create', null, null, row, {
					fieldLabel: '批量导入',
					beforeText: '—',
					afterText: '导入记录',
					force: true
				});
			});
			setAllRows(function (prev) {
				return prev.concat(imported);
			});
			var errN = (importPreview.errors || []).length;
			if (errN) {
				message.success(
					'成功导入 ' + imported.length + ' 条（待保存），' + errN + ' 条校验失败已跳过'
				);
			} else {
				message.success(
					'成功导入 ' + imported.length + ' 条，状态为「待保存」，请补传附件后保存并确认提交'
				);
			}
			closeImportModal();
		},
		[importPreview, appendChangeLog, closeImportModal]
	);

	var handleExport = useCallback(
		function () {
			if (!visibleRows.length) {
				message.warning('当前无数据可导出，请先查询或录入');
				return;
			}
			var body = visibleRows.map(rowToExportLine);
			var stamp = formatDate(nowDayjs()) || 'export';
			var ok = downloadWorkbookFile(
				'车辆维修明细导出_' + stamp + '_' + Date.now() + '.xlsx',
				'维修明细',
				[EXPORT_HEADERS].concat(body)
			);
			if (ok) message.success('已导出 ' + visibleRows.length + ' 条（当前筛选结果）');
		},
		[visibleRows]
	);

	var changeLogColumns = useMemo(
		function () {
			return [
				{
					title: '修改时间',
					dataIndex: 'at',
					key: 'at',
					width: 168,
					render: function (v) {
						return formatDateTime(v) || '—';
					}
				},
				{
					title: '修改人',
					dataIndex: 'userName',
					key: 'userName',
					width: 88,
					align: 'center'
				},
				{
					title: '修改字段',
					dataIndex: 'fieldLabel',
					key: 'fieldLabel',
					width: 140,
					align: 'center'
				},
				{
					title: '修改前',
					dataIndex: 'before',
					key: 'before',
					ellipsis: true,
					render: function (v) {
						return React.createElement('span', { style: { color: '#cf1322' } }, v == null || v === '' ? '—' : v);
					}
				},
				{
					title: '修改后',
					dataIndex: 'after',
					key: 'after',
					ellipsis: true,
					render: function (v) {
						return React.createElement('span', { style: { color: '#389e0d' } }, v == null || v === '' ? '—' : v);
					}
				}
			];
		},
		[]
	);

	var tableSummary = useCallback(function () {
		return React.createElement(
			Table.Summary,
			null,
			React.createElement(
				Table.Summary.Row,
				null,
				React.createElement(Table.Summary.Cell, { index: 0, align: 'center' }, '合计'),
				React.createElement(Table.Summary.Cell, { index: 1, colSpan: 7 }),
				React.createElement(Table.Summary.Cell, { index: 8, colSpan: 3 }),
				React.createElement(Table.Summary.Cell, { index: 11, align: 'left' }, fmtMoney(totals.laborAmount)),
				React.createElement(Table.Summary.Cell, { index: 12, colSpan: 3 }),
				React.createElement(Table.Summary.Cell, { index: 15, align: 'left' }, fmtMoney(totals.partsAmount)),
				React.createElement(Table.Summary.Cell, { index: 16 }),
				React.createElement(Table.Summary.Cell, { index: 17, align: 'left' }, fmtMoney(totals.totalCost)),
				React.createElement(Table.Summary.Cell, { index: 18 }),
				React.createElement(Table.Summary.Cell, { index: 19 })
			)
		);
	}, [totals]);

	return React.createElement(
		App,
		null,
		React.createElement(
			'div',
			{ className: 'vm-page ldb-page mldb-page', 'data-annotation-id': 'vmaint-header' },
			React.createElement(
				'section',
				{ className: 'vm-filter-card ldb-filter-card', 'data-annotation-id': 'mldb-filter' },
				React.createElement(
					'header',
					{ className: 'vm-filter-header' },
					React.createElement('h2', { className: 'vm-filter-title' }, '筛选条件')
				),
				React.createElement(
					'div',
					{ className: 'ldb-filter-body' },
					React.createElement(
						'div',
						{ className: 'vm-filter-grid ldb-filter-grid ldb-filter-grid--primary' },
					React.createElement(
						'label',
						{ className: 'vm-filter-field' },
						React.createElement('span', null, '进修日期'),
						React.createElement(DateRangeFilterField, {
							startDate: dateRangeDraft.startDate,
							endDate: dateRangeDraft.endDate,
							ariaLabel: '进修日期',
							startPlaceholder: '开始日期',
							endPlaceholder: '结束日期',
							onChange: function (range) {
								setDateRangeDraft({ startDate: range.startDate, endDate: range.endDate });
							}
						})
					),
					React.createElement(
						'label',
						{ className: 'vm-filter-field' },
						React.createElement('span', null, '车牌号'),
						React.createElement(Select, {
							allowClear: true,
							showSearch: true,
							placeholder: '全部，可输入车牌搜索',
							value: plateDraft,
							onChange: function (v) { setPlateDraft(v); },
							options: vehiclePlateOptions,
							optionFilterProp: 'label',
							filterOption: filterPlateOption
						})
					),
					React.createElement(
						'label',
						{ className: 'vm-filter-field' },
						React.createElement('span', null, '类型'),
						React.createElement(Select, {
							mode: 'multiple',
							allowClear: true,
							placeholder: '全部',
							value: typeDraft,
							onChange: function (v) { setTypeDraft(v || []); },
							options: RECORD_TYPE_OPTIONS,
							maxTagCount: 2
						})
					),
					React.createElement(
						'label',
						{ className: 'vm-filter-field' },
						React.createElement('span', null, '提报人'),
						React.createElement(Select, {
							mode: 'multiple',
							allowClear: true,
							showSearch: true,
							placeholder: '全部',
							value: reporterDraft,
							onChange: function (v) { setReporterDraft(v || []); },
							options: reporterOptions,
							filterOption: filterOption,
							maxTagCount: 2
						})
					)
				)
				),
				React.createElement(
					'div',
					{ className: 'vm-filter-actions ldb-filter-actions' },
					React.createElement('button', { 'data-vm-icon': 'rotate-ccw',  type: 'button', className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: handleReset  }, '重置'),
					React.createElement('button', { 'data-vm-icon': 'search',  type: 'button', className: 'vm-btn vm-btn-primary ldb-toolbar-btn', onClick: handleQuery  }, '查询')
				)
			),
			React.createElement(
				'section',
				{ className: 'vm-table-section' },
				React.createElement(
					'div',
					{ className: 'vm-table-toolbar ldb-table-toolbar', 'data-annotation-id': 'mldb-toolbar' },
					React.createElement(
						'div',
						{
							className: 'mldb-table-hint',
							role: 'note',
							'data-annotation-id': 'mldb-table-hint'
						},
						React.createElement(
							'svg',
							{
								className: 'mldb-table-hint__icon',
								viewBox: '0 0 24 24',
								width: 16,
								height: 16,
								fill: 'none',
								stroke: 'currentColor',
								strokeWidth: 2,
								strokeLinecap: 'round',
								strokeLinejoin: 'round',
								'aria-hidden': true
							},
							React.createElement('path', {
								d: 'M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z'
							}),
							React.createElement('line', { x1: 12, y1: 9, x2: 12, y2: 13 }),
							React.createElement('line', { x1: 12, y1: 17, x2: 12.01, y2: 17 })
						),
						React.createElement(
							'p',
							{ className: 'mldb-table-hint__text' },
							'维修保养台账涉及租赁账单，请及时填报并确认提交'
						)
					),
					React.createElement(
						'div',
						{ className: 'vm-table-actions' },
						React.createElement(
							'button',
							{
								type: 'button',
								className: 'vm-btn vm-btn-secondary ldb-toolbar-btn',
								'data-vm-icon': 'import',
								onClick: function () { setImportModalOpen(true); }
							},
							'批量导入'
						),
						React.createElement(
							'button',
							{
								type: 'button',
								className: 'vm-btn vm-btn-ghost ldb-toolbar-btn',
								'data-vm-icon': 'download',
								onClick: handleExport
							},
							'导出'
						),
						React.createElement('button', { 'data-vm-icon': 'save',  type: 'button', className: 'vm-btn vm-btn-ghost ldb-toolbar-btn', onClick: handleSave  }, '保存'),
						!isSupervisor
							? React.createElement('button', { 'data-vm-icon': 'send',  type: 'button', className: 'vm-btn vm-btn-primary ldb-toolbar-btn', onClick: handleConfirmSubmit  }, '确认提交')
							: null
					)
				),
				React.createElement(
					'div',
					{ className: 'vm-table-card' },
					React.createElement(
						'div',
						{ className: 'vm-table-wrap ldb-table-wrap', 'data-annotation-id': 'mldb-table' },
						visibleRows.length
							? React.createElement(Table, {
								className: 'mldb-ant-table ldb-table',
								size: 'small',
								bordered: false,
								rowKey: 'key',
								columns: columns,
								dataSource: pagedVisibleRows,
								pagination: false,
								rowClassName: function (record, index) {
									var tier = record.displayTier != null ? record.displayTier : rowDisplaySortTier(record);
									var prev = pagedVisibleRows[index - 1];
									var prevTier = prev ? (prev.displayTier != null ? prev.displayTier : rowDisplaySortTier(prev)) : tier;
									var cls = 'maint-row-data maint-row-tier-' + tier;
									if (index > 0 && prevTier !== tier) cls += ' maint-row-tier-boundary';
									return cls;
								},
								scroll: { x: 'max-content' },
								summary: tableSummary
							})
							: React.createElement(
								'div',
								{ className: 'vm-empty' },
								React.createElement('p', null, '暂无符合条件的维修/保养记录')
							)
					),
					!isSupervisor
						? React.createElement(
							Button,
							{
								type: 'dashed',
								block: true,
								className: 'mldb-add-row-btn',
								onClick: addRow
							},
							'新增一行'
						)
						: null,
					React.createElement(
						'div',
						{ className: 'vm-table-footer ldb-table-footer' },
						React.createElement(TablePagination, {
							page: listSafePage,
							pageSize: pageSize,
							total: visibleRows.length,
							onPageChange: setPage,
							onPageSizeChange: function (size) {
								setPageSize(size);
								setPage(1);
							}
						})
					)
				)
			),
			React.createElement(
				Modal,
				{
					title: '变更日志' + (changeLogModal.rowLabel ? ' · ' + changeLogModal.rowLabel : ''),
					open: changeLogModal.open,
					onCancel: closeChangeLogModal,
					footer: React.createElement(Button, { onClick: closeChangeLogModal }, '关闭'),
					width: 880,
					destroyOnClose: true
				},
				React.createElement(Table, {
					size: 'small',
					bordered: true,
					rowKey: 'id',
					pagination: changeLogRows.length > 8 ? { pageSize: 8, showSizeChanger: false } : false,
					columns: changeLogColumns,
					dataSource: changeLogRows,
					locale: { emptyText: '暂无变更记录' },
					scroll: { x: 'max-content', y: 360 }
				})
			),
			React.createElement(
				Modal,
				{
					title:
						'附件管理' +
						(attachmentModalRow && attachmentModalRow.plateNo ? ' · ' + attachmentModalRow.plateNo : ''),
					open: attachmentModal.open,
					onCancel: closeAttachmentModal,
					footer: React.createElement(Button, { onClick: closeAttachmentModal }, '关闭'),
					width: 560,
					destroyOnClose: true,
					className: 'mldb-attach-modal'
				},
				React.createElement(
					'div',
					{ className: 'mldb-attach-modal-body' },
					attachmentModalFiles.length
						? React.createElement(
							'ul',
							{ className: 'mldb-attach-list' },
							attachmentModalFiles.map(function (att) {
								var kind = att.kind || 'other';
								return React.createElement(
									'li',
									{ key: att.id, className: 'mldb-attach-item' },
									React.createElement(
										'span',
										{ className: 'mldb-attach-kind mldb-attach-kind--' + kind },
										ATTACHMENT_KIND_LABEL[kind] || ATTACHMENT_KIND_LABEL.other
									),
									React.createElement(
										'div',
										{ className: 'mldb-attach-meta' },
										React.createElement(
											'button',
											{
												type: 'button',
												className: 'mldb-attach-name',
												onClick: function () { previewAttachmentItem(att); }
											},
											att.name
										),
										React.createElement('span', { className: 'mldb-attach-size' }, formatFileSize(att.size))
									),
									attachmentModal.readOnly
										? null
										: React.createElement(
											'button',
											{
												type: 'button',
												className: 'mldb-attach-remove',
												onClick: function () { removeAttachmentItem(att.id); },
												'aria-label': '移除 ' + att.name
											},
											'移除'
										)
								);
							})
						)
						: React.createElement('p', { className: 'mldb-attach-empty' }, '暂无附件'),
					attachmentModal.readOnly
						? React.createElement(
							'p',
							{ className: 'mldb-attach-readonly-hint' },
							'记录已锁定，仅可查看附件；如需修改请联系运维主管。'
						)
						: React.createElement(
							Upload.Dragger,
							{
								className: 'mldb-attach-upload',
								multiple: true,
								showUploadList: false,
								accept: ATTACHMENT_ACCEPT,
								beforeUpload: handleAttachmentBeforeUpload
							},
							React.createElement('p', { className: 'mldb-attach-upload-title' }, '点击或拖拽文件到此处上传'),
							React.createElement(
								'p',
								{ className: 'mldb-attach-upload-hint' },
								'支持照片（JPG/PNG 等）、Word（.doc/.docx）、Excel（.xls/.xlsx/.csv）、PDF；可多选上传'
							)
						)
				)
			),
			React.createElement(
				Modal,
				{
					title: '批量导入维修/保养明细',
					open: importModalOpen,
					onCancel: closeImportModal,
					width: 920,
					destroyOnClose: true,
					okText: '确认导入',
					cancelText: '取消',
					okButtonProps: {
						disabled: !importPreview || !importPreview.valid.length
					},
					onOk: handleConfirmImport
				},
				React.createElement(
					'div',
					{ className: 'mldb-import-template-bar' },
					React.createElement(
						'div',
						{ className: 'mldb-import-template-text' },
						React.createElement('strong', null, '第一步：下载导入模板'),
						React.createElement(
							'p',
							null,
							'模板字段与表格一致；「类型」列下方以 1.维修、2.保养 序列展示可选值，填写须完全匹配，否则导入报错。'
						)
					),
					React.createElement(
						Button,
						{
							type: 'primary',
							ghost: true,
							className: 'mldb-import-template-btn',
							onClick: downloadImportTemplate
						},
						'下载模板'
					)
				),
				React.createElement(Alert, {
					type: 'info',
					showIcon: true,
					className: 'mldb-import-alert',
					message: '第二步：填写模板后上传',
					description:
						'支持 .xlsx、.xls、.csv；上传后系统将校验并预览可导入条数。导入记录标记为「待保存（未保存）」，可补传附件，保存后再点「确认提交」归档。'
				}),
				React.createElement(
					Upload.Dragger,
					{
						className: 'mldb-import-upload',
						accept: '.csv,.xlsx,.xls',
						multiple: false,
						maxCount: 1,
						fileList: importFileList,
						beforeUpload: function (file) {
							parseImportFile(file);
							return false;
						},
						onRemove: function () {
							setImportFileList([]);
							setImportPreview(null);
							return true;
						}
					},
					React.createElement('p', { className: 'mldb-import-upload-title' }, '点击或拖拽文件到此处上传'),
					React.createElement('p', { className: 'mldb-import-upload-hint' }, '单次上传一个文件')
				),
				importPreview
					? React.createElement(
						'div',
						{ className: 'mldb-import-preview' },
						React.createElement(
							'div',
							{ className: 'mldb-import-preview-title' },
							'解析结果 · ' + (importPreview.fileName || '')
						),
						React.createElement(
							'div',
							{ className: 'mldb-import-preview-stats' },
							React.createElement(
								'span',
								{ className: 'mldb-import-preview-ok' },
								'可导入 ' + importPreview.valid.length + ' 条'
							),
							importPreview.errors.length
								? React.createElement(
									'span',
									{ className: 'mldb-import-preview-err' },
									'失败 ' + importPreview.errors.length + ' 条'
								)
								: null
						),
						importPreview.errors.length
							? React.createElement(
								'div',
								{ className: 'mldb-import-fail-actions' },
								React.createElement(
									Button,
									{
										type: 'link',
										size: 'small',
										className: 'mldb-import-fail-download',
										onClick: downloadImportFailedRecords
									},
									'下载失败记录'
								)
							)
							: null,
						importPreview.previewRows && importPreview.previewRows.length
							? React.createElement(Table, {
								className: 'mldb-import-preview-table',
								size: 'small',
								pagination: false,
								scroll: { x: 760, y: 220 },
								rowClassName: function (record) {
									return record.isError ? 'mldb-import-preview-row--error' : '';
								},
								columns: [
									{
										title: '行号',
										dataIndex: 'lineNo',
										key: 'lineNo',
										width: 56,
										align: 'center'
									},
									{
										title: '进修日期',
										dataIndex: 'repairDate',
										key: 'repairDate',
										width: 108,
										ellipsis: true
									},
									{
										title: '车牌号',
										dataIndex: 'plateNo',
										key: 'plateNo',
										width: 100,
										ellipsis: true
									},
									{
										title: '类型',
										dataIndex: 'recordType',
										key: 'recordType',
										width: 72,
										align: 'center'
									},
									{
										title: '项目/材料',
										dataIndex: 'itemDesc',
										key: 'itemDesc',
										width: 140,
										ellipsis: true
									},
									{
										title: '备注',
										dataIndex: 'remark',
										key: 'remark',
										width: 220,
										ellipsis: true,
										render: function (v, record) {
											if (!v) {
												return React.createElement(
													'span',
													{ className: 'mldb-import-preview-remark-ok' },
													'可导入'
												);
											}
											return React.createElement(
												'span',
												{ className: 'mldb-import-preview-remark-err', title: v },
												v
											);
										}
									}
								],
								dataSource: importPreview.previewRows
							})
							: null
					)
					: null
			)
		)
	);
};

export default Component;
