authorize('viewAny', $this->modelClass); try { $perPage = $request->get('per_page', 10) ?? 10; $perPage = min($perPage, 1000); $search = (string) ($request->get('search', '') ?? ''); $sortBy = (string) ($request->get('sort_by', 'start_datetime') ?? 'start_datetime'); $sortOrder = (string) ($request->get('sort_order', 'desc') ?? 'desc'); $items = $this->getIndexData($request, $perPage, $search, $sortBy, $sortOrder); if ($items instanceof LengthAwarePaginator) { return response()->json([ 'success' => true, 'data' => ActivityListResource::collection($items->items()), 'pagination' => [ 'current_page' => $items->currentPage(), 'per_page' => $items->perPage(), 'total' => $items->total(), 'last_page' => $items->lastPage(), 'from' => $items->firstItem(), 'to' => $items->lastItem(), 'has_more_pages' => $items->hasMorePages(), ], 'message' => $this->getSuccessMessage('index'), ]); } return response()->json([ 'success' => true, 'data' => ActivityListResource::collection($items), 'message' => $this->getSuccessMessage('index'), ]); } catch (Exception $e) { Log::error("Error fetching {$this->resourceNamePlural}: ".$e->getMessage()); return $this->errorResponse($this->getErrorMessage('index'), 500); } } protected function getIndexData(Request $request, int $perPage = 10, string $search = '', string $sortBy = 'start_datetime', string $sortOrder = 'desc') { if ($perPage > 0) { return $this->repository->getIndexPaginated($perPage, $search, $sortBy, $sortOrder); } return $this->repository->all($search); } public function store(Request $request): JsonResponse { $this->authorize('create', $this->modelClass); try { $validated = $this->validateRequest($request); $documents = collect($request->file('documents', [])) ->filter() ->all(); $galleryImages = collect($request->file('gallery', [])) ->filter() ->all(); $activity = $this->activityService->create( $this->prepareStoreData(collect($validated)->except(['documents', 'gallery'])->all()), $documents, $galleryImages, ); ActivityLogger::log("Created {$this->resourceName}: {$this->getItemName($activity)}", $activity); return response()->json([ 'success' => true, 'data' => new $this->resourceClass($activity), 'message' => $this->getSuccessMessage('store'), ], 201); } catch (Exception $e) { Log::error("Error creating {$this->resourceName}: ".$e->getMessage()); return $this->errorResponse($this->getErrorMessage('store').': '.$e->getMessage(), 500); } } public function update(Request $request, string $id): JsonResponse { $this->authorize('update', $this->modelClass); try { $activity = $this->repository->findById($id); if (! $activity) { return $this->errorResponse($this->getNotFoundMessage(), 404); } $validated = $this->validateRequest($request); $documents = collect($request->file('documents', [])) ->filter() ->all(); $galleryImages = collect($request->file('gallery', [])) ->filter() ->all(); $activity = $this->activityService->update( $activity, $this->prepareUpdateData(collect($validated)->except(['documents', 'gallery'])->all(), $activity), $documents, $galleryImages, ); ActivityLogger::log("Updated {$this->resourceName}: {$this->getItemName($activity)}", $activity); return response()->json([ 'success' => true, 'data' => new $this->resourceClass($activity), 'message' => $this->getSuccessMessage('update'), ]); } catch (Exception $e) { Log::error("Error updating {$this->resourceName}: ".$e->getMessage()); return $this->errorResponse($this->getErrorMessage('update').': '.$e->getMessage(), 500); } } protected function loadShowRelations($item) { return $this->repository->findByIdWithRelations($item->id) ?? $item; } protected function getItemName($item): string { return $item->title ?? $item->id; } protected function checkDependencies($activity): ?JsonResponse { if ($activity->reports()->count() > 0) { return response()->json([ 'success' => false, 'message' => 'Aktiviti tidak boleh dipadam kerana masih mempunyai laporan.', ], 422); } return null; } public function uploadDocument( UploadActivityDocumentRequest $request, Activity $activity ): JsonResponse { $this->authorize('update', $this->modelClass); try { $activity = $this->activityService->uploadDocument( $activity, $request->file('file'), $request->validated('type', 'general'), $request->validated('description'), ); return response()->json([ 'success' => true, 'message' => 'Dokumen berjaya dimuat naik.', 'data' => new ActivityResource($activity), ]); } catch (Exception $e) { Log::error("Error uploading document for {$this->resourceName}: ".$e->getMessage()); return $this->errorResponse('Gagal memuat naik dokumen: '.$e->getMessage(), 500); } } public function uploadGalleryImage(UploadActivityGalleryImageRequest $request, Activity $activity): JsonResponse { $this->authorize('update', $this->modelClass); try { $activity = $this->activityService->uploadGalleryImage( $activity, $request->file('file'), $request->validated('description'), ); return response()->json([ 'success' => true, 'message' => 'Imej galeri berjaya dimuat naik.', 'data' => new ActivityResource($activity), ]); } catch (Exception $e) { Log::error("Error uploading gallery image for {$this->resourceName}: ".$e->getMessage()); return $this->errorResponse('Gagal memuat naik imej galeri: '.$e->getMessage(), 500); } } public function downloadDocument(Activity $activity, string $documentId): StreamedResponse { $this->authorize('view', $this->modelClass); $document = $activity->documents() ->where('type', '!=', Activity::GALLERY_DOCUMENT_TYPE) ->findOrFail($documentId); return $this->documentService->downloadDocument($document->id); } public function serveGalleryImage(Activity $activity, string $imageId): BinaryFileResponse { $this->authorize('view', $this->modelClass); $document = $activity->galleryImages()->findOrFail($imageId); $disk = Storage::disk(Document::STORAGE_DISK); if (! $disk->exists($document->path)) { abort(404, 'File not found'); } return response()->file($disk->path($document->path), [ 'Content-Type' => $document->mime_type ?? 'application/octet-stream', 'Content-Disposition' => 'inline; filename="'.$document->name.'"', ]); } public function deleteDocument(Activity $activity, string $documentId): JsonResponse { $this->authorize('update', $this->modelClass); try { $activity->documents() ->where('type', '!=', Activity::GALLERY_DOCUMENT_TYPE) ->findOrFail($documentId); $activity->deleteDocument($documentId); $activity = $this->repository->findByIdWithRelations($activity->id); return response()->json([ 'success' => true, 'message' => 'Dokumen berjaya dipadam.', 'data' => new ActivityResource($activity), ]); } catch (Exception $e) { Log::error("Error deleting document for {$this->resourceName}: ".$e->getMessage()); return $this->errorResponse('Gagal memadam dokumen: '.$e->getMessage(), 500); } } public function deleteGalleryImage(Activity $activity, string $imageId): JsonResponse { $this->authorize('update', $this->modelClass); try { $activity->galleryImages()->findOrFail($imageId); $activity->deleteDocument($imageId); $activity = $this->repository->findByIdWithRelations($activity->id); return response()->json([ 'success' => true, 'message' => 'Imej galeri berjaya dipadam.', 'data' => new ActivityResource($activity), ]); } catch (Exception $e) { Log::error("Error deleting gallery image for {$this->resourceName}: ".$e->getMessage()); return $this->errorResponse('Gagal memadam imej galeri: '.$e->getMessage(), 500); } } public function destroy(string $id): JsonResponse { $this->authorize('deleteAny', $this->modelClass); try { $activity = $this->repository->findByIdWithRelations($id); if (! $activity) { return $this->errorResponse($this->getNotFoundMessage(), 404); } $dependencyCheck = $this->checkDependencies($activity); if ($dependencyCheck !== null) { return $dependencyCheck; } $activity->documents->each( fn ($document) => $this->documentService->deleteDocument($document->id) ); $this->repository->delete($id); ActivityLogger::log("Deleted {$this->resourceName}: {$this->getItemName($activity)}", $activity); return response()->json([ 'success' => true, 'message' => $this->getSuccessMessage('destroy'), ]); } catch (Exception $e) { Log::error("Error deleting {$this->resourceName}: ".$e->getMessage()); return $this->errorResponse($this->getErrorMessage('destroy').': '.$e->getMessage(), 500); } } }