feat: allow editing manager field in cleaner table

- Add inline editing for manager column (Admin users can double-click to select from dropdown)
- Auto-assign current user as manager when User checks a material
- Defer database writes until 'Confirm Delete' button is clicked
- Add updateManager IPC handler and DAO method
- Update preload API with updateManager method
This commit is contained in:
Misaka_Company
2026-03-05 13:19:12 +08:00
parent 921ca15be6
commit ba64c27457
6 changed files with 205 additions and 7 deletions

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useMemo } from 'react'
import { useState, useEffect, useMemo, useRef } from 'react'
export interface ValidationResult {
materialName: string
@@ -60,6 +60,11 @@ export function useCleaner() {
})
const [showSettingsMenu, setShowSettingsMenu] = useState(false)
// Inline editing state for manager field (Admin only)
const [editingCell, setEditingCell] = useState<{ rowIndex: number; field: string } | null>(null)
const [editValue, setEditValue] = useState('')
const inputRef = useRef<HTMLInputElement | HTMLSelectElement>(null)
// Check admin status and get shared Production IDs on mount
useEffect(() => {
const initializePage = async () => {
@@ -159,6 +164,56 @@ export function useCleaner() {
})
}
const startEdit = (rowIndex: number, field: string) => {
const row = filteredResults[rowIndex]
if (!row) return
setEditingCell({ rowIndex, field })
setEditValue(row[field as keyof ValidationResult] as string)
setTimeout(() => {
if (inputRef.current) {
inputRef.current.focus()
if (inputRef.current instanceof HTMLInputElement) {
inputRef.current.select()
}
}
}, 0)
}
const saveEdit = () => {
if (!editingCell) return
const { rowIndex } = editingCell
const row = filteredResults[rowIndex]
if (!row) return
const newValue = editValue.trim()
if (row.managerName === newValue) {
setEditingCell(null)
return
}
setValidationResults((prev) =>
prev.map((r) => (r.materialCode === row.materialCode ? { ...r, managerName: newValue } : r))
)
setEditingCell(null)
}
const cancelEdit = () => {
setEditingCell(null)
}
const handleAssignManagerOnSelect = (materialCode: string) => {
if (!isAdmin && currentUsername) {
const result = validationResults.find((r) => r.materialCode === materialCode)
if (result && (!result.managerName || result.managerName !== currentUsername)) {
setValidationResults((prev) =>
prev.map((r) =>
r.materialCode === materialCode ? { ...r, managerName: currentUsername } : r
)
)
}
}
}
const handleConfirmDeletion = async () => {
const resultsToProcess = isAdmin ? validationResults : filteredResults
@@ -334,6 +389,14 @@ export function useCleaner() {
isReportDialogOpen,
setIsReportDialogOpen,
reportData,
editingCell,
editValue,
setEditValue,
inputRef,
startEdit,
saveEdit,
cancelEdit,
handleAssignManagerOnSelect,
handleValidation,
handleCheckboxToggle,
handleConfirmDeletion,

View File

@@ -47,6 +47,14 @@ const CleanerPage: React.FC = () => {
isReportDialogOpen,
setIsReportDialogOpen,
reportData,
editingCell,
editValue,
setEditValue,
inputRef,
startEdit,
saveEdit,
cancelEdit,
handleAssignManagerOnSelect,
handleValidation,
handleCheckboxToggle,
handleConfirmDeletion,
@@ -277,13 +285,15 @@ const CleanerPage: React.FC = () => {
</td>
</tr>
) : (
filteredResults.map((result) => {
filteredResults.map((result, rowIndex) => {
const isChecked = selectedItems.has(result.materialCode)
const trClass = isChecked ? 'bg-blue-50/30' : 'hover:bg-slate-50'
const noManager = !result.managerName?.trim()
const managerCellClass = noManager
? 'text-amber-500 text-xs italic'
: 'text-slate-700'
const isEditingManager =
editingCell?.rowIndex === rowIndex && editingCell?.field === 'managerName'
return (
<tr
@@ -293,13 +303,23 @@ const CleanerPage: React.FC = () => {
<td className="px-4 py-3 text-center truncate">
{isChecked ? (
<CheckSquare
onClick={() => handleCheckboxToggle(result.materialCode)}
onClick={() => {
handleCheckboxToggle(result.materialCode)
if (!isAdmin) {
handleAssignManagerOnSelect(result.materialCode)
}
}}
size={16}
className="text-blue-600 inline cursor-pointer"
/>
) : (
<Square
onClick={() => handleCheckboxToggle(result.materialCode)}
onClick={() => {
handleCheckboxToggle(result.materialCode)
if (!isAdmin) {
handleAssignManagerOnSelect(result.materialCode)
}
}}
size={16}
className="text-slate-300 inline cursor-pointer"
/>
@@ -331,9 +351,43 @@ const CleanerPage: React.FC = () => {
</td>
<td
className={`px-4 py-3 truncate ${managerCellClass}`}
title={result.managerName || '空(待分配)'}
title={result.managerName || '空 (待分配)'}
>
{result.managerName || '空(待分配)'}
{isAdmin && isEditingManager ? (
<select
ref={inputRef as React.Ref<HTMLSelectElement>}
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onBlur={saveEdit}
onKeyDown={(e) => {
if (e.key === 'Enter') saveEdit()
if (e.key === 'Escape') cancelEdit()
}}
onClick={(e) => e.stopPropagation()}
className="w-full px-2 py-1 border border-blue-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 text-xs"
>
<option value=""></option>
{managers.map((m) => (
<option key={m} value={m}>
{m}
</option>
))}
</select>
) : isAdmin ? (
<div
className="min-h-[24px] cursor-text"
onDoubleClick={(e) => {
e.stopPropagation()
startEdit(rowIndex, 'managerName')
}}
>
{result.managerName || (
<span className="text-slate-400 italic"></span>
)}
</div>
) : (
result.managerName || '空 (待分配)'
)}
</td>
</tr>
)