From ac3b72cac630eca03269cf2e1809a5553fa8e500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EB=B3=B4=EA=B3=A4?= Date: Sat, 28 Feb 2026 07:45:30 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[approvals]=20=EA=B2=B0=EC=9E=AC?= =?UTF-8?q?=EC=84=A0=20=EC=97=90=EB=94=94=ED=84=B0=202=ED=8C=A8=EB=84=90?= =?UTF-8?q?=20UI/UX=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 좌측 패널: 부서별 인원 목록 (접이식 그룹핑, 검색 필터) - 우측 패널: 결재선 (SortableJS 드래그앤드롭 순서 변경) - 부서별 전체 인원 API 추가 (GET /api/admin/tenant-users/list) - 결재/합의/참조 유형별 요약 바 추가 - position_key → positions 테이블 조인으로 직위 라벨 표시 --- .../Api/Admin/TenantUserApiController.php | 76 ++++ resources/views/approvals/create.blade.php | 2 +- resources/views/approvals/edit.blade.php | 2 +- .../partials/_approval-line-editor.blade.php | 391 ++++++++++++------ routes/api.php | 1 + 5 files changed, 353 insertions(+), 119 deletions(-) diff --git a/app/Http/Controllers/Api/Admin/TenantUserApiController.php b/app/Http/Controllers/Api/Admin/TenantUserApiController.php index af840431..0ea49b70 100644 --- a/app/Http/Controllers/Api/Admin/TenantUserApiController.php +++ b/app/Http/Controllers/Api/Admin/TenantUserApiController.php @@ -50,4 +50,80 @@ public function search(Request $request): JsonResponse 'data' => $users, ]); } + + /** + * 테넌트 전체 인원 목록 (부서별 그룹핑, 결재선 에디터용) + */ + public function list(): JsonResponse + { + $tenantId = session('selected_tenant_id'); + + $users = DB::table('users') + ->join('user_tenants', function ($join) use ($tenantId) { + $join->on('users.id', '=', 'user_tenants.user_id') + ->where('user_tenants.tenant_id', $tenantId) + ->where('user_tenants.is_active', true); + }) + ->leftJoin('tenant_user_profiles as tp', function ($join) use ($tenantId) { + $join->on('tp.user_id', '=', 'users.id') + ->where('tp.tenant_id', $tenantId); + }) + ->leftJoin('departments', 'departments.id', '=', 'tp.department_id') + ->leftJoin('positions as pos_rank', function ($join) use ($tenantId) { + $join->on('pos_rank.tenant_id', '=', DB::raw($tenantId)) + ->where('pos_rank.type', 'rank') + ->whereRaw('pos_rank.`key` COLLATE utf8mb4_unicode_ci = tp.position_key COLLATE utf8mb4_unicode_ci'); + }) + ->leftJoin('positions as pos_title', function ($join) use ($tenantId) { + $join->on('pos_title.tenant_id', '=', DB::raw($tenantId)) + ->where('pos_title.type', 'title') + ->whereRaw('pos_title.`key` COLLATE utf8mb4_unicode_ci = tp.job_title_key COLLATE utf8mb4_unicode_ci'); + }) + ->whereNull('users.deleted_at') + ->orderBy('departments.name') + ->orderBy('users.name') + ->select([ + 'users.id', + 'users.name', + 'tp.department_id', + 'departments.name as department_name', + DB::raw('COALESCE(pos_rank.name, tp.position_key, \'\') as position'), + DB::raw('COALESCE(pos_title.name, tp.job_title_key, \'\') as job_title'), + ]) + ->get(); + + $grouped = $users->groupBy(fn ($u) => $u->department_id ?? 'none'); + + $data = []; + foreach ($grouped as $deptId => $deptUsers) { + $first = $deptUsers->first(); + $data[] = [ + 'department_id' => $deptId === 'none' ? null : (int) $deptId, + 'department_name' => $deptId === 'none' ? '미배정' : $first->department_name, + 'users' => $deptUsers->map(fn ($u) => [ + 'id' => $u->id, + 'name' => $u->name, + 'position' => $u->position, + 'job_title' => $u->job_title, + ])->values()->toArray(), + ]; + } + + // 미배정 그룹을 마지막으로 + usort($data, function ($a, $b) { + if ($a['department_id'] === null) { + return 1; + } + if ($b['department_id'] === null) { + return -1; + } + + return strcmp($a['department_name'], $b['department_name']); + }); + + return response()->json([ + 'success' => true, + 'data' => $data, + ]); + } } diff --git a/resources/views/approvals/create.blade.php b/resources/views/approvals/create.blade.php index ab12b7a1..08ba3cf2 100644 --- a/resources/views/approvals/create.blade.php +++ b/resources/views/approvals/create.blade.php @@ -52,7 +52,7 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline- {{-- 우측: 결재선 --}} -
+
@include('approvals.partials._approval-line-editor', [ 'lines' => $lines, 'initialSteps' => [], diff --git a/resources/views/approvals/edit.blade.php b/resources/views/approvals/edit.blade.php index 8a8d6d86..0e364157 100644 --- a/resources/views/approvals/edit.blade.php +++ b/resources/views/approvals/edit.blade.php @@ -74,7 +74,7 @@ class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-
{{-- 우측: 결재선 --}} -
+
@php $initialSteps = $approval->steps->map(fn($s) => [ 'user_id' => $s->approver_id, diff --git a/resources/views/approvals/partials/_approval-line-editor.blade.php b/resources/views/approvals/partials/_approval-line-editor.blade.php index 1e440849..07a46a9e 100644 --- a/resources/views/approvals/partials/_approval-line-editor.blade.php +++ b/resources/views/approvals/partials/_approval-line-editor.blade.php @@ -1,93 +1,190 @@ -{{-- 결재선 편집 컴포넌트 (Alpine.js) --}} -
-

결재선

+{{-- 결재선 편집 컴포넌트 (2패널 구조 — Alpine.js + SortableJS) --}} +
- {{-- 결재선 템플릿 선택 --}} -
- - +
+

결재선

- {{-- 결재자 추가 --}} -
- -
- + {{-- 2패널 컨테이너 --}} +
- {{-- 검색 결과 드롭다운 --}} -
-