314 lines
10 KiB
PHP
314 lines
10 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Boards\File;
|
|
use App\Models\System\Holiday;
|
|
use App\Models\System\Schedule;
|
|
use App\Services\GoogleCloudStorageService;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Contracts\View\View;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
|
|
class DashboardCalendarController extends Controller
|
|
{
|
|
/**
|
|
* 달력 partial 반환 (HTMX용)
|
|
*/
|
|
public function calendar(Request $request): View
|
|
{
|
|
$year = (int) $request->input('year', now()->year);
|
|
$month = (int) $request->input('month', now()->month);
|
|
$tenantId = session('selected_tenant_id', 1);
|
|
|
|
$firstDay = Carbon::create($year, $month, 1);
|
|
$lastDay = $firstDay->copy()->endOfMonth();
|
|
$startOfWeek = $firstDay->copy()->startOfWeek(Carbon::SUNDAY);
|
|
$endOfWeek = $lastDay->copy()->endOfWeek(Carbon::SATURDAY);
|
|
|
|
$calendarData = Schedule::forTenant($tenantId)
|
|
->active()
|
|
->betweenDates($startOfWeek->toDateString(), $endOfWeek->toDateString())
|
|
->withCount('files')
|
|
->orderBy('start_date')
|
|
->orderBy('start_time')
|
|
->get()
|
|
->groupBy(fn ($s) => $s->start_date->format('Y-m-d'));
|
|
|
|
$holidayMap = $this->getHolidayMap($tenantId, $year, $month);
|
|
|
|
return view('dashboard.partials.calendar', compact(
|
|
'year', 'month', 'calendarData', 'holidayMap'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* 일정 등록
|
|
*/
|
|
public function store(Request $request): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'title' => 'required|string|max:255',
|
|
'description' => 'nullable|string|max:1000',
|
|
'start_date' => 'required|date',
|
|
'end_date' => 'nullable|date|after_or_equal:start_date',
|
|
'start_time' => 'nullable|date_format:H:i',
|
|
'end_time' => 'nullable|date_format:H:i',
|
|
'is_all_day' => 'boolean',
|
|
'type' => 'required|string|max:50',
|
|
'color' => 'nullable|string|max:20',
|
|
]);
|
|
|
|
$tenantId = session('selected_tenant_id', 1);
|
|
|
|
$validated['tenant_id'] = $tenantId;
|
|
$validated['created_by'] = auth()->id();
|
|
$validated['is_all_day'] = $validated['is_all_day'] ?? true;
|
|
|
|
if (empty($validated['end_date'])) {
|
|
$validated['end_date'] = $validated['start_date'];
|
|
}
|
|
|
|
$schedule = Schedule::create($validated);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => '일정이 등록되었습니다.',
|
|
'data' => $schedule,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 일정 상세 (JSON)
|
|
*/
|
|
public function show(int $id): JsonResponse
|
|
{
|
|
$tenantId = session('selected_tenant_id', 1);
|
|
|
|
$schedule = Schedule::forTenant($tenantId)->findOrFail($id);
|
|
|
|
// 첨부파일 목록
|
|
$files = File::where('document_type', 'schedule')
|
|
->where('document_id', $id)
|
|
->whereNull('deleted_at')
|
|
->get(['id', 'display_name', 'original_name', 'file_size', 'mime_type', 'created_at']);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $schedule,
|
|
'files' => $files,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 일정 수정
|
|
*/
|
|
public function update(Request $request, int $id): JsonResponse
|
|
{
|
|
$validated = $request->validate([
|
|
'title' => 'required|string|max:255',
|
|
'description' => 'nullable|string|max:1000',
|
|
'start_date' => 'required|date',
|
|
'end_date' => 'nullable|date|after_or_equal:start_date',
|
|
'start_time' => 'nullable|date_format:H:i',
|
|
'end_time' => 'nullable|date_format:H:i',
|
|
'is_all_day' => 'boolean',
|
|
'type' => 'required|string|max:50',
|
|
'color' => 'nullable|string|max:20',
|
|
]);
|
|
|
|
$tenantId = session('selected_tenant_id', 1);
|
|
$schedule = Schedule::forTenant($tenantId)->findOrFail($id);
|
|
|
|
$validated['updated_by'] = auth()->id();
|
|
$validated['is_all_day'] = $validated['is_all_day'] ?? true;
|
|
|
|
if (empty($validated['end_date'])) {
|
|
$validated['end_date'] = $validated['start_date'];
|
|
}
|
|
|
|
$schedule->update($validated);
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => '일정이 수정되었습니다.',
|
|
'data' => $schedule->fresh(),
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 일정 삭제
|
|
*/
|
|
public function destroy(int $id): JsonResponse
|
|
{
|
|
$tenantId = session('selected_tenant_id', 1);
|
|
$schedule = Schedule::forTenant($tenantId)->findOrFail($id);
|
|
|
|
$schedule->update(['deleted_by' => auth()->id()]);
|
|
$schedule->delete();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => '일정이 삭제되었습니다.',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 파일 업로드 (다중)
|
|
*/
|
|
public function uploadFiles(Request $request, int $scheduleId, GoogleCloudStorageService $gcs): JsonResponse
|
|
{
|
|
$request->validate([
|
|
'files' => 'required|array|min:1',
|
|
'files.*' => 'file|max:20480', // 20MB
|
|
]);
|
|
|
|
$tenantId = session('selected_tenant_id', 1);
|
|
$schedule = Schedule::forTenant($tenantId)->findOrFail($scheduleId);
|
|
|
|
$uploaded = [];
|
|
|
|
foreach ($request->file('files') as $file) {
|
|
$originalName = $file->getClientOriginalName();
|
|
$storedName = Str::random(40).'.'.$file->getClientOriginalExtension();
|
|
$storagePath = "schedules/{$tenantId}/{$schedule->id}/{$storedName}";
|
|
|
|
// 로컬(tenant 디스크) 저장
|
|
Storage::disk('tenant')->put($storagePath, file_get_contents($file));
|
|
|
|
// GCS 업로드 (가능한 경우)
|
|
$gcsUri = null;
|
|
if ($gcs->isAvailable()) {
|
|
$gcsObjectName = "schedules/{$tenantId}/{$schedule->id}/{$storedName}";
|
|
$gcsUri = $gcs->upload($file->getRealPath(), $gcsObjectName);
|
|
}
|
|
|
|
// DB 레코드
|
|
$fileRecord = File::create([
|
|
'tenant_id' => $tenantId,
|
|
'document_type' => 'schedule',
|
|
'document_id' => $schedule->id,
|
|
'file_path' => $storagePath,
|
|
'display_name' => $originalName,
|
|
'stored_name' => $storedName,
|
|
'original_name' => $originalName,
|
|
'file_name' => $originalName,
|
|
'file_size' => $file->getSize(),
|
|
'mime_type' => $file->getMimeType(),
|
|
'file_type' => $this->determineFileType($file->getMimeType()),
|
|
'is_temp' => false,
|
|
'uploaded_by' => auth()->id(),
|
|
'created_by' => auth()->id(),
|
|
]);
|
|
|
|
$uploaded[] = [
|
|
'id' => $fileRecord->id,
|
|
'name' => $originalName,
|
|
'size' => $fileRecord->getFormattedSize(),
|
|
'gcs' => $gcsUri ? true : false,
|
|
];
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => count($uploaded).'개 파일이 업로드되었습니다.',
|
|
'files' => $uploaded,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 파일 삭제
|
|
*/
|
|
public function deleteFile(int $scheduleId, int $fileId, GoogleCloudStorageService $gcs): JsonResponse
|
|
{
|
|
$tenantId = session('selected_tenant_id', 1);
|
|
Schedule::forTenant($tenantId)->findOrFail($scheduleId);
|
|
|
|
$file = File::where('document_type', 'schedule')
|
|
->where('document_id', $scheduleId)
|
|
->where('id', $fileId)
|
|
->firstOrFail();
|
|
|
|
// GCS 삭제
|
|
if ($gcs->isAvailable() && $file->file_path) {
|
|
$gcs->delete($file->file_path);
|
|
}
|
|
|
|
// 로컬 삭제
|
|
if ($file->file_path && Storage::disk('tenant')->exists($file->file_path)) {
|
|
Storage::disk('tenant')->delete($file->file_path);
|
|
}
|
|
|
|
$file->deleted_by = auth()->id();
|
|
$file->save();
|
|
$file->delete();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => '파일이 삭제되었습니다.',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* 파일 다운로드
|
|
*/
|
|
public function downloadFile(int $scheduleId, int $fileId)
|
|
{
|
|
$tenantId = session('selected_tenant_id', 1);
|
|
Schedule::forTenant($tenantId)->findOrFail($scheduleId);
|
|
|
|
$file = File::where('document_type', 'schedule')
|
|
->where('document_id', $scheduleId)
|
|
->where('id', $fileId)
|
|
->firstOrFail();
|
|
|
|
return $file->download();
|
|
}
|
|
|
|
/**
|
|
* MIME 타입으로 파일 유형 결정
|
|
*/
|
|
private function determineFileType(string $mimeType): string
|
|
{
|
|
if (str_starts_with($mimeType, 'image/')) {
|
|
return 'image';
|
|
}
|
|
if (str_contains($mimeType, 'spreadsheet') || str_contains($mimeType, 'excel')) {
|
|
return 'excel';
|
|
}
|
|
if (str_contains($mimeType, 'zip') || str_contains($mimeType, 'rar') || str_contains($mimeType, 'archive')) {
|
|
return 'archive';
|
|
}
|
|
|
|
return 'document';
|
|
}
|
|
|
|
/**
|
|
* 해당 월의 휴일 맵 생성 (날짜 => 휴일명)
|
|
*/
|
|
private function getHolidayMap(int $tenantId, int $year, int $month): array
|
|
{
|
|
$startOfMonth = Carbon::create($year, $month, 1)->startOfWeek(Carbon::SUNDAY);
|
|
$endOfMonth = Carbon::create($year, $month, 1)->endOfMonth()->endOfWeek(Carbon::SATURDAY);
|
|
|
|
$holidays = Holiday::forTenant($tenantId)
|
|
->where('start_date', '<=', $endOfMonth->toDateString())
|
|
->where('end_date', '>=', $startOfMonth->toDateString())
|
|
->get();
|
|
|
|
$map = [];
|
|
foreach ($holidays as $holiday) {
|
|
$start = $holiday->start_date->copy();
|
|
$end = $holiday->end_date->copy();
|
|
for ($d = $start; $d->lte($end); $d->addDay()) {
|
|
$map[$d->format('Y-m-d')] = $holiday->name;
|
|
}
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
}
|