# B01_Dashboard 설계 및 구현 계획 **상태:** 기본 설계 완료. 역할별 대시보드 구축 및 회사 연결 기능 미구현 **참고:** 완료된 항목은 `.agent/complete_and_old/plan_register_split_COMPLETED.md` 참조 --- ## 1. B01_Dashboard 개편 사항 ### 1.1 페이지명 변경 - **이전:** B01_AccountDetail (계정 상세 페이지) - **현재:** B01_Dashboard (역할별 대시보드) - **목적:** 로그인 후 메인 랜딩 페이지로, 역할에 따라 다양한 정보 표시 ### 1.2 역할별 대시보드 구조 #### 🟡 **일반 사용자 (USER) 대시보드** ``` ┌─────────────────────────────────────────────────────┐ │ AISLO 대시보드 │ ├─────────────────────────────────────────────────────┤ │ │ │ 📊 [나의 프로젝트] │ │ • 프로젝트 리스트 (테이블) │ │ • 프로젝트명 | 지역 | 진행도 [워크플로우] | 수정일 │ │ • 클릭 시 해당 단계로 이동 │ │ • [+ 신규 프로젝트] 버튼 │ │ │ │ 🏢 [회사 정보] │ │ • 현재 소속 회사명 │ │ • 팀원 수, 진행 중인 프로젝트 수 │ │ │ │ 📞 [기본정보 & 보안] (섹션 병합) │ │ • 사용자명, 직급, 부서, 전화번호 수정 │ │ • 비밀번호 변경 │ │ │ └─────────────────────────────────────────────────────┘ ``` #### 🟢 **회사 관리자 (ADMIN) 대시보드** ``` ┌─────────────────────────────────────────────────────┐ │ AISLO 회사 관리자 대시보드 │ ├─────────────────────────────────────────────────────┤ │ │ │ 🏢 [회사 정보] │ │ 회사명: 테스트회사 | 사업자: 123-45-67890 │ │ 주소: 서울시 강남구 | 상태: 활동중 │ │ │ │ 👥 [팀원 관리] │ │ • 소속 사용자 리스트 (테이블) │ │ • 사용자명 | 직급 | 부서 | 역할 | 상태 │ │ • [+ 팀원 추가] (기가입 계정 검색/추가) │ │ • [제거] 버튼 │ │ │ │ 📋 [가입 요청] │ │ • 이 회사에 가입을 신청한 사용자 리스트 │ │ • [승인] [거절] 버튼 │ │ │ │ 📊 [회사 프로젝트] │ │ • 회사의 모든 프로젝트 리스트 (테이블) │ │ • 프로젝트명 | 소유자 | 진행도 | 상태 │ │ │ │ 📞 [기본정보 & 보안] │ │ • 사용자 이름, 직급, 전화번호 수정 │ │ • 비밀번호 변경 │ │ │ └─────────────────────────────────────────────────────┘ ``` #### 🔴 **시스템 관리자 (SYSTEM_ADMIN) 대시보드** ``` ┌────────────────────────────────────────────────────┐ │ AISLO 시스템 관리자 대시보드 │ ├────────────────────────────────────────────────────┤ │ │ │ 📊 [리소스 현황] │ │ CPU: 45% | MEM: 62% | DISK: 78% │ │ 활성 사용자: 23 | 활성 프로젝트: 15 │ │ │ │ 🏢 [회사 관리] │ │ • 전체 회사 리스트 (테이블) │ │ • 회사명 | 사업자번호 | 상태 | 사용자 수 │ │ • [+ 회사 추가] 버튼 │ │ │ │ 👤 [사용자 관리] │ │ • 전체 사용자 리스트 (테이블) │ │ • 이메일 | 이름 | 역할 | 회사 | 상태 │ │ • [역할 변경] [회사 할당] [비활성화] 버튼 │ │ │ │ 📝 [가입 요청 관리] │ │ • 회사 참여 대기 중인 요청 (테이블) │ │ • 신청자 | 회사명 | 신청일 | 상태 │ │ • [승인] [거절] 버튼 │ │ │ │ 📊 [시스템 로그] │ │ • 사용자 로그인/로그아웃 이력 (테이블) │ │ • 계정 생성/수정/삭제 이력 │ │ • 필터링: 기간, 사용자, 행동 유형 │ │ │ │ 🖥️ [하드웨어 리소스 모니터링] │ │ • CPU 사용량 그래프 (시간대별) │ │ • 메모리 사용량 그래프 │ │ • 디스크 사용량 그래프 │ │ • 동시 접속자 수 추이 │ │ │ └────────────────────────────────────────────────────┘ ``` --- ## 2. 핵심 기능 상세 설계 ### 2.1 프로젝트 리스트 섹션 (USER, ADMIN 공통) **테이블 구조:** | 프로젝트명 | 지역 | 진행도 | 워크플로우 | 수정일 | |----------|------|--------|----------|--------| | 2025년 산불진화임도 | 울진군 | 60% | [B03●●●○○○] | 2025-07-08 | **진행도 워크플로우:** ``` [B03 완료] → [B04 진행중 ▶] → [B05 대기] → [B06 대기] → ... ↑ (클릭하면 이동) (비활성화) ``` **동작:** - **완료/진행 중 단계:** 클릭 가능 → 해당 페이지로 이동 - **미진행 단계:** 비활성화 (회색), 클릭 불가 - **[+ 신규 프로젝트]:** 새 프로젝트 생성 페이지로 이동 (B02_ProjRegister) ### 2.2 회사 상태 섹션 (USER만 표시) **상태 표시:** ``` 회사 상태: 테스트회사 (활동중) [회사 변경] ← 다른 회사 검색 및 신청 ``` **동작:** - `status = 'NO_COMPANY'`: "회사 미연결" 표시 + [회사 생성] [회사 찾기] 버튼 - `status = 'PENDING'`: "가입 승인 대기 중 (OOO회사)" 표시 - `status = 'ACTIVE'`: "OOO회사 (ACTIVE)" 표시 + [회사 변경] 버튼 ### 2.3 팀원 관리 섹션 (ADMIN만 표시) **팀원 추가 동작:** 1. [+ 팀원 추가] 클릭 → 모달 팝업 노출 2. 기가입 사용자 검색 (이메일 입력) 3. 검색 결과 선택 → 회사에 추가 4. 중복 확인 (이미 팀원인 경우 "이미 팀원입니다" 메시지) **팀원 제거:** - [제거] 버튼 클릭 → 확인 팝업 → 회사에서 제거 ### 2.4 가입 요청 섹션 (ADMIN은 회사별, SYSTEM_ADMIN은 전체) **ADMIN 대시보드:** ``` 신청자 | 신청일 | 상태 김테스트 | 2025-07-08 | [승인] [거절] ``` **SYSTEM_ADMIN 대시보드:** ``` 신청자 | 회사명 | 신청일 | 상태 김테스트 | 테스트회사 | 2025-07-08 | [승인] [거절] ``` **동작:** - [승인]: `join_requests.status = 'APPROVED'` → `users.status = 'ACTIVE'` - [거절]: `join_requests.status = 'REJECTED'` → 이메일 발송 ### 2.5 인증 갱신 로직 **회원가입 시:** ``` 1. 회원가입 폼 입력 2. [가입하기] 클릭 3. 인증번호 입력 필드 노출 ← 가입 후에만 표시 4. 인증 완료 → users.auth_expires_at = NOW() + 3개월 설정 ``` **로그인 시:** ``` 1. 이메일/비밀번호 입력 2. DB 조회 후 auth_expires_at 확인 - auth_expires_at > NOW() → 바로 대시보드 이동 ✅ - auth_expires_at <= NOW() → 재인증 모달 표시 ⚠️ 3. 로그인 성공 → last_login = NOW() 업데이트 4. 다음 로그인 시 auth_expires_at = NOW() + 3개월로 연장 ``` **인증 만료 기준:** - **고정 기간 방식 (이전):** 가입일로부터 3개월 후 만료 (자주 사용하면 3개월 후 갑자기 재인증) - **로그인 기반 갱신 (현재):** 매 로그인 시 `auth_expires_at` 연장 (지속 사용 시 계속 유효) --- ## 3. DB 스키마 확장 ### 3.1 users 테이블 추가 컬럼 ```sql ALTER TABLE users ADD COLUMN ( role ENUM('SYSTEM_ADMIN', 'ADMIN', 'USER') DEFAULT 'USER', is_master BOOLEAN DEFAULT FALSE, status ENUM('PENDING_EMAIL', 'NO_COMPANY', 'PENDING', 'ACTIVE', 'INACTIVE', 'REJECTED') DEFAULT 'PENDING_EMAIL', last_login DATETIME DEFAULT NULL, auth_expires_at DATETIME DEFAULT NULL ); ``` ### 3.2 신규 테이블: join_requests ```sql CREATE TABLE join_requests ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, company_id INT NOT NULL, requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, status ENUM('PENDING', 'APPROVED', 'REJECTED') DEFAULT 'PENDING', reviewed_by INT DEFAULT NULL, reviewed_at TIMESTAMP DEFAULT NULL, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (company_id) REFERENCES companies(id), FOREIGN KEY (reviewed_by) REFERENCES users(id), UNIQUE KEY (user_id, company_id) -- 중복 신청 방지 ); ``` ### 3.3 신규 테이블: system_audit_logs ```sql CREATE TABLE system_audit_logs ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, action VARCHAR(50), -- LOGIN, LOGOUT, USER_CREATE, USER_UPDATE, COMPANY_CREATE, etc resource_type VARCHAR(50), -- user, company, project resource_id INT, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, ip_address VARCHAR(50), user_agent TEXT, FOREIGN KEY (user_id) REFERENCES users(id), INDEX (timestamp), INDEX (action) ); ``` ### 3.4 신규 테이블: system_resources ```sql CREATE TABLE system_resources ( id INT PRIMARY KEY AUTO_INCREMENT, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, cpu_usage_percent FLOAT, memory_usage_percent FLOAT, disk_usage_percent FLOAT, active_user_count INT, active_project_count INT, total_storage_mb FLOAT, INDEX (timestamp) ); ``` --- ## 4. 백엔드 API 설계 ### 4.1 B01_Dashboard 라우터 (`B01_Dashboard/B01_Dashboard_Router.py`) #### 공통 API ```python # GET /api/dashboard/me — 현재 사용자 정보 조회 @router.get("/api/dashboard/me") async def get_dashboard_me(session: dict = Depends(verify_session)): """ 현재 로그인한 사용자의 정보 반환 응답: { id, email, name, position, department, phone, company_id, role, is_master, status, last_login, auth_expires_at } """ # PATCH /api/dashboard/me — 사용자 정보 수정 @router.patch("/api/dashboard/me") async def patch_dashboard_me( request: UpdateUserRequest, session: dict = Depends(verify_session) ): """이름/직급/부서/전화번호 수정""" # 응답: 수정된 사용자 정보 ``` #### USER 역할 API ```python # GET /api/dashboard/user/projects — 사용자의 프로젝트 리스트 @router.get("/api/dashboard/user/projects") async def get_user_projects(session: dict = Depends(verify_session)): """ 사용자가 생성한 프로젝트 리스트 응답: [{id, name, region, status, progress_percent, created_at, updated_at}, ...] """ # GET /api/dashboard/user/company — 사용자의 회사 정보 @router.get("/api/dashboard/user/company") async def get_user_company(session: dict = Depends(verify_session)): """현재 소속 회사 정보""" # 응답: {id, name, status, user_count, project_count} # GET /api/dashboard/user/companies?q= — 회사 검색 @router.get("/api/dashboard/user/companies") async def search_companies_for_join( q: str = Query(...), session: dict = Depends(verify_session) ): """회사명 또는 사업자번호로 검색""" # 응답: [{id, name, business_number, status}, ...] # POST /api/dashboard/user/company/create — 신규 회사 생성 @router.post("/api/dashboard/user/company/create") async def create_company( request: CreateCompanyRequest, session: dict = Depends(verify_session) ): """ 신규 회사 생성 + 사용자를 ADMIN(is_master=TRUE)으로 자동 연결 - companies 테이블에 INSERT - users.company_id = new_company_id - users.role = 'ADMIN' - users.is_master = TRUE - users.status = 'ACTIVE' """ # 응답: {company_id, status='ACTIVE'} # POST /api/dashboard/user/company/join — 회사 참여 신청 @router.post("/api/dashboard/user/company/join") async def join_company( request: JoinCompanyRequest, session: dict = Depends(verify_session) ): """ 기존 회사에 참여 신청 - join_requests 테이블에 INSERT (status='PENDING') - users.status = 'PENDING' (관리자 승인 대기) - 관리자에게 이메일 발송 """ # 응답: {join_request_id, status='PENDING'} ``` #### ADMIN 역할 API ```python # GET /api/dashboard/admin/members — 회사 팀원 리스트 @router.get("/api/dashboard/admin/members") async def get_company_members(session: dict = Depends(verify_session)): """회사에 소속된 모든 팀원 조회""" # 응답: [{id, email, name, position, department, role, status}, ...] # POST /api/dashboard/admin/members — 팀원 추가 @router.post("/api/dashboard/admin/members") async def add_company_member( request: AddMemberRequest, session: dict = Depends(verify_session) ): """ 기가입 사용자를 회사에 추가 - users.company_id = session.company_id - users.status = 'ACTIVE' - 중복 확인 필수 """ # 응답: {user_id, status='ACTIVE'} # DELETE /api/dashboard/admin/members/{user_id} — 팀원 제거 @router.delete("/api/dashboard/admin/members/{user_id}") async def remove_company_member( user_id: int, session: dict = Depends(verify_session) ): """팀원을 회사에서 제거 (soft delete)""" # GET /api/dashboard/admin/join-requests — 가입 신청 리스트 @router.get("/api/dashboard/admin/join-requests") async def get_company_join_requests(session: dict = Depends(verify_session)): """이 회사에 가입을 신청한 사용자 리스트""" # 응답: [{id, user_id, user_email, user_name, requested_at, status}, ...] # PATCH /api/dashboard/admin/join-requests/{request_id} — 가입 신청 처리 @router.patch("/api/dashboard/admin/join-requests/{request_id}") async def process_join_request( request_id: int, request: ProcessJoinRequest, # {action: 'APPROVE' | 'REJECT'} session: dict = Depends(verify_session) ): """ 가입 신청 승인/거절 - APPROVE: join_requests.status='APPROVED', users.status='ACTIVE' - REJECT: join_requests.status='REJECTED' - 사용자에게 이메일 발송 """ # GET /api/dashboard/admin/projects — 회사 프로젝트 리스트 @router.get("/api/dashboard/admin/projects") async def get_company_projects(session: dict = Depends(verify_session)): """회사에 속한 모든 프로젝트 조회""" # 응답: [{id, name, owner, status, progress_percent}, ...] ``` #### SYSTEM_ADMIN 역할 API ```python # GET /api/dashboard/admin/companies — 전체 회사 리스트 @router.get("/api/dashboard/admin/companies") async def get_all_companies(session: dict = Depends(verify_session)): """시스템 관리자용: 전체 회사 조회""" # 응답: [{id, name, status, user_count, project_count, created_at}, ...] # GET /api/dashboard/admin/users — 전체 사용자 리스트 @router.get("/api/dashboard/admin/users") async def get_all_users(session: dict = Depends(verify_session)): """시스템 관리자용: 전체 사용자 조회""" # 응답: [{id, email, name, role, company_id, status, last_login}, ...] # PATCH /api/dashboard/admin/users/{user_id}/role — 사용자 역할 변경 @router.patch("/api/dashboard/admin/users/{user_id}/role") async def change_user_role( user_id: int, request: ChangeUserRoleRequest, session: dict = Depends(verify_session) ): """사용자 역할 변경 (USER ↔ ADMIN ↔ SYSTEM_ADMIN)""" # PATCH /api/dashboard/admin/users/{user_id}/company — 사용자 회사 할당 @router.patch("/api/dashboard/admin/users/{user_id}/company") async def assign_user_to_company( user_id: int, request: AssignCompanyRequest, session: dict = Depends(verify_session) ): """사용자를 다른 회사에 할당""" # GET /api/dashboard/admin/join-requests-all — 전체 가입 신청 @router.get("/api/dashboard/admin/join-requests-all") async def get_all_join_requests(session: dict = Depends(verify_session)): """시스템 관리자용: 전체 가입 신청 조회""" # PATCH /api/dashboard/admin/join-requests/{request_id}/approve — 시스템 레벨 승인 @router.patch("/api/dashboard/admin/join-requests/{request_id}/approve") async def system_approve_join_request( request_id: int, session: dict = Depends(verify_session) ): """시스템 관리자가 가입 신청 승인""" # GET /api/dashboard/admin/audit-logs — 시스템 로그 @router.get("/api/dashboard/admin/audit-logs") async def get_system_audit_logs( page: int = Query(1), limit: int = Query(50), start_date: str = Query(None), end_date: str = Query(None), user_id: int = Query(None), action: str = Query(None), session: dict = Depends(verify_session) ): """ 시스템 감시 로그 조회 (필터링 지원) 응답: {total, items: [{id, user_id, action, resource_type, timestamp}]} """ # GET /api/dashboard/admin/resources — 하드웨어 리소스 현황 @router.get("/api/dashboard/admin/resources") async def get_system_resources( period: str = Query('1h'), # 1h, 6h, 24h, 7d session: dict = Depends(verify_session) ): """ 시스템 리소스 사용량 조회 (그래프용) 응답: { current: {cpu_usage_percent, memory_usage_percent, disk_usage_percent}, history: [{timestamp, cpu_usage_percent, memory_usage_percent, ...}], stats: {active_user_count, active_project_count, total_storage_mb} } """ ``` ### 4.2 Schema 정의 (`B01_Dashboard/B01_Dashboard_Schema.py`) ```python from pydantic import BaseModel # 공통 class UpdateUserRequest(BaseModel): name: str position: str | None department: str | None phone: str | None # USER 역할 class CreateCompanyRequest(BaseModel): name: str business_registration_number: str business_address: str business_owner: str class JoinCompanyRequest(BaseModel): company_id: int class AddMemberRequest(BaseModel): email: str # 기가입 사용자의 이메일 class ProcessJoinRequest(BaseModel): action: str # 'APPROVE' | 'REJECT' reason: str | None = None # SYSTEM_ADMIN 역할 class ChangeUserRoleRequest(BaseModel): role: str # 'USER' | 'ADMIN' | 'SYSTEM_ADMIN' class AssignCompanyRequest(BaseModel): company_id: int | None = None # None이면 회사 미연결 ``` ### 4.3 Repository (`B01_Dashboard/B01_Dashboard_Repository.py`) Raw SQL을 이용한 데이터 조회/수정 함수들을 구현 --- ## 5. 프론트엔드 구현 필요 ### 5.1 B01_Dashboard_UI_Page.ts 전체 재설계 **구조:** ```typescript function DashboardPage() { const user = useAuth(); // role, status, company_id 포함 return (
{/* USER/ADMIN 공통: 프로젝트 리스트 */} {(user.role === 'USER' || user.role === 'ADMIN') && ( )} {/* USER만: 회사 정보 */} {user.role === 'USER' && ( )} {/* ADMIN만: 팀원 관리 */} {user.role === 'ADMIN' && ( <> )} {/* SYSTEM_ADMIN만: 시스템 관리 */} {user.role === 'SYSTEM_ADMIN' && ( <> )} {/* 모든 역할: 기본정보 & 보안 */}
); } ``` ### 5.2 B01_Dashboard_Api_Fetch.ts 신규 ```typescript // 공통 export async function fetchDashboardMe(): Promise; export async function updateUserProfile(request: UpdateUserRequest): Promise; // USER 역할 export async function fetchUserProjects(): Promise; export async function fetchUserCompany(): Promise; export async function searchCompanies(query: string): Promise; export async function createCompany(request: CreateCompanyRequest): Promise; export async function joinCompany(companyId: number): Promise; // ADMIN 역할 export async function fetchCompanyMembers(): Promise; export async function addCompanyMember(email: string): Promise; export async function removeCompanyMember(userId: number): Promise; export async function fetchCompanyJoinRequests(): Promise; export async function processJoinRequest(requestId: number, action: 'APPROVE' | 'REJECT'): Promise; export async function fetchCompanyProjects(): Promise; // SYSTEM_ADMIN 역할 export async function fetchAllCompanies(): Promise; export async function fetchAllUsers(filters?: UserFilters): Promise; export async function changeUserRole(userId: number, role: string): Promise; export async function assignUserToCompany(userId: number, companyId: number | null): Promise; export async function fetchAllJoinRequests(): Promise; export async function systemApproveJoinRequest(requestId: number): Promise; export async function fetchAuditLogs(filters?: AuditLogFilters): Promise; export async function fetchSystemResources(period: string): Promise; ``` ### 5.3 B01_Dashboard_UI_Style.css 확장 - 역할별 섹션 레이아웃 - 프로젝트 리스트 테이블 스타일 - 워크플로우 진행도 시각화 - 팝업 모달 스타일 - 그래프/차트 영역 스타일 ### 5.4 섹션별 컴포넌트 #### ProjectListSection - 프로젝트 테이블 (프로젝트명, 지역, 진행도, 워크플로우, 수정일) - [+ 신규 프로젝트] 버튼 - 워크플로우 단계 클릭 이벤트 처리 #### CompanyInfoSection (USER용) ``` 회사 상태: 테스트회사 (ACTIVE) | [회사 변경] 상태별 표시: - NO_COMPANY: "회사 미연결" + [회사 생성] [회사 찾기] - PENDING: "가입 승인 대기 중 (OOO회사)" - ACTIVE: "OOO회사 (ACTIVE)" + [회사 변경] ``` #### TeamMembersSection (ADMIN용) - 팀원 리스트 테이블 (이메일, 이름, 직급, 부서, 역할, 상태) - [+ 팀원 추가] 버튼 → 모달 팝업 - [제거] 버튼 #### JoinRequestsSection (ADMIN용) - 가입 신청 리스트 테이블 (신청자, 신청일, 상태) - [승인] [거절] 버튼 #### ResourceStatusSection (SYSTEM_ADMIN용) ``` CPU: 45% | MEM: 62% | DISK: 78% 활성 사용자: 23 | 활성 프로젝트: 15 ``` #### SystemAuditLogsSection (SYSTEM_ADMIN용) - 시스템 로그 테이블 (사용자, 행동, 대상, 타임스탬프) - 필터링: 기간, 사용자, 행동 유형 #### SystemResourcesGraphSection (SYSTEM_ADMIN용) - CPU 사용량 그래프 (시간대별) - 메모리 사용량 그래프 - 디스크 사용량 그래프 - 동시 접속자 수 추이 --- ## 6. 검증 방법 (다음 단계) ### 6.1 USER 역할 검증 1. 새 계정으로 로그인 → B01_Dashboard 진입 2. 프로젝트 리스트 표시 확인 3. 회사 상태: "회사 미연결" 표시 + [회사 생성] [회사 찾기] 버튼 확인 4. [회사 생성] → 모달 팝업 → 회사 정보 입력 → [생성] 5. DB 확인: - `users.company_id` 세팅 - `users.role = 'ADMIN'` (관리자 전환) - `users.is_master = TRUE` - `users.status = 'ACTIVE'` 6. 페이지 새로고침 → 회사명 표시 확인 ### 6.2 ADMIN 역할 검증 1. 회사 생성자(관리자)로 로그인 → B01_Dashboard 진입 2. 팀원 관리, 가입 요청, 회사 프로젝트 섹션 표시 확인 3. [+ 팀원 추가] → 모달 → 기가입 사용자 이메일 입력 → 검색 → 선택 → [추가] 4. DB 확인: - `users.company_id` 업데이트 - 중복 확인 (이미 팀원인 경우 오류 메시지) 5. 팀원 리스트에 새로운 팀원 표시 확인 ### 6.3 SYSTEM_ADMIN 역할 검증 1. 시스템 관리자 계정으로 로그인 → B01_Dashboard 진입 2. 전체 회사 리스트, 사용자 리스트, 시스템 로그, 리소스 현황 섹션 표시 확인 3. 리소스 현황 (CPU, MEM, DISK, 활성 사용자/프로젝트) 표시 확인 4. 사용자 역할 변경: USER → ADMIN 전환 → DB 확인 5. 시스템 로그 필터링 (기간, 사용자, 행동 유형) 동작 확인