feat: [daily-work-log] 메모/회고 파일 업로드 기능 추가

- 메모, 회고 섹션에 파일 첨부 기능 추가
- 드래그앤드롭 및 클릭 업로드 지원
- 이미지 썸네일 미리보기, 파일 다운로드/삭제
- Boards\File 모델 재사용 (document_type: daily_work_log)
This commit is contained in:
김보곤
2026-03-16 15:56:16 +09:00
parent 6881c0c6cb
commit d9f0d3ffbf
4 changed files with 180 additions and 0 deletions

View File

@@ -3,10 +3,13 @@
namespace App\Http\Controllers\Finance;
use App\Http\Controllers\Controller;
use App\Models\Boards\File;
use App\Models\Finance\DailyWorkLog;
use App\Models\Finance\DailyWorkLogItem;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class DailyWorkLogController extends Controller
{
@@ -41,6 +44,21 @@ public function show(Request $request): JsonResponse
$total = $items->count();
$completed = $items->where('is_completed', true)->count();
$files = File::where('document_type', 'daily_work_log')
->where('document_id', $log->id)
->whereNull('deleted_at')
->get()
->map(fn (File $f) => [
'id' => $f->id,
'field_key' => $f->field_key,
'original_name' => $f->original_name,
'file_size' => $f->file_size,
'formatted_size' => $f->getFormattedSize(),
'mime_type' => $f->mime_type,
'is_image' => $f->isImage(),
'url' => $f->getUrl(),
]);
return response()->json([
'success' => true,
'data' => [
@@ -50,6 +68,7 @@ public function show(Request $request): JsonResponse
'reflection' => $log->reflection ?? '',
'items' => $items->values(),
'achievement_rate' => $total > 0 ? round(($completed / $total) * 100, 2) : 0,
'files' => $files->values(),
],
]);
}
@@ -173,4 +192,101 @@ public function copyFromPrevious(Request $request): JsonResponse
],
]);
}
/**
* 파일 업로드 (메모/회고 첨부)
*/
public function uploadFile(Request $request): JsonResponse
{
$request->validate([
'log_date' => 'required|date',
'field_key' => 'required|in:memo,reflection',
'file' => 'required|file|max:10240',
]);
$tenantId = session('selected_tenant_id', 1);
$log = DailyWorkLog::updateOrCreate(
['tenant_id' => $tenantId, 'log_date' => $request->input('log_date')],
['created_by' => auth()->id()]
);
$file = $request->file('file');
$storedName = Str::uuid().'.'.$file->getClientOriginalExtension();
$basePath = "daily-work-log/{$tenantId}/{$log->id}";
$filePath = "{$basePath}/{$storedName}";
Storage::disk('tenant')->put($filePath, file_get_contents($file));
$record = File::create([
'tenant_id' => $tenantId,
'is_temp' => false,
'file_path' => $filePath,
'display_name' => $file->getClientOriginalName(),
'stored_name' => $storedName,
'original_name' => $file->getClientOriginalName(),
'file_name' => $file->getClientOriginalName(),
'file_size' => $file->getSize(),
'mime_type' => $file->getMimeType(),
'file_type' => $this->detectFileType($file->getMimeType()),
'field_key' => $request->input('field_key'),
'document_type' => 'daily_work_log',
'document_id' => $log->id,
'uploaded_by' => auth()->id(),
'created_by' => auth()->id(),
]);
return response()->json([
'success' => true,
'file' => [
'id' => $record->id,
'field_key' => $record->field_key,
'original_name' => $record->original_name,
'file_size' => $record->file_size,
'formatted_size' => $record->getFormattedSize(),
'mime_type' => $record->mime_type,
'is_image' => $record->isImage(),
'url' => $record->getUrl(),
],
]);
}
/**
* 파일 삭제
*/
public function deleteFile(int $fileId): JsonResponse
{
$file = File::where('document_type', 'daily_work_log')->findOrFail($fileId);
$file->softDeleteFile(auth()->id());
return response()->json(['success' => true]);
}
/**
* 파일 다운로드
*/
public function downloadFile(int $fileId)
{
$file = File::where('document_type', 'daily_work_log')->findOrFail($fileId);
return $file->download();
}
private function detectFileType(?string $mimeType): string
{
if (! $mimeType) {
return 'other';
}
if (str_starts_with($mimeType, 'image/')) {
return 'image';
}
if ($mimeType === 'application/pdf') {
return 'pdf';
}
if (str_contains($mimeType, 'spreadsheet') || str_contains($mimeType, 'excel')) {
return 'excel';
}
return 'document';
}
}