setUserRequirements({
- ...userRequirements,
- [material.id]: e.target.value
- })}
- placeholder="Enter additional request..."
- style={{
- width: '100%',
- padding: '6px 8px',
- border: '1px solid #d1d5db',
- borderRadius: '4px',
- fontSize: '12px'
- }}
- />
+
{info.purchaseQuantity.toLocaleString()}
diff --git a/frontend/src/components/bom/materials/PipeMaterialsView.jsx b/frontend/src/components/bom/materials/PipeMaterialsView.jsx
index cd27f09..063ed56 100644
--- a/frontend/src/components/bom/materials/PipeMaterialsView.jsx
+++ b/frontend/src/components/bom/materials/PipeMaterialsView.jsx
@@ -11,6 +11,7 @@ const PipeMaterialsView = ({
setUserRequirements,
purchasedMaterials,
onPurchasedMaterialsUpdate,
+ updateMaterial, // 자재 업데이트 함수
fileId,
jobNo,
user,
@@ -19,6 +20,97 @@ const PipeMaterialsView = ({
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' });
const [columnFilters, setColumnFilters] = useState({});
const [showFilterDropdown, setShowFilterDropdown] = useState(null);
+ const [brandInputs, setBrandInputs] = useState({}); // 브랜드 입력 상태
+ const [savingBrand, setSavingBrand] = useState({}); // 브랜드 저장 중 상태
+ const [savingRequest, setSavingRequest] = useState({}); // 추가요구사항 저장 중 상태
+ const [savedBrands, setSavedBrands] = useState({}); // 저장된 브랜드 상태
+ const [savedRequests, setSavedRequests] = useState({}); // 저장된 추가요구사항 상태
+ const [editingBrand, setEditingBrand] = useState({}); // 브랜드 편집 모드
+ const [editingRequest, setEditingRequest] = useState({}); // 추가요구사항 편집 모드
+
+ // 컴포넌트 마운트 시 저장된 데이터 로드
+ React.useEffect(() => {
+ const loadSavedData = () => {
+ const savedBrandsData = {};
+ const savedRequestsData = {};
+
+ materials.forEach(material => {
+ // 백엔드에서 가져온 데이터가 있으면 저장된 상태로 설정
+ if (material.brand && material.brand.trim()) {
+ savedBrandsData[material.id] = material.brand.trim();
+ }
+ if (material.user_requirement && material.user_requirement.trim()) {
+ savedRequestsData[material.id] = material.user_requirement.trim();
+ }
+ });
+
+ setSavedBrands(savedBrandsData);
+ setSavedRequests(savedRequestsData);
+ };
+
+ if (materials && materials.length > 0) {
+ loadSavedData();
+ } else {
+ setSavedBrands({});
+ setSavedRequests({});
+ }
+ }, [materials]);
+
+ // 브랜드 저장 함수
+ const handleSaveBrand = async (materialId, brand) => {
+ if (!brand.trim()) return;
+
+ setSavingBrand(prev => ({ ...prev, [materialId]: true }));
+ try {
+ await api.patch(`/materials/${materialId}/brand`, { brand: brand.trim() });
+ setSavedBrands(prev => ({ ...prev, [materialId]: brand.trim() }));
+ setEditingBrand(prev => ({ ...prev, [materialId]: false }));
+ setBrandInputs(prev => ({ ...prev, [materialId]: '' }));
+
+ if (updateMaterial) {
+ updateMaterial(materialId, { brand: brand.trim() });
+ }
+ } catch (error) {
+ console.error('브랜드 저장 실패:', error);
+ alert('브랜드 저장에 실패했습니다.');
+ } finally {
+ setSavingBrand(prev => ({ ...prev, [materialId]: false }));
+ }
+ };
+
+ // 브랜드 편집 시작
+ const handleEditBrand = (materialId, currentBrand) => {
+ setEditingBrand(prev => ({ ...prev, [materialId]: true }));
+ setBrandInputs(prev => ({ ...prev, [materialId]: currentBrand || '' }));
+ };
+
+ // 추가요구사항 저장 함수
+ const handleSaveRequest = async (materialId, request) => {
+ setSavingRequest(prev => ({ ...prev, [materialId]: true }));
+ try {
+ await api.patch(`/materials/${materialId}/user-requirement`, {
+ user_requirement: request.trim()
+ });
+ setSavedRequests(prev => ({ ...prev, [materialId]: request.trim() }));
+ setEditingRequest(prev => ({ ...prev, [materialId]: false }));
+ setUserRequirements(prev => ({ ...prev, [materialId]: '' }));
+
+ if (updateMaterial) {
+ updateMaterial(materialId, { user_requirement: request.trim() });
+ }
+ } catch (error) {
+ console.error('추가요구사항 저장 실패:', error);
+ alert('추가요구사항 저장에 실패했습니다.');
+ } finally {
+ setSavingRequest(prev => ({ ...prev, [materialId]: false }));
+ }
+ };
+
+ // 추가요구사항 편집 시작
+ const handleEditRequest = (materialId, currentRequest) => {
+ setEditingRequest(prev => ({ ...prev, [materialId]: true }));
+ setUserRequirements(prev => ({ ...prev, [materialId]: currentRequest || '' }));
+ };
// 파이프 구매 수량 계산 (백엔드 그룹화 데이터 활용)
const calculatePipePurchase = (material) => {
@@ -96,6 +188,7 @@ const PipeMaterialsView = ({
};
};
+
// 정렬 처리
const handleSort = (key) => {
let direction = 'asc';
@@ -175,7 +268,7 @@ const PipeMaterialsView = ({
// 사용자 요구사항 포함
const dataWithRequirements = selectedMaterialsData.map(material => ({
...material,
- user_requirement: userRequirements[material.id] || ''
+ user_requirement: savedRequests[material.id] || userRequirements[material.id] || ''
}));
try {
@@ -397,7 +490,7 @@ const PipeMaterialsView = ({
{/* 헤더 */}
{info.unit}
-
-
setUserRequirements({
- ...userRequirements,
- [material.id]: e.target.value
- })}
- placeholder="Enter additional request..."
- style={{
- width: '100%',
- padding: '6px 8px',
- border: '1px solid #d1d5db',
- borderRadius: '4px',
- fontSize: '12px'
- }}
- />
+
+ {!editingRequest[material.id] && savedRequests[material.id] ? (
+ // 저장된 상태 - 요구사항 표시 + 수정 버튼
+ <>
+
+ {savedRequests[material.id]}
+
+
+ >
+ ) : (
+ // 편집 상태 - 입력 필드 + 저장 버튼
+ <>
+
setUserRequirements({
+ ...userRequirements,
+ [material.id]: e.target.value
+ })}
+ placeholder="Enter additional request..."
+ disabled={isPurchased}
+ style={{
+ flex: 1,
+ padding: '6px',
+ border: '1px solid #d1d5db',
+ borderRadius: '4px',
+ fontSize: '11px',
+ opacity: isPurchased ? 0.5 : 1,
+ cursor: isPurchased ? 'not-allowed' : 'text'
+ }}
+ />
+
+ >
+ )}
);
diff --git a/frontend/src/components/bom/materials/SupportMaterialsView.jsx b/frontend/src/components/bom/materials/SupportMaterialsView.jsx
index 32352ea..aef43e3 100644
--- a/frontend/src/components/bom/materials/SupportMaterialsView.jsx
+++ b/frontend/src/components/bom/materials/SupportMaterialsView.jsx
@@ -11,6 +11,7 @@ const SupportMaterialsView = ({
setUserRequirements,
purchasedMaterials,
onPurchasedMaterialsUpdate,
+ updateMaterial, // 자재 업데이트 함수
jobNo,
fileId,
user
@@ -18,6 +19,58 @@ const SupportMaterialsView = ({
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' });
const [columnFilters, setColumnFilters] = useState({});
const [showFilterDropdown, setShowFilterDropdown] = useState(null);
+ const [savedRequests, setSavedRequests] = useState({}); // 저장된 추가요구사항 상태
+ const [editingRequest, setEditingRequest] = useState({}); // 추가요구사항 편집 모드
+ const [savingRequest, setSavingRequest] = useState({}); // 추가요구사항 저장 중 상태
+
+ // 컴포넌트 마운트 시 저장된 데이터 로드
+ React.useEffect(() => {
+ const loadSavedData = () => {
+ const savedRequestsData = {};
+
+ materials.forEach(material => {
+ if (material.user_requirement && material.user_requirement.trim()) {
+ savedRequestsData[material.id] = material.user_requirement.trim();
+ }
+ });
+
+ setSavedRequests(savedRequestsData);
+ };
+
+ if (materials && materials.length > 0) {
+ loadSavedData();
+ } else {
+ setSavedRequests({});
+ }
+ }, [materials]);
+
+ // 추가요구사항 저장 함수
+ const handleSaveRequest = async (materialId, request) => {
+ setSavingRequest(prev => ({ ...prev, [materialId]: true }));
+ try {
+ await api.patch(`/materials/${materialId}/user-requirement`, {
+ user_requirement: request.trim()
+ });
+ setSavedRequests(prev => ({ ...prev, [materialId]: request.trim() }));
+ setEditingRequest(prev => ({ ...prev, [materialId]: false }));
+ setUserRequirements(prev => ({ ...prev, [materialId]: '' }));
+
+ if (updateMaterial) {
+ updateMaterial(materialId, { user_requirement: request.trim() });
+ }
+ } catch (error) {
+ console.error('추가요구사항 저장 실패:', error);
+ alert('추가요구사항 저장에 실패했습니다.');
+ } finally {
+ setSavingRequest(prev => ({ ...prev, [materialId]: false }));
+ }
+ };
+
+ // 추가요구사항 편집 시작
+ const handleEditRequest = (materialId, currentRequest) => {
+ setEditingRequest(prev => ({ ...prev, [materialId]: true }));
+ setUserRequirements(prev => ({ ...prev, [materialId]: currentRequest || '' }));
+ };
const parseSupportInfo = (material) => {
const desc = material.original_description || '';
@@ -29,7 +82,13 @@ const SupportMaterialsView = ({
if (descUpper.includes('URETHANE') || descUpper.includes('BLOCK SHOE') || descUpper.includes('우레탄')) {
supportType = 'URETHANE BLOCK SHOE';
} else if (descUpper.includes('CLAMP') || descUpper.includes('클램프')) {
- supportType = 'CLAMP';
+ // 클램프 타입 상세 분류 (CL-1, CL-2, CL-3 등)
+ const clampMatch = desc.match(/CL[-\s]*(\d+)/i);
+ if (clampMatch) {
+ supportType = `CLAMP CL-${clampMatch[1]}`;
+ } else {
+ supportType = 'CLAMP CL-1'; // 기본값
+ }
} else if (descUpper.includes('HANGER') || descUpper.includes('행거')) {
supportType = 'HANGER';
} else if (descUpper.includes('SPRING') || descUpper.includes('스프링')) {
@@ -86,6 +145,9 @@ const SupportMaterialsView = ({
if (!consolidated[key]) {
consolidated[key] = {
...material,
+ // Material Grade 정보를 parsedInfo에서 가져와서 설정
+ material_grade: info.grade,
+ full_material_grade: info.grade,
consolidatedQuantity: info.originalQuantity,
consolidatedIds: [material.id],
parsedInfo: info
@@ -208,8 +270,13 @@ const SupportMaterialsView = ({
// 엑셀 내보내기
const handleExportToExcel = async () => {
- const selectedMaterialsData = materials.filter(m => selectedMaterials.has(m.id));
- if (selectedMaterialsData.length === 0) {
+ // 선택된 합산 자료 가져오기
+ const filteredMaterials = getFilteredAndSortedMaterials();
+ const selectedConsolidatedMaterials = filteredMaterials.filter(consolidatedMaterial =>
+ consolidatedMaterial.consolidatedIds.some(id => selectedMaterials.has(id))
+ );
+
+ if (selectedConsolidatedMaterials.length === 0) {
alert('내보낼 자재를 선택해주세요.');
return;
}
@@ -217,9 +284,13 @@ const SupportMaterialsView = ({
const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, '-');
const excelFileName = `SUPPORT_Materials_${timestamp}.xlsx`;
- const dataWithRequirements = selectedMaterialsData.map(material => ({
- ...material,
- user_requirement: userRequirements[material.id] || ''
+ // 합산된 자료를 엑셀 형태로 변환
+ const dataWithRequirements = selectedConsolidatedMaterials.map(consolidatedMaterial => ({
+ ...consolidatedMaterial,
+ // 합산된 수량으로 덮어쓰기
+ quantity: consolidatedMaterial.consolidatedQuantity,
+ // 사용자 요구사항은 대표 ID 기준 (저장된 데이터 우선)
+ user_requirement: savedRequests[consolidatedMaterial.id] || userRequirements[consolidatedMaterial.id] || ''
}));
try {
@@ -235,7 +306,7 @@ const SupportMaterialsView = ({
console.log('✅ 엑셀 Blob 생성 완료:', excelBlob.size, 'bytes');
// 2. 구매신청 생성
- const allMaterialIds = selectedMaterialsData.map(m => m.id);
+ const allMaterialIds = selectedConsolidatedMaterials.flatMap(cm => cm.consolidatedIds);
const response = await api.post('/purchase-request/create', {
file_id: fileId,
job_no: jobNo,
@@ -248,7 +319,7 @@ const SupportMaterialsView = ({
size: m.size_inch || m.size_spec,
schedule: m.schedule,
material_grade: m.material_grade || m.full_material_grade,
- quantity: m.quantity,
+ quantity: m.quantity, // 이미 합산된 수량
unit: m.unit,
user_requirement: userRequirements[m.id] || ''
}))
@@ -374,7 +445,7 @@ const SupportMaterialsView = ({
{/* 헤더 */}
{info.size}
{info.grade}
{info.userRequirements}
-
-
setUserRequirements({
- ...userRequirements,
- [consolidatedMaterial.id]: e.target.value
- })}
- placeholder="Enter additional request..."
- style={{
- width: '100%',
- padding: '8px',
- border: '1px solid #d1d5db',
- borderRadius: '4px',
- fontSize: '12px'
- }}
- />
+
+ {!editingRequest[consolidatedMaterial.id] && savedRequests[consolidatedMaterial.id] ? (
+ // 저장된 상태 - 요구사항 표시 + 수정 버튼
+ <>
+
+ {savedRequests[consolidatedMaterial.id]}
+
+
+ >
+ ) : (
+ // 편집 상태 - 입력 필드 + 저장 버튼
+ <>
+
setUserRequirements({
+ ...userRequirements,
+ [consolidatedMaterial.id]: e.target.value
+ })}
+ placeholder="Enter additional request..."
+ disabled={hasAnyPurchased}
+ style={{
+ flex: 1,
+ padding: '8px',
+ border: '1px solid #d1d5db',
+ borderRadius: '4px',
+ fontSize: '12px',
+ opacity: hasAnyPurchased ? 0.5 : 1,
+ cursor: hasAnyPurchased ? 'not-allowed' : 'text'
+ }}
+ />
+
+ >
+ )}
{info.purchaseQuantity}
diff --git a/frontend/src/components/bom/materials/ValveMaterialsView.jsx b/frontend/src/components/bom/materials/ValveMaterialsView.jsx
index 699ba66..a4c61aa 100644
--- a/frontend/src/components/bom/materials/ValveMaterialsView.jsx
+++ b/frontend/src/components/bom/materials/ValveMaterialsView.jsx
@@ -11,6 +11,7 @@ const ValveMaterialsView = ({
setUserRequirements,
purchasedMaterials,
onPurchasedMaterialsUpdate,
+ updateMaterial, // 자재 업데이트 함수
fileId,
jobNo,
user
@@ -18,58 +19,154 @@ const ValveMaterialsView = ({
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' });
const [columnFilters, setColumnFilters] = useState({});
const [showFilterDropdown, setShowFilterDropdown] = useState(null);
+ const [brandInputs, setBrandInputs] = useState({}); // 브랜드 입력 상태
+ const [savingBrand, setSavingBrand] = useState({}); // 브랜드 저장 중 상태
+ const [savingRequest, setSavingRequest] = useState({}); // 추가요구사항 저장 중 상태
+ const [savedBrands, setSavedBrands] = useState({}); // 저장된 브랜드 상태
+ const [savedRequests, setSavedRequests] = useState({}); // 저장된 추가요구사항 상태
+ const [editingBrand, setEditingBrand] = useState({}); // 브랜드 편집 모드
+ const [editingRequest, setEditingRequest] = useState({}); // 추가요구사항 편집 모드
+
+ // 컴포넌트 마운트 시 저장된 데이터 로드
+ React.useEffect(() => {
+ const loadSavedData = () => {
+ const savedBrandsData = {};
+ const savedRequestsData = {};
+
+ console.log('🔍 ValveMaterialsView useEffect 트리거됨:', materials.length, '개 자재');
+ console.log('🔍 현재 materials 배열:', materials.map(m => ({id: m.id, brand: m.brand, user_requirement: m.user_requirement})));
+
+ materials.forEach(material => {
+ // 백엔드에서 가져온 데이터가 있으면 저장된 상태로 설정
+ if (material.brand && material.brand.trim()) {
+ savedBrandsData[material.id] = material.brand.trim();
+ console.log('✅ 브랜드 로드됨:', material.id, '→', material.brand);
+ }
+ if (material.user_requirement && material.user_requirement.trim()) {
+ savedRequestsData[material.id] = material.user_requirement.trim();
+ console.log('✅ 요구사항 로드됨:', material.id, '→', material.user_requirement);
+ }
+ });
+
+ console.log('💾 최종 저장된 브랜드:', savedBrandsData);
+ console.log('💾 최종 저장된 요구사항:', savedRequestsData);
+
+ // 상태 업데이트를 즉시 반영하기 위해 setTimeout 사용
+ setSavedBrands(savedBrandsData);
+ setSavedRequests(savedRequestsData);
+
+ // 상태 업데이트 후 강제 리렌더링 확인
+ setTimeout(() => {
+ console.log('🔄 상태 업데이트 후 확인 - savedBrands:', savedBrandsData);
+ }, 100);
+ };
+
+ console.log('🔄 ValveMaterialsView useEffect 실행 - materials 길이:', materials?.length || 0);
+
+ if (materials && materials.length > 0) {
+ loadSavedData();
+ } else {
+ console.log('⚠️ materials가 비어있거나 undefined');
+ // 빈 상태로 초기화
+ setSavedBrands({});
+ setSavedRequests({});
+ }
+ }, [materials]);
const parseValveInfo = (material) => {
const valveDetails = material.valve_details || {};
const description = material.original_description || '';
+ const descUpper = description.toUpperCase();
- // 밸브 타입 파싱 (GATE, BALL, CHECK, GLOBE 등) - 기존 NewMaterialsPage와 동일
- let valveType = valveDetails.valve_type || '';
- if (!valveType && description) {
- if (description.includes('GATE')) valveType = 'GATE';
- else if (description.includes('BALL')) valveType = 'BALL';
- else if (description.includes('CHECK')) valveType = 'CHECK';
- else if (description.includes('GLOBE')) valveType = 'GLOBE';
- else if (description.includes('BUTTERFLY')) valveType = 'BUTTERFLY';
- else if (description.includes('NEEDLE')) valveType = 'NEEDLE';
- else if (description.includes('RELIEF')) valveType = 'RELIEF';
+ // 1. 벨브 타입 파싱 (한글명으로 표시)
+ let valveType = '';
+ if (descUpper.includes('SIGHT GLASS') || descUpper.includes('사이트글라스')) {
+ valveType = 'SIGHT GLASS';
+ } else if (descUpper.includes('STRAINER') || descUpper.includes('스트레이너')) {
+ valveType = 'STRAINER';
+ } else if (descUpper.includes('GATE') || descUpper.includes('게이트')) {
+ valveType = 'GATE VALVE';
+ } else if (descUpper.includes('BALL') || descUpper.includes('볼')) {
+ valveType = 'BALL VALVE';
+ } else if (descUpper.includes('CHECK') || descUpper.includes('체크')) {
+ valveType = 'CHECK VALVE';
+ } else if (descUpper.includes('GLOBE') || descUpper.includes('글로브')) {
+ valveType = 'GLOBE VALVE';
+ } else if (descUpper.includes('BUTTERFLY') || descUpper.includes('버터플라이')) {
+ valveType = 'BUTTERFLY VALVE';
+ } else if (descUpper.includes('NEEDLE') || descUpper.includes('니들')) {
+ valveType = 'NEEDLE VALVE';
+ } else if (descUpper.includes('RELIEF') || descUpper.includes('릴리프')) {
+ valveType = 'RELIEF VALVE';
+ } else {
+ valveType = 'VALVE';
}
- // 연결 방식 파싱 (FLG, SW, THRD 등) - 기존 NewMaterialsPage와 동일
+ // 2. 사이즈 정보
+ const size = material.main_nom || material.size_inch || material.size_spec || '-';
+
+ // 3. 압력 등급
+ const pressure = material.pressure_rating ||
+ (descUpper.match(/(\d+)\s*LB/) ? descUpper.match(/(\d+)\s*LB/)[0] : '-');
+
+ // 4. 브랜드 (사용자 입력 가능)
+ const brand = '-'; // 기본값, 사용자가 입력할 수 있도록
+
+ // 5. 추가 정보 추출 (3-WAY, DOUL PLATE, DOUBLE DISC 등)
+ let additionalInfo = '';
+ const additionalPatterns = [
+ '3-WAY', '3WAY', 'THREE WAY',
+ 'DOUL PLATE', 'DOUBLE PLATE', 'DUAL PLATE',
+ 'DOUBLE DISC', 'DUAL DISC',
+ 'SWING', 'LIFT', 'TILTING',
+ 'WAFER', 'LUG', 'FLANGED',
+ 'FULL BORE', 'REDUCED BORE',
+ 'FIRE SAFE', 'ANTI STATIC'
+ ];
+
+ for (const pattern of additionalPatterns) {
+ if (descUpper.includes(pattern)) {
+ if (additionalInfo) {
+ additionalInfo += ', ';
+ }
+ additionalInfo += pattern;
+ }
+ }
+
+ if (!additionalInfo) {
+ additionalInfo = '-';
+ }
+
+ // 6. 연결 방식 (투입구/Connection Type)
let connectionType = '';
- if (description.includes('FLG')) {
- connectionType = 'FLG';
- } else if (description.includes('SW X THRD')) {
+ if (descUpper.includes('SW X THRD') || descUpper.includes('SW×THRD')) {
connectionType = 'SW×THRD';
- } else if (description.includes('SW')) {
+ } else if (descUpper.includes('FLG') || descUpper.includes('FLANGE')) {
+ connectionType = 'FLG';
+ } else if (descUpper.includes('SW') || descUpper.includes('SOCKET')) {
connectionType = 'SW';
- } else if (description.includes('THRD')) {
+ } else if (descUpper.includes('THRD') || descUpper.includes('THREAD')) {
connectionType = 'THRD';
- } else if (description.includes('BW')) {
+ } else if (descUpper.includes('BW') || descUpper.includes('BUTT WELD')) {
connectionType = 'BW';
+ } else {
+ connectionType = '-';
}
- // 압력 등급 파싱
- let pressure = '-';
- const pressureMatch = description.match(/(\d+)LB/i);
- if (pressureMatch) {
- pressure = `${pressureMatch[1]}LB`;
- }
-
- // 스케줄은 밸브에는 일반적으로 없음 (기본값)
- let schedule = '-';
+ // 7. 구매 수량 계산 (기본 수량 그대로)
+ const qty = Math.round(material.quantity || 0);
+ const purchaseQuantity = `${qty} EA`;
return {
- type: 'VALVE',
- subtype: `${valveType} ${connectionType}`.trim() || 'VALVE', // 타입과 연결방식 결합
- valveType: valveType,
- connectionType: connectionType,
- size: material.size_spec || '-',
+ type: valveType, // 벨브 종류만 (GATE VALVE, BALL VALVE 등)
+ size: size,
pressure: pressure,
- schedule: schedule,
- grade: material.material_grade || '-',
- quantity: Math.round(material.quantity || 0),
- unit: '개',
+ brand: brand, // 브랜드 (사용자 입력 가능)
+ additionalInfo: additionalInfo, // 추가 정보 (3-WAY, DOUL PLATE 등)
+ connection: connectionType, // 투입구/연결방식 (SW, FLG 등)
+ additionalRequest: '-', // 추가 요구사항 (기존 User Requirement)
+ purchaseQuantity: purchaseQuantity,
+ originalQuantity: qty,
isValve: true
};
};
@@ -130,6 +227,68 @@ const ValveMaterialsView = ({
};
// 전체 선택/해제 (구매신청된 자재 제외)
+ // 브랜드 저장 함수
+ const handleSaveBrand = async (materialId, brand) => {
+ if (!brand.trim()) return;
+
+ setSavingBrand(prev => ({ ...prev, [materialId]: true }));
+ try {
+ await api.patch(`/materials/${materialId}/brand`, { brand: brand.trim() });
+ // 성공 시 저장된 상태로 전환
+ setSavedBrands(prev => ({ ...prev, [materialId]: brand.trim() }));
+ setEditingBrand(prev => ({ ...prev, [materialId]: false }));
+ setBrandInputs(prev => ({ ...prev, [materialId]: '' })); // 입력 필드 초기화
+
+ // materials 배열도 업데이트 (카테고리 변경 시 데이터 유지를 위해)
+ if (updateMaterial) {
+ updateMaterial(materialId, { brand: brand.trim() });
+ console.log('✅ materials 배열 업데이트 완료:', materialId, '→', brand.trim());
+ }
+ } catch (error) {
+ console.error('브랜드 저장 실패:', error);
+ alert('브랜드 저장에 실패했습니다.');
+ } finally {
+ setSavingBrand(prev => ({ ...prev, [materialId]: false }));
+ }
+ };
+
+ // 브랜드 편집 시작
+ const handleEditBrand = (materialId, currentBrand) => {
+ setEditingBrand(prev => ({ ...prev, [materialId]: true }));
+ setBrandInputs(prev => ({ ...prev, [materialId]: currentBrand || '' }));
+ };
+
+ // 추가요구사항 저장 함수
+ const handleSaveRequest = async (materialId, request) => {
+ setSavingRequest(prev => ({ ...prev, [materialId]: true }));
+ try {
+ await api.patch(`/materials/${materialId}/user-requirement`, {
+ user_requirement: request.trim()
+ });
+ // 성공 시 저장된 상태로 전환
+ setSavedRequests(prev => ({ ...prev, [materialId]: request.trim() }));
+ setEditingRequest(prev => ({ ...prev, [materialId]: false }));
+ setUserRequirements(prev => ({ ...prev, [materialId]: '' })); // 입력 필드 초기화
+
+ // materials 배열도 업데이트 (카테고리 변경 시 데이터 유지를 위해)
+ if (updateMaterial) {
+ updateMaterial(materialId, { user_requirement: request.trim() });
+ console.log('✅ materials 배열 업데이트 완료:', materialId, '→', request.trim());
+ }
+ } catch (error) {
+ console.error('추가요구사항 저장 실패:', error);
+ alert('추가요구사항 저장에 실패했습니다.');
+ } finally {
+ setSavingRequest(prev => ({ ...prev, [materialId]: false }));
+ }
+ };
+
+ // 추가요구사항 편집 시작
+ const handleEditRequest = (materialId, currentRequest) => {
+ setEditingRequest(prev => ({ ...prev, [materialId]: true }));
+ setUserRequirements(prev => ({ ...prev, [materialId]: currentRequest || '' }));
+ };
+
const handleSelectAll = () => {
const filteredMaterials = getFilteredAndSortedMaterials();
const selectableMaterials = filteredMaterials.filter(m => !purchasedMaterials.has(m.id));
@@ -169,7 +328,7 @@ const ValveMaterialsView = ({
const dataWithRequirements = selectedMaterialsData.map(material => ({
...material,
- user_requirement: userRequirements[material.id] || ''
+ user_requirement: savedRequests[material.id] || userRequirements[material.id] || ''
}));
try {
@@ -289,45 +448,48 @@ const ValveMaterialsView = ({
>
{selectedMaterials.size === filteredMaterials.length ? 'Deselect All' : 'Select All'}
-
-
-