diff --git a/resources/views/finance/consulting-fee.blade.php b/resources/views/finance/consulting-fee.blade.php index f2cc902f..57995bb3 100644 --- a/resources/views/finance/consulting-fee.blade.php +++ b/resources/views/finance/consulting-fee.blade.php @@ -9,6 +9,7 @@ @endpush @section('content') +
@endsection @@ -45,13 +46,11 @@ const Users = createIcon('users'); function ConsultingFeeManagement() { - const [fees, setFees] = useState([ - { id: 1, date: '2026-01-21', consultant: '김상담', customer: '(주)제조산업', service: '기술 컨설팅', hours: 8, hourlyRate: 200000, amount: 1600000, status: 'pending', memo: 'MES 도입 상담' }, - { id: 2, date: '2026-01-18', consultant: '박컨설', customer: '(주)스마트팩토리', service: '프로세스 컨설팅', hours: 16, hourlyRate: 250000, amount: 4000000, status: 'paid', memo: '공정 개선' }, - { id: 3, date: '2026-01-15', consultant: '김상담', customer: '(주)디지털제조', service: '기술 컨설팅', hours: 4, hourlyRate: 200000, amount: 800000, status: 'paid', memo: 'ERP 연동 상담' }, - { id: 4, date: '2026-01-10', consultant: '이자문', customer: '(주)AI산업', service: '전략 컨설팅', hours: 24, hourlyRate: 300000, amount: 7200000, status: 'pending', memo: 'DX 전략 수립' }, - { id: 5, date: '2025-12-20', consultant: '박컨설', customer: '(주)테크솔루션', service: '프로세스 컨설팅', hours: 8, hourlyRate: 250000, amount: 2000000, status: 'paid', memo: '' }, - ]); + const [fees, setFees] = useState([]); + const [stats, setStats] = useState({ totalAmount: 0, paidAmount: 0, pendingAmount: 0, totalHours: 0 }); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content'); const [searchTerm, setSearchTerm] = useState(''); const [filterStatus, setFilterStatus] = useState('all'); @@ -89,35 +88,84 @@ function ConsultingFeeManagement() { }; const parseInputCurrency = (value) => String(value).replace(/[^\d]/g, ''); + const fetchData = async () => { + setLoading(true); + try { + const res = await fetch('/finance/consulting-fees/list'); + const data = await res.json(); + if (data.success) { + setFees(data.data); + setStats(data.stats); + } + } catch (err) { + console.error('조회 실패:', err); + } finally { + setLoading(false); + } + }; + useEffect(() => { fetchData(); }, []); + const filteredFees = fees.filter(item => { - const matchesSearch = item.customer.toLowerCase().includes(searchTerm.toLowerCase()) || - item.consultant.toLowerCase().includes(searchTerm.toLowerCase()); + const matchesSearch = (item.customer || '').toLowerCase().includes(searchTerm.toLowerCase()) || + (item.consultant || '').toLowerCase().includes(searchTerm.toLowerCase()); const matchesStatus = filterStatus === 'all' || item.status === filterStatus; const matchesConsultant = filterConsultant === 'all' || item.consultant === filterConsultant; - const matchesDate = item.date >= dateRange.start && item.date <= dateRange.end; + const matchesDate = (item.date || '') >= dateRange.start && (item.date || '') <= dateRange.end; return matchesSearch && matchesStatus && matchesConsultant && matchesDate; }); - const totalAmount = filteredFees.reduce((sum, item) => sum + item.amount, 0); - const paidAmount = filteredFees.filter(i => i.status === 'paid').reduce((sum, item) => sum + item.amount, 0); - const pendingAmount = filteredFees.filter(i => i.status === 'pending').reduce((sum, item) => sum + item.amount, 0); - const totalHours = filteredFees.reduce((sum, item) => sum + item.hours, 0); - const handleAdd = () => { setModalMode('add'); setFormData(initialFormState); setShowModal(true); }; - const handleEdit = (item) => { setModalMode('edit'); setEditingItem(item); setFormData({ ...item }); setShowModal(true); }; - const handleSave = () => { - if (!formData.customer || !formData.hours) { alert('필수 항목을 입력해주세요.'); return; } - const hours = parseInt(formData.hours) || 0; - const hourlyRate = parseInt(formData.hourlyRate) || 0; - const amount = parseInt(formData.amount) || hours * hourlyRate; - if (modalMode === 'add') { - setFees(prev => [{ id: Date.now(), ...formData, hours, hourlyRate, amount }, ...prev]); - } else { - setFees(prev => prev.map(item => item.id === editingItem.id ? { ...item, ...formData, hours, hourlyRate, amount } : item)); - } - setShowModal(false); setEditingItem(null); + const handleEdit = (item) => { + setModalMode('edit'); + setEditingItem(item); + const safeItem = {}; + Object.keys(initialFormState).forEach(key => { safeItem[key] = item[key] ?? ''; }); + setFormData(safeItem); + setShowModal(true); + }; + const handleSave = async () => { + if (!formData.customer || !formData.hours) { alert('필수 항목을 입력해주세요.'); return; } + setSaving(true); + try { + const url = modalMode === 'add' ? '/finance/consulting-fees/store' : `/finance/consulting-fees/${editingItem.id}`; + const body = { ...formData, hours: parseInt(formData.hours) || 0, hourlyRate: parseInt(formData.hourlyRate) || 0, amount: parseInt(formData.amount) || 0 }; + const res = await fetch(url, { + method: modalMode === 'add' ? 'POST' : 'PUT', + headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken }, + body: JSON.stringify(body), + }); + const data = await res.json(); + if (!res.ok) { + const errors = data.errors ? Object.values(data.errors).flat().join('\n') : data.message; + alert(errors || '저장에 실패했습니다.'); + return; + } + setShowModal(false); + setEditingItem(null); + fetchData(); + } catch (err) { + console.error('저장 실패:', err); + alert('저장에 실패했습니다.'); + } finally { + setSaving(false); + } + }; + const handleDelete = async (id) => { + if (!confirm('정말 삭제하시겠습니까?')) return; + try { + const res = await fetch(`/finance/consulting-fees/${id}`, { + method: 'DELETE', + headers: { 'X-CSRF-TOKEN': csrfToken }, + }); + if (res.ok) { + setShowModal(false); + fetchData(); + } + } catch (err) { + console.error('삭제 실패:', err); + alert('삭제에 실패했습니다.'); + } }; - const handleDelete = (id) => { if (confirm('정말 삭제하시겠습니까?')) { setFees(prev => prev.filter(item => item.id !== id)); setShowModal(false); } }; const handleDownload = () => { const rows = [['상담수수료', `${dateRange.start} ~ ${dateRange.end}`], [], ['날짜', '컨설턴트', '고객사', '서비스', '시간', '시급', '금액', '상태'], @@ -145,19 +193,19 @@ function ConsultingFeeManagement() {{totalHours}시간
+{stats.totalHours}시간
{formatCurrency(totalAmount)}원
+{formatCurrency(stats.totalAmount)}원
{formatCurrency(paidAmount)}원
+{formatCurrency(stats.paidAmount)}원
{formatCurrency(pendingAmount)}원
+{formatCurrency(stats.pendingAmount)}원
{formatCurrency(totalSales)}원
+{formatCurrency(stats.totalSales)}원
{formatCurrency(totalNet)}원
+{formatCurrency(stats.totalNet)}원
{formatCurrency(settledAmount)}원
+{formatCurrency(stats.settledAmount)}원
{formatCurrency(totalCommission)}원
+{formatCurrency(stats.totalCommission)}원
{formatCurrency(totalAmount)}원
+{formatCurrency(stats.totalAmount)}원
VAT 별도
{formatCurrency(receivedAmount)}원
+{formatCurrency(stats.receivedAmount)}원
{formatCurrency(pendingAmount)}원
+{formatCurrency(stats.pendingAmount)}원
{formatCurrency(totalVat)}원
+{formatCurrency(stats.totalVat)}원
{activeSubscriptions.length}개
+{stats.activeCount}개
{formatCurrency(monthlyRecurring)}원
+{formatCurrency(stats.monthlyRecurring)}원
{formatCurrency(yearlyRecurring)}원
+{formatCurrency(stats.yearlyRecurring)}원
{totalUsers}명
+{stats.totalUsers}명