feat: [equipment] 사진 멀티 업로드(GCS) + 엑셀 Import 기능 추가
- EquipmentPhotoService: GCS 기반 사진 업로드/삭제/조회 (최대 10장) - EquipmentImportService: 엑셀 파싱 → 설비 일괄 등록 (한글 헤더 자동 매핑) - API: 사진 업로드/목록/삭제, Import 미리보기/실행 엔드포인트 - 뷰: create/edit에 드래그앤드롭 사진 업로드, show에 갤러리 표시 - import.blade.php: 3단계 Import UI (파일선택 → 미리보기 → 결과) - phpoffice/phpspreadsheet 패키지 추가
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreEquipmentRequest;
|
||||
use App\Http\Requests\UpdateEquipmentRequest;
|
||||
use App\Services\EquipmentImportService;
|
||||
use App\Services\EquipmentPhotoService;
|
||||
use App\Services\EquipmentService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -12,7 +14,9 @@
|
||||
class EquipmentController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private EquipmentService $equipmentService
|
||||
private EquipmentService $equipmentService,
|
||||
private EquipmentPhotoService $photoService,
|
||||
private EquipmentImportService $importService,
|
||||
) {}
|
||||
|
||||
public function index(Request $request)
|
||||
@@ -138,4 +142,119 @@ public function templates(int $id): JsonResponse
|
||||
'data' => $equipment->inspectionTemplates,
|
||||
]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 사진 관리
|
||||
// =========================================================================
|
||||
|
||||
public function uploadPhotos(Request $request, int $id): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'photos' => 'required|array|min:1|max:10',
|
||||
'photos.*' => 'required|image|max:10240',
|
||||
]);
|
||||
|
||||
$equipment = $this->equipmentService->getEquipmentById($id);
|
||||
|
||||
if (! $equipment) {
|
||||
return response()->json(['success' => false, 'message' => '설비를 찾을 수 없습니다.'], 404);
|
||||
}
|
||||
|
||||
$result = $this->photoService->uploadPhotos($equipment, $request->file('photos'));
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => "{$result['uploaded']}장 업로드 완료",
|
||||
'data' => [
|
||||
'uploaded' => $result['uploaded'],
|
||||
'errors' => $result['errors'],
|
||||
'photos' => $this->photoService->getPhotoUrls($equipment),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function photos(int $id): JsonResponse
|
||||
{
|
||||
$equipment = $this->equipmentService->getEquipmentById($id);
|
||||
|
||||
if (! $equipment) {
|
||||
return response()->json(['success' => false, 'message' => '설비를 찾을 수 없습니다.'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $this->photoService->getPhotoUrls($equipment),
|
||||
]);
|
||||
}
|
||||
|
||||
public function deletePhoto(int $id, int $fileId): JsonResponse
|
||||
{
|
||||
$equipment = $this->equipmentService->getEquipmentById($id);
|
||||
|
||||
if (! $equipment) {
|
||||
return response()->json(['success' => false, 'message' => '설비를 찾을 수 없습니다.'], 404);
|
||||
}
|
||||
|
||||
$deleted = $this->photoService->deletePhoto($equipment, $fileId);
|
||||
|
||||
if (! $deleted) {
|
||||
return response()->json(['success' => false, 'message' => '사진을 찾을 수 없습니다.'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '사진이 삭제되었습니다.',
|
||||
]);
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// 엑셀 Import
|
||||
// =========================================================================
|
||||
|
||||
public function importPreview(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'file' => 'required|file|mimes:xlsx,xls|max:10240',
|
||||
]);
|
||||
|
||||
try {
|
||||
$result = $this->importService->preview($request->file('file')->getRealPath());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $result,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
|
||||
public function importExecute(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'file' => 'required|file|mimes:xlsx,xls|max:10240',
|
||||
'duplicate_action' => 'in:skip,overwrite',
|
||||
]);
|
||||
|
||||
try {
|
||||
$result = $this->importService->import(
|
||||
$request->file('file')->getRealPath(),
|
||||
['duplicate_action' => $request->input('duplicate_action', 'skip')]
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => "Import 완료: 성공 {$result['success']}건, 실패 {$result['failed']}건, 건너뜀 {$result['skipped']}건",
|
||||
'data' => $result,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => $e->getMessage(),
|
||||
], 400);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,4 +94,13 @@ public function repairCreate(): View
|
||||
|
||||
return view('equipment.repairs.create', compact('equipmentList', 'users'));
|
||||
}
|
||||
|
||||
public function import(Request $request): View|Response
|
||||
{
|
||||
if ($request->header('HX-Request')) {
|
||||
return response('', 200)->header('HX-Redirect', route('equipment.import'));
|
||||
}
|
||||
|
||||
return view('equipment.import');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user