전체적인 수당 시뮬레이터 로직을 하드코딩된 방식에서 salesConfig 데이터 기반의 동적 방식으로 전면 개편
This commit is contained in:
@@ -5094,11 +5094,8 @@
|
||||
const [editingItem, setEditingItem] = useState(null);
|
||||
const isOperator = selectedRole === '운영자';
|
||||
|
||||
// 1차 선택: 선택모델, 공사관리, 공정/정부지원사업
|
||||
const [selectedSelectModels, setSelectedSelectModels] = useState(false);
|
||||
const [selectedConstruction, setSelectedConstruction] = useState(false);
|
||||
const [selectedProcessGov, setSelectedProcessGov] = useState(false);
|
||||
|
||||
// 1차 선택: 동적 패키지 관리
|
||||
const [selectedPackageIds, setSelectedPackageIds] = useState([]);
|
||||
// 2차 선택: 선택모델의 세부 모델들
|
||||
const [selectedModels, setSelectedModels] = useState([]);
|
||||
|
||||
@@ -5123,9 +5120,6 @@
|
||||
}, []);
|
||||
|
||||
const packageTypes = salesConfig.package_types || [];
|
||||
const selectModelsPackage = packageTypes.find(p => p.id === 'select_models');
|
||||
const constructionPackage = packageTypes.find(p => p.id === 'construction_management');
|
||||
const processGovPackage = packageTypes.find(p => p.id === 'process_government');
|
||||
|
||||
// DB 가격 정보와 기본 설정 병합
|
||||
const getItemPrice = (itemType, itemId, defaultJoinFee, defaultSubFee) => {
|
||||
@@ -5148,44 +5142,36 @@
|
||||
let totalManagerCommission = 0;
|
||||
let totalEducatorCommission = 0;
|
||||
|
||||
// 선택모델의 세부 모델들 합산
|
||||
if (selectedSelectModels && selectModelsPackage) {
|
||||
selectedModels.forEach(modelId => {
|
||||
const model = selectModelsPackage.models.find(m => m.id === modelId);
|
||||
if (model) {
|
||||
const price = getItemPrice('model', modelId, model.join_fee, model.subscription_fee);
|
||||
packageTypes.forEach(pkg => {
|
||||
if (pkg.id === 'select_models') {
|
||||
if (selectedPackageIds.includes(pkg.id)) {
|
||||
(pkg.models || []).forEach(modelIdOrObj => {
|
||||
// package_types의 models가 ID 배열일수도, 객체 배열일수도 있음 (company_info.php에선 객체 배열)
|
||||
const model = typeof modelIdOrObj === 'string'
|
||||
? pkg.models.find(m => m.id === modelIdOrObj)
|
||||
: modelIdOrObj;
|
||||
|
||||
if (model && selectedModels.includes(model.id)) {
|
||||
const price = getItemPrice('model', model.id, model.join_fee, model.subscription_fee);
|
||||
totalJoinFee += price.join_fee || 0;
|
||||
|
||||
const rates = model.commission_rates || { seller: { join: 0.20 }, manager: { join: 0.05 }, educator: { join: 0 } };
|
||||
totalSellerCommission += (price.join_fee * (rates.seller?.join || 0.20));
|
||||
totalManagerCommission += (price.join_fee * (rates.manager?.join || 0.05));
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (selectedPackageIds.includes(pkg.id)) {
|
||||
const price = getItemPrice('package', pkg.id, pkg.join_fee, pkg.subscription_fee);
|
||||
totalJoinFee += price.join_fee || 0;
|
||||
|
||||
// 가입비에 대한 수당만 계산: 판매자 20%, 관리자 5%, 메뉴제작 협업수당 별도
|
||||
const rates = model.commission_rates || { seller: { join: 0.20 }, manager: { join: 0.05 }, educator: { join: 0 } };
|
||||
totalSellerCommission += (price.join_fee * (rates.seller.join || 0.20));
|
||||
totalManagerCommission += (price.join_fee * (rates.manager.join || 0.05));
|
||||
totalEducatorCommission += 0; // 운영팀 별도 산정
|
||||
const rates = pkg.commission_rates || { seller: { join: 0.20 }, manager: { join: 0.05 }, educator: { join: 0 } };
|
||||
totalSellerCommission += (price.join_fee * (rates.seller?.join || 0.20));
|
||||
totalManagerCommission += (price.join_fee * (rates.manager?.join || 0.05));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 공사관리 패키지
|
||||
if (selectedConstruction && constructionPackage) {
|
||||
const price = getItemPrice('package', 'construction_management', constructionPackage.join_fee, constructionPackage.subscription_fee);
|
||||
totalJoinFee += price.join_fee || 0;
|
||||
|
||||
const rates = constructionPackage.commission_rates || { seller: { join: 0.20 }, manager: { join: 0.05 }, educator: { join: 0 } };
|
||||
totalSellerCommission += (price.join_fee * (rates.seller.join || 0.20));
|
||||
totalManagerCommission += (price.join_fee * (rates.manager.join || 0.05));
|
||||
totalEducatorCommission += 0;
|
||||
}
|
||||
|
||||
// 공정/정부지원사업 패키지
|
||||
if (selectedProcessGov && processGovPackage) {
|
||||
const price = getItemPrice('package', 'process_government', processGovPackage.join_fee, processGovPackage.subscription_fee);
|
||||
totalJoinFee += price.join_fee || 0;
|
||||
|
||||
const rates = processGovPackage.commission_rates || { seller: { join: 0.20 }, manager: { join: 0.05 }, educator: { join: 0 } };
|
||||
totalSellerCommission += (price.join_fee * (rates.seller.join || 0.20));
|
||||
totalManagerCommission += (price.join_fee * (rates.manager.join || 0.05));
|
||||
totalEducatorCommission += 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const totalCommission = totalSellerCommission + totalManagerCommission + totalEducatorCommission;
|
||||
const totalRevenue = totalJoinFee; // 구독료 제거
|
||||
@@ -5201,6 +5187,18 @@
|
||||
);
|
||||
};
|
||||
|
||||
const handlePackageToggle = (pkgId) => {
|
||||
setSelectedPackageIds(prev => {
|
||||
const next = prev.includes(pkgId)
|
||||
? prev.filter(id => id !== pkgId)
|
||||
: [...prev, pkgId];
|
||||
if (pkgId === 'select_models' && prev.includes(pkgId)) {
|
||||
setSelectedModels([]);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
};
|
||||
|
||||
const handleEditPrice = (itemType, itemId, itemName, subName, defaultJoinFee, defaultSubFee) => {
|
||||
const key = `${itemType}_${itemId}`;
|
||||
const currentPrice = pricingData[key] || { join_fee: defaultJoinFee, subscription_fee: defaultSubFee };
|
||||
@@ -5279,129 +5277,40 @@
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">패키지 선택</label>
|
||||
<div className="space-y-3">
|
||||
{/* 선택모델 */}
|
||||
<div className="flex items-start gap-3 p-4 border border-slate-200 rounded-lg hover:border-blue-300 transition-colors">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="select_models"
|
||||
checked={selectedSelectModels}
|
||||
onChange={(e) => {
|
||||
setSelectedSelectModels(e.target.checked);
|
||||
if (!e.target.checked) {
|
||||
setSelectedModels([]);
|
||||
}
|
||||
}}
|
||||
className="mt-1 w-4 h-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
<label htmlFor="select_models" className="flex-1 cursor-pointer">
|
||||
<div className="font-medium text-slate-900">선택모델</div>
|
||||
<div className="text-xs text-slate-500 mt-1">여러 모델을 선택할 수 있습니다</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{/* 공사관리 */}
|
||||
{constructionPackage && (() => {
|
||||
const price = getItemPrice('package', 'construction_management', constructionPackage.join_fee, constructionPackage.subscription_fee);
|
||||
return (
|
||||
<div className="flex items-start gap-3 p-4 border border-slate-200 rounded-lg hover:border-blue-300 transition-colors">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="construction_management"
|
||||
checked={selectedConstruction}
|
||||
onChange={(e) => setSelectedConstruction(e.target.checked)}
|
||||
className="mt-1 w-4 h-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
<label htmlFor="construction_management" className="flex-1 cursor-pointer">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-slate-900">공사관리</div>
|
||||
<div className="text-xs text-slate-500 mt-1">패키지</div>
|
||||
<div className="text-xs text-blue-600 mt-1">
|
||||
가입비: {formatCurrency(price.join_fee)} / 월 구독료: {formatCurrency(price.subscription_fee)}
|
||||
</div>
|
||||
</div>
|
||||
{isOperator && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleEditPrice('package', 'construction_management', '공사관리', '패키지', constructionPackage.join_fee, constructionPackage.subscription_fee);
|
||||
}}
|
||||
className="p-1.5 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
||||
title="가격 설정"
|
||||
>
|
||||
<LucideIcon name="settings" className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* 공정/정부지원사업 */}
|
||||
{processGovPackage && (() => {
|
||||
const price = getItemPrice('package', 'process_government', processGovPackage.join_fee, processGovPackage.subscription_fee);
|
||||
return (
|
||||
<div className="flex items-start gap-3 p-4 border border-slate-200 rounded-lg hover:border-blue-300 transition-colors">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="process_government"
|
||||
checked={selectedProcessGov}
|
||||
onChange={(e) => setSelectedProcessGov(e.target.checked)}
|
||||
className="mt-1 w-4 h-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
<label htmlFor="process_government" className="flex-1 cursor-pointer">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-slate-900">공정/정부지원사업</div>
|
||||
<div className="text-xs text-slate-500 mt-1">패키지</div>
|
||||
<div className="text-xs text-blue-600 mt-1">
|
||||
가입비: {formatCurrency(price.join_fee)} / 월 구독료: {formatCurrency(price.subscription_fee)}
|
||||
</div>
|
||||
</div>
|
||||
{isOperator && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleEditPrice('package', 'process_government', '공정/정부지원사업', '패키지', processGovPackage.join_fee, processGovPackage.subscription_fee);
|
||||
}}
|
||||
className="p-1.5 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
||||
title="가격 설정"
|
||||
>
|
||||
<LucideIcon name="settings" className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 2차 선택: 선택모델의 세부 모델들 */}
|
||||
{selectedSelectModels && selectModelsPackage && (
|
||||
<div className="border-t border-slate-200 pt-6">
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">선택모델 세부 항목</label>
|
||||
<div className="space-y-2 max-h-60 overflow-y-auto">
|
||||
{selectModelsPackage.models.map(model => {
|
||||
const price = getItemPrice('model', model.id, model.join_fee, model.subscription_fee);
|
||||
<div className="space-y-3">
|
||||
{packageTypes.map(pkg => {
|
||||
if (pkg.id === 'select_models') {
|
||||
return (
|
||||
<div key={model.id} className="flex items-start gap-3 p-3 border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors">
|
||||
<div key={pkg.id} className="flex items-start gap-3 p-4 border border-slate-200 rounded-lg hover:border-blue-300 transition-colors">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={model.id}
|
||||
checked={selectedModels.includes(model.id)}
|
||||
onChange={() => handleModelToggle(model.id)}
|
||||
id={pkg.id}
|
||||
checked={selectedPackageIds.includes(pkg.id)}
|
||||
onChange={() => handlePackageToggle(pkg.id)}
|
||||
className="mt-1 w-4 h-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
<label htmlFor={model.id} className="flex-1 cursor-pointer">
|
||||
<label htmlFor={pkg.id} className="flex-1 cursor-pointer">
|
||||
<div className="font-medium text-slate-900">{pkg.name}</div>
|
||||
<div className="text-xs text-slate-500 mt-1">여러 모델을 선택할 수 있습니다</div>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
const price = getItemPrice('package', pkg.id, pkg.join_fee, pkg.subscription_fee);
|
||||
return (
|
||||
<div key={pkg.id} className="flex items-start gap-3 p-4 border border-slate-200 rounded-lg hover:border-blue-300 transition-colors">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={pkg.id}
|
||||
checked={selectedPackageIds.includes(pkg.id)}
|
||||
onChange={() => handlePackageToggle(pkg.id)}
|
||||
className="mt-1 w-4 h-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
<label htmlFor={pkg.id} className="flex-1 cursor-pointer">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-slate-900 text-sm">{model.name}</div>
|
||||
{model.sub_name && (
|
||||
<div className="text-xs text-slate-500 mt-0.5">{model.sub_name}</div>
|
||||
)}
|
||||
<div className="font-medium text-slate-900">{pkg.name}</div>
|
||||
<div className="text-xs text-slate-500 mt-1">{pkg.sub_name || '패키지'}</div>
|
||||
<div className="text-xs text-blue-600 mt-1">
|
||||
가입비: {formatCurrency(price.join_fee)} / 월 구독료: {formatCurrency(price.subscription_fee)}
|
||||
</div>
|
||||
@@ -5410,9 +5319,9 @@
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleEditPrice('model', model.id, model.name, model.sub_name, model.join_fee, model.subscription_fee);
|
||||
handleEditPrice('package', pkg.id, pkg.name, pkg.sub_name, pkg.join_fee, pkg.subscription_fee);
|
||||
}}
|
||||
className="p-1.5 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors ml-2"
|
||||
className="p-1.5 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors"
|
||||
title="가격 설정"
|
||||
>
|
||||
<LucideIcon name="settings" className="w-4 h-4" />
|
||||
@@ -5422,10 +5331,63 @@
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 2차 선택: 선택모델의 세부 모델들 */}
|
||||
{selectedPackageIds.includes('select_models') && (() => {
|
||||
const selectModelsPkg = packageTypes.find(p => p.id === 'select_models');
|
||||
if (!selectModelsPkg || !selectModelsPkg.models) return null;
|
||||
return (
|
||||
<div className="border-t border-slate-200 pt-6">
|
||||
<label className="block text-sm font-medium text-slate-700 mb-3">선택모델 세부 항목</label>
|
||||
<div className="space-y-2 max-h-60 overflow-y-auto">
|
||||
{selectModelsPkg.models.map(model => {
|
||||
const price = getItemPrice('model', model.id, model.join_fee, model.subscription_fee);
|
||||
return (
|
||||
<div key={model.id} className="flex items-start gap-3 p-3 border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors">
|
||||
<input
|
||||
type="checkbox"
|
||||
id={model.id}
|
||||
checked={selectedModels.includes(model.id)}
|
||||
onChange={() => handleModelToggle(model.id)}
|
||||
className="mt-1 w-4 h-4 text-blue-600 border-slate-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
<label htmlFor={model.id} className="flex-1 cursor-pointer">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<div className="font-medium text-slate-900 text-sm">{model.name}</div>
|
||||
{model.sub_name && (
|
||||
<div className="text-xs text-slate-500 mt-0.5">{model.sub_name}</div>
|
||||
)}
|
||||
<div className="text-xs text-blue-600 mt-1">
|
||||
가입비: {formatCurrency(price.join_fee)} / 월 구독료: {formatCurrency(price.subscription_fee)}
|
||||
</div>
|
||||
</div>
|
||||
{isOperator && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleEditPrice('model', model.id, model.name, model.sub_name, model.join_fee, model.subscription_fee);
|
||||
}}
|
||||
className="p-1.5 text-slate-400 hover:text-blue-600 hover:bg-blue-50 rounded transition-colors ml-2"
|
||||
title="가격 설정"
|
||||
>
|
||||
<LucideIcon name="settings" className="w-4 h-4" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user