feat:바로빌 계좌 거래내역 적요/내용 수정 기능 추가
- BankTransactionOverride 모델 추가 (오버라이드 데이터 관리) - EaccountController에 saveOverride 엔드포인트 추가 - parseTransactionLogs에서 오버라이드 데이터 병합 로직 추가 - 프론트엔드에 TransactionEditModal 컴포넌트 추가 - 적요 셀 클릭 시 수정 모달 표시 - 오버라이드된 항목 시각적 표시 (배경색, 수정 배지) - 원본 복원 기능 포함 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
use App\Models\Barobill\BarobillConfig;
|
||||
use App\Models\Barobill\BarobillMember;
|
||||
use App\Models\Barobill\BankTransaction;
|
||||
use App\Models\Barobill\BankTransactionOverride;
|
||||
use App\Models\Tenants\Tenant;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -309,9 +310,12 @@ public function transactions(Request $request): JsonResponse
|
||||
// DB에서 저장된 계정과목 데이터 조회
|
||||
$savedData = BankTransaction::getByDateRange($tenantId, $startDate, $endDate, $bankAccountNum ?: null);
|
||||
|
||||
// 오버라이드 데이터 (수정된 적요/내용) 조회
|
||||
$overrideData = null;
|
||||
|
||||
// 전체 계좌 조회: 빈 값이면 모든 계좌의 거래 내역 조회
|
||||
if (empty($bankAccountNum)) {
|
||||
return $this->getAllAccountsTransactions($userId, $startDate, $endDate, $page, $limit, $savedData);
|
||||
return $this->getAllAccountsTransactions($userId, $startDate, $endDate, $page, $limit, $savedData, $overrideData, $tenantId);
|
||||
}
|
||||
|
||||
// 단일 계좌 조회
|
||||
@@ -358,8 +362,8 @@ public function transactions(Request $request): JsonResponse
|
||||
]);
|
||||
}
|
||||
|
||||
// 데이터 파싱 (저장된 계정과목 병합)
|
||||
$logs = $this->parseTransactionLogs($resultData, '', $savedData);
|
||||
// 데이터 파싱 (저장된 계정과목 + 오버라이드 병합)
|
||||
$logs = $this->parseTransactionLogs($resultData, '', $savedData, $tenantId);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
@@ -386,7 +390,7 @@ public function transactions(Request $request): JsonResponse
|
||||
/**
|
||||
* 전체 계좌의 거래 내역 조회
|
||||
*/
|
||||
private function getAllAccountsTransactions(string $userId, string $startDate, string $endDate, int $page, int $limit, $savedData = null): JsonResponse
|
||||
private function getAllAccountsTransactions(string $userId, string $startDate, string $endDate, int $page, int $limit, $savedData = null, $overrideData = null, int $tenantId = 1): JsonResponse
|
||||
{
|
||||
// 먼저 계좌 목록 조회
|
||||
$accountResult = $this->callSoap('GetBankAccountEx', ['AvailOnly' => 0]);
|
||||
@@ -432,7 +436,7 @@ private function getAllAccountsTransactions(string $userId, string $startDate, s
|
||||
$errorCode = $this->checkErrorCode($accData);
|
||||
|
||||
if (!$errorCode || in_array($errorCode, [-25005, -25001])) {
|
||||
$parsed = $this->parseTransactionLogs($accData, $acc->BankName ?? '', $savedData);
|
||||
$parsed = $this->parseTransactionLogs($accData, $acc->BankName ?? '', $savedData, $tenantId);
|
||||
foreach ($parsed['logs'] as $log) {
|
||||
$log['bankName'] = $acc->BankName ?? $this->getBankName($acc->BankCode ?? '');
|
||||
$allLogs[] = $log;
|
||||
@@ -476,11 +480,12 @@ private function getAllAccountsTransactions(string $userId, string $startDate, s
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래 내역 파싱 (저장된 계정과목 병합)
|
||||
* 거래 내역 파싱 (저장된 계정과목 + 오버라이드 병합)
|
||||
*/
|
||||
private function parseTransactionLogs($resultData, string $defaultBankName = '', $savedData = null): array
|
||||
private function parseTransactionLogs($resultData, string $defaultBankName = '', $savedData = null, int $tenantId = 1): array
|
||||
{
|
||||
$logs = [];
|
||||
$uniqueKeys = [];
|
||||
$totalDeposit = 0;
|
||||
$totalWithdraw = 0;
|
||||
|
||||
@@ -491,6 +496,22 @@ private function parseTransactionLogs($resultData, string $defaultBankName = '',
|
||||
: [$resultData->BankAccountLogList->BankAccountTransLog];
|
||||
}
|
||||
|
||||
// 1단계: 모든 고유 키 수집
|
||||
foreach ($rawLogs as $log) {
|
||||
$bankAccountNum = $log->BankAccountNum ?? '';
|
||||
$transDT = $log->TransDT ?? '';
|
||||
$deposit = (int) floatval($log->Deposit ?? 0);
|
||||
$withdraw = (int) floatval($log->Withdraw ?? 0);
|
||||
$balance = (int) floatval($log->Balance ?? 0);
|
||||
|
||||
$uniqueKey = implode('|', [$bankAccountNum, $transDT, $deposit, $withdraw, $balance]);
|
||||
$uniqueKeys[] = $uniqueKey;
|
||||
}
|
||||
|
||||
// 2단계: 오버라이드 데이터 일괄 조회
|
||||
$overrides = BankTransactionOverride::getByUniqueKeys($tenantId, $uniqueKeys);
|
||||
|
||||
// 3단계: 각 로그 처리
|
||||
foreach ($rawLogs as $log) {
|
||||
$deposit = floatval($log->Deposit ?? 0);
|
||||
$withdraw = floatval($log->Withdraw ?? 0);
|
||||
@@ -525,6 +546,15 @@ private function parseTransactionLogs($resultData, string $defaultBankName = '',
|
||||
// 고유 키 생성하여 저장된 데이터와 매칭 (숫자는 정수로 변환하여 형식 통일)
|
||||
$uniqueKey = implode('|', [$bankAccountNum, $transDT, (int) $deposit, (int) $withdraw, (int) $balance]);
|
||||
$savedItem = $savedData?->get($uniqueKey);
|
||||
$override = $overrides->get($uniqueKey);
|
||||
|
||||
// 원본 적요/내용
|
||||
$originalSummary = $fullSummary;
|
||||
$originalCast = $savedItem?->cast ?? '';
|
||||
|
||||
// 오버라이드 적용 (수정된 값이 있으면 사용)
|
||||
$displaySummary = $override?->modified_summary ?? $originalSummary;
|
||||
$displayCast = $override?->modified_cast ?? $originalCast;
|
||||
|
||||
$logItem = [
|
||||
'transDate' => $transDate,
|
||||
@@ -538,15 +568,18 @@ private function parseTransactionLogs($resultData, string $defaultBankName = '',
|
||||
'withdrawFormatted' => number_format($withdraw),
|
||||
'balance' => $balance,
|
||||
'balanceFormatted' => number_format($balance),
|
||||
'summary' => $fullSummary,
|
||||
// 저장된 상대계좌예금주명 우선 사용 (직접 입력 가능)
|
||||
'cast' => $savedItem?->cast ?? '',
|
||||
'summary' => $displaySummary,
|
||||
'originalSummary' => $originalSummary,
|
||||
'cast' => $displayCast,
|
||||
'originalCast' => $originalCast,
|
||||
'memo' => $log->Memo ?? '',
|
||||
'transOffice' => $log->TransOffice ?? '',
|
||||
// 저장된 계정과목 정보 병합
|
||||
'accountCode' => $savedItem?->account_code ?? '',
|
||||
'accountName' => $savedItem?->account_name ?? '',
|
||||
'isSaved' => $savedItem !== null,
|
||||
'isOverridden' => $override !== null,
|
||||
'uniqueKey' => $uniqueKey,
|
||||
];
|
||||
|
||||
$logs[] = $logItem;
|
||||
@@ -965,6 +998,53 @@ public function exportExcel(Request $request): StreamedResponse|JsonResponse
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 거래내역 적요/내용 오버라이드 저장
|
||||
*/
|
||||
public function saveOverride(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tenantId = session('selected_tenant_id', self::HEADQUARTERS_TENANT_ID);
|
||||
|
||||
$validated = $request->validate([
|
||||
'uniqueKey' => 'required|string|max:100',
|
||||
'modifiedSummary' => 'nullable|string|max:200',
|
||||
'modifiedCast' => 'nullable|string|max:200',
|
||||
]);
|
||||
|
||||
$result = BankTransactionOverride::saveOverride(
|
||||
$tenantId,
|
||||
$validated['uniqueKey'],
|
||||
$validated['modifiedSummary'] ?? null,
|
||||
$validated['modifiedCast'] ?? null
|
||||
);
|
||||
|
||||
if ($result === null) {
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '오버라이드가 삭제되었습니다.',
|
||||
'deleted' => true
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '오버라이드가 저장되었습니다.',
|
||||
'data' => [
|
||||
'id' => $result->id,
|
||||
'modifiedSummary' => $result->modified_summary,
|
||||
'modifiedCast' => $result->modified_cast,
|
||||
]
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('오버라이드 저장 오류: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'error' => '저장 오류: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* SOAP 호출
|
||||
*/
|
||||
|
||||
80
app/Models/Barobill/BankTransactionOverride.php
Normal file
80
app/Models/Barobill/BankTransactionOverride.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Barobill;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* 바로빌 계좌 거래내역 적요/내용 수정 오버라이드 모델
|
||||
*/
|
||||
class BankTransactionOverride extends Model
|
||||
{
|
||||
protected $table = 'barobill_bank_transaction_overrides';
|
||||
|
||||
protected $fillable = [
|
||||
'tenant_id',
|
||||
'unique_key',
|
||||
'modified_summary',
|
||||
'modified_cast',
|
||||
];
|
||||
|
||||
/**
|
||||
* 테넌트별 조회 스코프
|
||||
*/
|
||||
public function scopeForTenant($query, int $tenantId)
|
||||
{
|
||||
return $query->where('tenant_id', $tenantId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 고유키로 조회
|
||||
*/
|
||||
public function scopeByUniqueKey($query, string $uniqueKey)
|
||||
{
|
||||
return $query->where('unique_key', $uniqueKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 여러 고유키에 대한 오버라이드 조회
|
||||
* @return Collection<string, self> key가 unique_key인 컬렉션
|
||||
*/
|
||||
public static function getByUniqueKeys(int $tenantId, array $uniqueKeys): Collection
|
||||
{
|
||||
if (empty($uniqueKeys)) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return static::forTenant($tenantId)
|
||||
->whereIn('unique_key', $uniqueKeys)
|
||||
->get()
|
||||
->keyBy('unique_key');
|
||||
}
|
||||
|
||||
/**
|
||||
* 오버라이드 저장 또는 업데이트
|
||||
*/
|
||||
public static function saveOverride(
|
||||
int $tenantId,
|
||||
string $uniqueKey,
|
||||
?string $modifiedSummary,
|
||||
?string $modifiedCast
|
||||
): ?self {
|
||||
// 둘 다 null이거나 빈 문자열이면 기존 레코드 삭제
|
||||
if (empty($modifiedSummary) && empty($modifiedCast)) {
|
||||
static::forTenant($tenantId)->byUniqueKey($uniqueKey)->delete();
|
||||
return null;
|
||||
}
|
||||
|
||||
return static::updateOrCreate(
|
||||
[
|
||||
'tenant_id' => $tenantId,
|
||||
'unique_key' => $uniqueKey,
|
||||
],
|
||||
[
|
||||
'modified_summary' => $modifiedSummary ?: null,
|
||||
'modified_cast' => $modifiedCast ?: null,
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,7 @@
|
||||
accountCodesDestroy: (id) => `/barobill/eaccount/account-codes/${id}`,
|
||||
save: '{{ route("barobill.eaccount.save") }}',
|
||||
export: '{{ route("barobill.eaccount.export") }}',
|
||||
saveOverride: '{{ route("barobill.eaccount.save-override") }}',
|
||||
};
|
||||
|
||||
const CSRF_TOKEN = '{{ csrf_token() }}';
|
||||
@@ -596,6 +597,197 @@ className="px-6 py-2 bg-stone-600 text-white rounded-lg hover:bg-stone-700 font-
|
||||
);
|
||||
};
|
||||
|
||||
// 적요/내용 수정 모달 컴포넌트
|
||||
const TransactionEditModal = ({ isOpen, onClose, log, onSave }) => {
|
||||
const [modifiedSummary, setModifiedSummary] = useState('');
|
||||
const [modifiedCast, setModifiedCast] = useState('');
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen && log) {
|
||||
// 현재 표시되는 값으로 초기화 (수정된 값이 있으면 그 값, 없으면 원본)
|
||||
setModifiedSummary(log.summary || '');
|
||||
setModifiedCast(log.cast || '');
|
||||
}
|
||||
}, [isOpen, log]);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!log?.uniqueKey) {
|
||||
notify('고유 키가 없습니다.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await fetch(API.saveOverride, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': CSRF_TOKEN,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
uniqueKey: log.uniqueKey,
|
||||
modifiedSummary: modifiedSummary !== log.originalSummary ? modifiedSummary : null,
|
||||
modifiedCast: modifiedCast !== log.originalCast ? modifiedCast : null,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
notify(data.message, 'success');
|
||||
onSave(modifiedSummary, modifiedCast);
|
||||
onClose();
|
||||
} else {
|
||||
notify(data.error || '저장 실패', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
notify('저장 오류: ' + err.message, 'error');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = async () => {
|
||||
if (!confirm('원본 값으로 되돌리시겠습니까?')) return;
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
const res = await fetch(API.saveOverride, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRF-TOKEN': CSRF_TOKEN,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
uniqueKey: log.uniqueKey,
|
||||
modifiedSummary: null,
|
||||
modifiedCast: null,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
notify('원본으로 복원되었습니다.', 'success');
|
||||
onSave(log.originalSummary, log.originalCast);
|
||||
onClose();
|
||||
} else {
|
||||
notify(data.error || '복원 실패', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
notify('복원 오류: ' + err.message, 'error');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!isOpen || !log) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-xl shadow-2xl w-full max-w-lg overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between px-6 py-4 border-b border-stone-200 bg-stone-50">
|
||||
<h2 className="text-lg font-bold text-stone-900">적요/내용 수정</h2>
|
||||
<button onClick={onClose} className="text-stone-400 hover:text-stone-600 text-2xl">×</button>
|
||||
</div>
|
||||
|
||||
{/* 거래 정보 */}
|
||||
<div className="px-6 py-4 bg-emerald-50/50 border-b border-stone-100">
|
||||
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-stone-500">거래일시:</span>
|
||||
<span className="ml-2 font-medium">{log.transDateTime}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-stone-500">계좌:</span>
|
||||
<span className="ml-2 font-medium">{log.bankName}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-stone-500">입금:</span>
|
||||
<span className="ml-2 font-medium text-blue-600">{log.deposit > 0 ? log.depositFormatted + '원' : '-'}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-stone-500">출금:</span>
|
||||
<span className="ml-2 font-medium text-red-600">{log.withdraw > 0 ? log.withdrawFormatted + '원' : '-'}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 수정 폼 */}
|
||||
<div className="px-6 py-4 space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-stone-700 mb-1">
|
||||
적요
|
||||
{log.isOverridden && log.originalSummary !== modifiedSummary && (
|
||||
<span className="ml-2 text-xs text-amber-600">(수정됨)</span>
|
||||
)}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={modifiedSummary}
|
||||
onChange={(e) => setModifiedSummary(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-stone-200 rounded-lg text-sm focus:ring-2 focus:ring-emerald-500 outline-none"
|
||||
placeholder="적요 입력"
|
||||
/>
|
||||
{log.originalSummary && modifiedSummary !== log.originalSummary && (
|
||||
<p className="mt-1 text-xs text-stone-400">원본: {log.originalSummary}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-stone-700 mb-1">
|
||||
내용 (상대계좌예금주명)
|
||||
{log.isOverridden && log.originalCast !== modifiedCast && (
|
||||
<span className="ml-2 text-xs text-amber-600">(수정됨)</span>
|
||||
)}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={modifiedCast}
|
||||
onChange={(e) => setModifiedCast(e.target.value)}
|
||||
className="w-full px-3 py-2 border border-stone-200 rounded-lg text-sm focus:ring-2 focus:ring-emerald-500 outline-none"
|
||||
placeholder="내용 입력"
|
||||
/>
|
||||
{log.originalCast && modifiedCast !== log.originalCast && (
|
||||
<p className="mt-1 text-xs text-stone-400">원본: {log.originalCast}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="px-6 py-4 border-t border-stone-200 bg-stone-50 flex justify-between">
|
||||
<div>
|
||||
{log.isOverridden && (
|
||||
<button
|
||||
onClick={handleReset}
|
||||
disabled={saving}
|
||||
className="px-4 py-2 text-amber-600 hover:text-amber-700 text-sm font-medium disabled:opacity-50"
|
||||
>
|
||||
원본으로 복원
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-4 py-2 bg-stone-200 text-stone-700 rounded-lg hover:bg-stone-300 text-sm font-medium"
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="px-4 py-2 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 text-sm font-medium disabled:opacity-50"
|
||||
>
|
||||
{saving ? '저장 중...' : '저장'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// TransactionTable Component
|
||||
const TransactionTable = ({
|
||||
logs,
|
||||
@@ -615,7 +807,8 @@ className="px-6 py-2 bg-stone-600 text-white rounded-lg hover:bg-stone-700 font-
|
||||
onExport,
|
||||
onOpenSettings,
|
||||
saving,
|
||||
hasChanges
|
||||
hasChanges,
|
||||
onEditTransaction
|
||||
}) => {
|
||||
const formatCurrency = (val) => new Intl.NumberFormat('ko-KR').format(val || 0) + '원';
|
||||
|
||||
@@ -750,7 +943,7 @@ className="flex items-center gap-2 px-4 py-2 bg-stone-100 text-stone-700 rounded
|
||||
</tr>
|
||||
) : (
|
||||
logs.map((log, index) => (
|
||||
<tr key={index} className={`hover:bg-stone-50 transition-colors ${log.isSaved ? 'bg-green-50/30' : ''}`}>
|
||||
<tr key={index} className={`hover:bg-stone-50 transition-colors ${log.isSaved ? 'bg-green-50/30' : ''} ${log.isOverridden ? 'bg-amber-50/50' : ''}`}>
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<div className="font-medium text-stone-900">{log.transDateTime || '-'}</div>
|
||||
</td>
|
||||
@@ -760,8 +953,19 @@ className="flex items-center gap-2 px-4 py-2 bg-stone-100 text-stone-700 rounded
|
||||
{log.bankAccountNum ? '****' + log.bankAccountNum.slice(-4) : '-'}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="font-medium text-stone-900">{log.summary || '-'}</div>
|
||||
<td
|
||||
className="px-4 py-3 cursor-pointer hover:bg-emerald-50 group"
|
||||
onClick={() => onEditTransaction(index)}
|
||||
>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="font-medium text-stone-900">{log.summary || '-'}</div>
|
||||
{log.isOverridden && (
|
||||
<span className="px-1 py-0.5 bg-amber-100 text-amber-700 text-xs rounded">수정</span>
|
||||
)}
|
||||
<svg className="w-3 h-3 text-stone-400 opacity-0 group-hover:opacity-100 transition-opacity" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z" />
|
||||
</svg>
|
||||
</div>
|
||||
{log.memo && <div className="text-xs text-stone-400">{log.memo}</div>}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right font-medium text-blue-600">
|
||||
@@ -818,6 +1022,8 @@ className="w-full px-2 py-1 text-sm border border-stone-200 rounded focus:ring-2
|
||||
const [accountCodes, setAccountCodes] = useState([]);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [showSettingsModal, setShowSettingsModal] = useState(false);
|
||||
const [editModalOpen, setEditModalOpen] = useState(false);
|
||||
const [editingLogIndex, setEditingLogIndex] = useState(null);
|
||||
|
||||
// 날짜 필터 상태 (기본: 현재 월)
|
||||
const currentMonth = getMonthDates(0);
|
||||
@@ -915,6 +1121,28 @@ className="w-full px-2 py-1 text-sm border border-stone-200 rounded focus:ring-2
|
||||
setHasChanges(true);
|
||||
}, []);
|
||||
|
||||
// 거래 수정 모달 열기
|
||||
const handleEditTransaction = useCallback((index) => {
|
||||
setEditingLogIndex(index);
|
||||
setEditModalOpen(true);
|
||||
}, []);
|
||||
|
||||
// 오버라이드 저장 후 로그 업데이트
|
||||
const handleSaveOverride = useCallback((newSummary, newCast) => {
|
||||
if (editingLogIndex !== null) {
|
||||
setLogs(prevLogs => {
|
||||
const newLogs = [...prevLogs];
|
||||
newLogs[editingLogIndex] = {
|
||||
...newLogs[editingLogIndex],
|
||||
summary: newSummary,
|
||||
cast: newCast,
|
||||
isOverridden: true
|
||||
};
|
||||
return newLogs;
|
||||
});
|
||||
}
|
||||
}, [editingLogIndex]);
|
||||
|
||||
// 저장 핸들러
|
||||
const handleSave = async () => {
|
||||
if (logs.length === 0) return;
|
||||
@@ -1072,9 +1300,21 @@ className="w-full px-2 py-1 text-sm border border-stone-200 rounded focus:ring-2
|
||||
onOpenSettings={() => setShowSettingsModal(true)}
|
||||
saving={saving}
|
||||
hasChanges={hasChanges}
|
||||
onEditTransaction={handleEditTransaction}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Transaction Edit Modal */}
|
||||
<TransactionEditModal
|
||||
isOpen={editModalOpen}
|
||||
onClose={() => {
|
||||
setEditModalOpen(false);
|
||||
setEditingLogIndex(null);
|
||||
}}
|
||||
log={editingLogIndex !== null ? logs[editingLogIndex] : null}
|
||||
onSave={handleSaveOverride}
|
||||
/>
|
||||
|
||||
{/* Account Code Settings Modal */}
|
||||
<AccountCodeSettingsModal
|
||||
isOpen={showSettingsModal}
|
||||
|
||||
@@ -462,6 +462,7 @@
|
||||
Route::delete('/account-codes/{id}', [\App\Http\Controllers\Barobill\EaccountController::class, 'accountCodeDestroy'])->name('account-codes.destroy');
|
||||
Route::post('/save', [\App\Http\Controllers\Barobill\EaccountController::class, 'save'])->name('save');
|
||||
Route::get('/export', [\App\Http\Controllers\Barobill\EaccountController::class, 'exportExcel'])->name('export');
|
||||
Route::post('/save-override', [\App\Http\Controllers\Barobill\EaccountController::class, 'saveOverride'])->name('save-override');
|
||||
});
|
||||
|
||||
// 카드 사용내역 (React 페이지)
|
||||
|
||||
Reference in New Issue
Block a user