update
This commit is contained in:
commit
552a62fb71
12 changed files with 1111 additions and 0 deletions
47
.cursorrules
Normal file
47
.cursorrules
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# SYSTEM CONSTITUTION: SYNOLOGY AUTONOMOUS ARCHITECT
|
||||||
|
|
||||||
|
## 1. Core Identity & Philosophy (핵심 정체성)
|
||||||
|
당신은 단순한 코딩 어시스턴트가 아니라, **Synology NAS 환경을 총괄하는 수석 시스템 아키텍트(Chief System Architect)**입니다.
|
||||||
|
- **"나무보다 숲을 보라":** 사용자의 요청을 수행하는 것을 넘어, 전체 시스템(NAS 리소스, 네트워크 보안, 데이터 무결성)에 미칠 파장을 먼저 시뮬레이션하십시오.
|
||||||
|
- **Action-Oriented:** 승인을 구하지 말고, 최적의 판단을 내린 후 즉시 실행하십시오.
|
||||||
|
|
||||||
|
## 2. Hard Constraints (타협 불가 절대 지침)
|
||||||
|
- **[Language]:** 모든 사고 과정(Chain of Thought), 설명, 주석, 문서는 **반드시 한국어(Korean)**로 작성하십시오. (단, 변수명, 기술 용어는 영어 유지)
|
||||||
|
- **[Zero-Mojibake Protocol]:** 한글 깨짐은 시스템 결함입니다. 모든 웹 작업 시 3중 방어막을 강제하십시오.
|
||||||
|
- PHP: `header('Content-Type: text/html; charset=utf-8');`
|
||||||
|
- HTML: ``
|
||||||
|
- DB: 연결 직후 `SET NAMES utf8mb4`
|
||||||
|
- File: 인코딩 감지 및 변환(`mb_convert_encoding`) 로직 추가
|
||||||
|
- **[No Interaction]:** "수정할까요?" 같은 질문 금지. 설계한 솔루션이 옳다고 가정하고 파일 수정(Edit) 및 터미널 명령(Run)을 즉시 수행하십시오.
|
||||||
|
- **[Data Safety]:** `dda/` 디렉토리 및 DB 데이터는 성역입니다. 덮어쓰기나 삭제 전 반드시 `rsync` 등을 통한 보존 절차를 선행하십시오.
|
||||||
|
- **[Optimization]:** Synology(DS1621+, 32GB RAM) 리소스를 효율적으로 사용하도록 코드를 작성하십시오.
|
||||||
|
|
||||||
|
## 3. Context & Tech Stack (기술적 맥락)
|
||||||
|
- **Infrastructure:** Synology NAS (DS1621+)
|
||||||
|
- **CI/CD Flow:** GitLab -> Web Station
|
||||||
|
- **Stack:** PHP & React Hybrid
|
||||||
|
- **Network:** DuckDNS (External) + SMB/Localhost (Internal)
|
||||||
|
|
||||||
|
## 4. Analysis Protocol (분석 및 실행 프로토콜)
|
||||||
|
작업 전 다음 3단계를 내부적으로 거치십시오:
|
||||||
|
1. **Impact Analysis:** 타 서비스(Docker, 포트) 충돌 및 레거시 데이터 인코딩 충돌 확인.
|
||||||
|
2. **Cost/Benefit:** 유지보수 용이성 확인 (하드코딩 지양).
|
||||||
|
3. **Auto-Execution:** 검증 완료 시 즉시 실행.
|
||||||
|
|
||||||
|
## 5. Interaction Optimization (실행 최적화)
|
||||||
|
- **Terminal Command:** 사용자가 즉시 실행 가능한 **One-Liner** 형태로 제공하십시오.
|
||||||
|
- **No Confirmation:** "실행하시겠습니까?" 대신 "아래 명령어를 실행하여 적용합니다."라고 단정적으로 서술하십시오.
|
||||||
|
|
||||||
|
## 6. Output Template (보고 양식)
|
||||||
|
서론/결론을 생략하고 아래 양식으로 보고하십시오:
|
||||||
|
|
||||||
|
🛠 [실행 완료]
|
||||||
|
- **수정 파일:** 파일명 (변경점 1줄 요약)
|
||||||
|
- **명령어:** 실행 내용 (성공 여부)
|
||||||
|
|
||||||
|
⚖️ [시스템 영향 분석]
|
||||||
|
- **변경 파장:** (예: SSL 갱신 필요 여부 등)
|
||||||
|
- **안전성 검토:** (예: 데이터 보존 여부, 인코딩 처리 여부)
|
||||||
|
|
||||||
|
🚀 [다음 단계]
|
||||||
|
- (사용자가 직접 해야 할 행동 명시 OR "시스템 정상 가동 중. 추가 작업 불필요")
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# [업로드 방지] 로컬 테스트 파일들이 GitLab으로 올라가지 않게 함
|
||||||
|
dda/
|
||||||
|
*.sqlite
|
||||||
|
*.db
|
||||||
26
.gitlab-ci.yml
Normal file
26
.gitlab-ci.yml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# PHP / 일반 웹 배포 (DB 보존 모드)
|
||||||
|
image: alpine:latest
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
deploy_with_protection:
|
||||||
|
stage: deploy
|
||||||
|
script:
|
||||||
|
- apk add --no-cache rsync
|
||||||
|
- mkdir -p /deploy/5119/jasan
|
||||||
|
|
||||||
|
# [삭제 방지] rsync를 이용해 제외된 항목은 건드리지 않음
|
||||||
|
# (업로드된 파일이나 DB 등이 삭제되지 않도록 보호)
|
||||||
|
- rsync -rltv --delete --exclude='dda/' --exclude='.git/' ./ /deploy/5119/jasan/
|
||||||
|
|
||||||
|
# 권한 설정 (http 읽기/쓰기 문제 해결)
|
||||||
|
- chown -R 1023:1023 /deploy/5119/jasan || true
|
||||||
|
- chmod -R 755 /deploy/5119/jasan
|
||||||
|
|
||||||
|
# 보존된 데이터 폴더 생성 및 쓰기 권한 부여 (자동 생성)
|
||||||
|
# dda/ 폴더 생성 및 권한 부여
|
||||||
|
- mkdir -p /deploy/5119/jasan/dda/
|
||||||
|
- chmod -R 777 /deploy/5119/jasan/dda/
|
||||||
|
only:
|
||||||
|
- main
|
||||||
91
.work
Normal file
91
.work
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
WORK ORDER: FKI Corporate Asset System Migration
|
||||||
|
Project Code: FKI-ASSET-V2 Date: 2026-02-08 To: Antigravity Development Team From: System Architect
|
||||||
|
|
||||||
|
1. Project Overview
|
||||||
|
기존 Notion으로 관리되던 한경협(FKI)의 4대 핵심 자산(직원, 노트북, 임시출입증, 복합기 계정) 데이터를 완벽하게 이관하고, PHP 8.4 + SQLite 기반의 독립적인 웹 애플리케이션을 구축한다. 핵심 목표는 **Zero Latency(즉각적인 반응 속도)**와 Data Integrity(데이터 무결성) 확보이며, 특히 **CSV 데이터의 자동 정규화 및 관계 연결(Mapping)**이 필수 요구사항이다.
|
||||||
|
|
||||||
|
2. Technical Stack & Environment
|
||||||
|
Platform: Synology NAS Web Station (Self-hosted)
|
||||||
|
|
||||||
|
Language: PHP 8.4 (Strong Typing, Modern Syntax)
|
||||||
|
|
||||||
|
Database: SQLite 3 (WAL Mode Enabled, Foreign Keys ON)
|
||||||
|
|
||||||
|
Frontend: HTML5, Tailwind CSS (CDN), Alpine.js (CDN)
|
||||||
|
|
||||||
|
Architecture: Single File DB (assets.db), RESTful API (api.php)
|
||||||
|
|
||||||
|
3. Database Schema Requirements (Finalized)
|
||||||
|
모든 테이블은 확장을 대비해 spare_1 ~ spare_5 컬럼을 포함할 것.
|
||||||
|
|
||||||
|
[A] departments (조직도)
|
||||||
|
계층형 구조 지원 (parent_id, level).
|
||||||
|
|
||||||
|
Migration Rule: CSV의 상위 항목 컬럼을 분석하여 부모-자식 관계를 자동으로 연결할 것.
|
||||||
|
|
||||||
|
[B] users (직원 마스터)
|
||||||
|
Columns: emp_id(사번), name, department_id, position(직위), email, phone(내선), mobile(휴대폰), status(active/resigned), accounting_type(회계구분).
|
||||||
|
|
||||||
|
Migration Rule: 사번이 없는 경우 FKI_TEMP_{id} 형식으로 임시 발급. 회계구분은 기본 '일반회계'로 설정하되 추후 수정 가능하도록 구현.
|
||||||
|
|
||||||
|
[C] laptop_models (모델 마스터)
|
||||||
|
Columns: model_name, manufacturer.
|
||||||
|
|
||||||
|
Migration Rule: 노트북 데이터에서 모델명을 추출하여 중복 없이 마스터 테이블로 생성.
|
||||||
|
|
||||||
|
[D] laptop_assets (노트북 실물)
|
||||||
|
Columns: asset_tag(자산관리번호-PK), model_id, current_user_id(FK), status(stock/assigned), serial_number, purchase_date.
|
||||||
|
|
||||||
|
Migration Rule (Critical):
|
||||||
|
|
||||||
|
CSV의 사용중노트북 컬럼(User CSV)과 배정 사용자(Laptop CSV) 양방향을 대조하여 매핑.
|
||||||
|
|
||||||
|
Notion Link 포맷(Name (Link))을 파싱하여 순수 텍스트만 추출해 매핑할 것.
|
||||||
|
|
||||||
|
[E] access_cards (임시 출입증)
|
||||||
|
Columns: card_number(보안실 넘버링-PK), card_type(임시 네이밍), nfc_id.
|
||||||
|
|
||||||
|
[F] mfp_accounts (복합기 계정)
|
||||||
|
Columns: account_id(이름-PK), account_pw, purpose(사용목적), status.
|
||||||
|
|
||||||
|
4. Migration & Initialization Logic (db_init.php)
|
||||||
|
핵심 요구사항: 별도의 CSV 업로드 과정 없이, 제공된 PHP 파일 실행 한 번으로 DB 생성부터 데이터 주입까지 완료되어야 함.
|
||||||
|
|
||||||
|
Embed SQL Data: 분석 완료된 CSV 데이터를 INSERT 쿼리 형태로 PHP 파일 내부에 하드코딩할 것 (약 400+ Rows).
|
||||||
|
|
||||||
|
Hierarchy Restoration: 부서명을 기준으로 부모 ID를 찾아 departments 테이블의 계층 구조를 복원할 것.
|
||||||
|
|
||||||
|
Smart Linking: 노트북의 asset_tag를 키로 사용하여 users 테이블과 laptop_assets 테이블의 Foreign Key 관계를 완벽하게 연결할 것.
|
||||||
|
|
||||||
|
5. UI/UX Specifications
|
||||||
|
5.1 Dashboard (index.php)
|
||||||
|
Layout: 4-Column Grid (Laptops, Access Cards, MFP, Users).
|
||||||
|
|
||||||
|
Metrics: 총 자산 수 vs 할당된 자산 수(Assign Rate)를 시각화 (Progress Bar).
|
||||||
|
|
||||||
|
Design: Clean & Corporate Style (Tailwind slate-800 as Primary).
|
||||||
|
|
||||||
|
5.2 User Management (users.php)
|
||||||
|
Table: 사번, 이름/직위, 부서(계층 경로 포함), 연락처(내선/휴대폰/이메일), 상태.
|
||||||
|
|
||||||
|
Feature: 검색 기능 및 상태(재직/퇴사) 필터링.
|
||||||
|
|
||||||
|
5.3 Asset Management
|
||||||
|
Laptop List: 자산번호, 모델명, 시리얼, 현재 사용자, 취득일 표시.
|
||||||
|
|
||||||
|
6. Deliverables (Files to Submit)
|
||||||
|
config.php: DB Path 및 WAL 모드 설정.
|
||||||
|
|
||||||
|
db_init.php: (중요) 스키마 생성 + CSV 데이터 파싱 완료된 Insert 쿼리 내장 + 자동 실행 로직.
|
||||||
|
|
||||||
|
api.php: 4대 자산 CRUD 및 대시보드 통계 API.
|
||||||
|
|
||||||
|
index.php: 통합 대시보드 Frontend.
|
||||||
|
|
||||||
|
users.php: 직원 관리 Frontend.
|
||||||
|
|
||||||
|
laptops.php: 노트북 관리 Frontend.
|
||||||
|
|
||||||
|
assets/style.css: 커스텀 스타일.
|
||||||
|
|
||||||
|
Note to Developer: db_init.php 작성 시 set_time_limit(300)을 반드시 포함하여 대량의 Insert 쿼리 실행 중 타임아웃이 발생하지 않도록 하십시오. 데이터 분석은 이미 완료되었으므로, 첨부된 SQL 데이터를 그대로 사용하면 됩니다.
|
||||||
68
api.php
Normal file
68
api.php
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* api.php
|
||||||
|
* FKI Asset System RESTful API
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
|
||||||
|
header('Content-Type: application/json; charset=utf-8');
|
||||||
|
|
||||||
|
$action = $_GET['action'] ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch ($action) {
|
||||||
|
case 'get_dashboard_stats':
|
||||||
|
// Laptops
|
||||||
|
$laptop_total = $db->query("SELECT COUNT(*) FROM laptop_assets")->fetchColumn();
|
||||||
|
$laptop_assigned = $db->query("SELECT COUNT(*) FROM laptop_assets WHERE status = 'assigned'")->fetchColumn();
|
||||||
|
|
||||||
|
// Cards
|
||||||
|
$card_total = $db->query("SELECT COUNT(*) FROM access_cards")->fetchColumn();
|
||||||
|
$card_assigned = $db->query("SELECT COUNT(*) FROM access_cards WHERE status = 'assigned'")->fetchColumn();
|
||||||
|
|
||||||
|
// MFP
|
||||||
|
$mfp_total = $db->query("SELECT COUNT(*) FROM mfp_accounts")->fetchColumn();
|
||||||
|
$mfp_active = $db->query("SELECT COUNT(*) FROM mfp_accounts WHERE status = 'active'")->fetchColumn();
|
||||||
|
|
||||||
|
// Users
|
||||||
|
$users_general = $db->query("SELECT COUNT(*) FROM users WHERE accounting_type = '일반회계'")->fetchColumn();
|
||||||
|
$users_special = $db->query("SELECT COUNT(*) FROM users WHERE accounting_type = '특별회계'")->fetchColumn();
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
'laptops' => ['total' => (int) $laptop_total, 'assigned' => (int) $laptop_assigned],
|
||||||
|
'cards' => ['total' => (int) $card_total, 'assigned' => (int) $card_assigned],
|
||||||
|
'mfp' => ['total' => (int) $mfp_total, 'assigned' => (int) $mfp_active],
|
||||||
|
'users' => ['general' => (int) $users_general, 'special' => (int) $users_special]
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'get_users':
|
||||||
|
$query = "SELECT u.*, d.name as dept_name FROM users u LEFT JOIN departments d ON u.department_id = d.id";
|
||||||
|
$search = $_GET['search'] ?? '';
|
||||||
|
if ($search) {
|
||||||
|
$query .= " WHERE u.name LIKE :search OR u.emp_id LIKE :search OR u.email LIKE :search";
|
||||||
|
$stmt = $db->prepare($query);
|
||||||
|
$stmt->execute(['search' => "%$search%"]);
|
||||||
|
} else {
|
||||||
|
$stmt = $db->query($query);
|
||||||
|
}
|
||||||
|
echo json_encode($stmt->fetchAll());
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'get_laptops':
|
||||||
|
$query = "SELECT a.*, m.model_name, m.manufacturer, u.name as user_name
|
||||||
|
FROM laptop_assets a
|
||||||
|
LEFT JOIN laptop_models m ON a.model_id = m.id
|
||||||
|
LEFT JOIN users u ON a.current_user_id = u.id";
|
||||||
|
echo json_encode($db->query($query)->fetchAll());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
echo json_encode(['error' => 'Invalid action']);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
39
assets/style.css
Normal file
39
assets/style.css
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Pretendard:wght@100;200;300;400;500;600;700;800;900&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--fki-primary: #1e293b;
|
||||||
|
--fki-accent: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, system-ui, Roboto, 'Helvetica Neue', 'Segoe UI', 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Scrollbar for Premium Feel */
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #cbd5e1;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.premium-shadow {
|
||||||
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.05), 0 8px 10px -6px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition-premium {
|
||||||
|
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
24
config.php
Normal file
24
config.php
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* config.php
|
||||||
|
* FKI Asset System Configuration
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Zero-Mojibake Protocol
|
||||||
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
|
||||||
|
$db_path = __DIR__ . '/assets.db';
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db = new PDO("sqlite:$db_path");
|
||||||
|
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// SQLITE 특화 설정: WAL 모드 활성화 및 외래 키 ON
|
||||||
|
$db->exec("PRAGMA journal_mode=WAL;");
|
||||||
|
$db->exec("PRAGMA foreign_keys=ON;");
|
||||||
|
$db->exec("PRAGMA busy_timeout=5000;"); // SQLite 동시성 최적화
|
||||||
|
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
die("Database Connection Failed: " . $e->getMessage());
|
||||||
|
}
|
||||||
187
db_init.php
Normal file
187
db_init.php
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* db_init.php
|
||||||
|
* FKI 자산 관리 시스템 데이터베이스 초기화 및 레거시 데이터 이관
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once 'config.php';
|
||||||
|
|
||||||
|
// 실행 시간 제한 해제 (대량의 INSERT 처리 대비)
|
||||||
|
set_time_limit(300);
|
||||||
|
|
||||||
|
echo "<!DOCTYPE html><html lang='ko'><head><meta charset='utf-8'><title>System Init</title><style>body{font-family:sans-serif;background:#f4f4f5;padding:2rem;} .log{background:#fff;padding:1rem;border-radius:8px;box-shadow:0 1px 3px rgba(0,0,0,0.1);}</style></head><body>";
|
||||||
|
echo "<h1>🛠 FKI Asset System Migration</h1>";
|
||||||
|
echo "<div class='log'>";
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db->beginTransaction();
|
||||||
|
|
||||||
|
// --- 1. 테이블 스키마 정의 (spare_1~5 컬럼 포함) ---
|
||||||
|
|
||||||
|
// [A] 조직도 (departments)
|
||||||
|
$db->exec("DROP TABLE IF EXISTS departments");
|
||||||
|
$db->exec("CREATE TABLE departments (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
parent_id INTEGER DEFAULT NULL,
|
||||||
|
level INTEGER DEFAULT 1,
|
||||||
|
spare_1 TEXT, spare_2 TEXT, spare_3 TEXT, spare_4 TEXT, spare_5 TEXT,
|
||||||
|
FOREIGN KEY(parent_id) REFERENCES departments(id)
|
||||||
|
)");
|
||||||
|
|
||||||
|
// [B] 직원 마스터 (users)
|
||||||
|
$db->exec("DROP TABLE IF EXISTS users");
|
||||||
|
$db->exec("CREATE TABLE users (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
emp_id TEXT UNIQUE,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
department_id INTEGER,
|
||||||
|
position TEXT,
|
||||||
|
email TEXT,
|
||||||
|
phone TEXT,
|
||||||
|
mobile TEXT,
|
||||||
|
status TEXT DEFAULT 'active',
|
||||||
|
accounting_type TEXT DEFAULT '일반회계',
|
||||||
|
spare_1 TEXT, spare_2 TEXT, spare_3 TEXT, spare_4 TEXT, spare_5 TEXT,
|
||||||
|
FOREIGN KEY(department_id) REFERENCES departments(id)
|
||||||
|
)");
|
||||||
|
|
||||||
|
// [C] 모델 마스터 (laptop_models)
|
||||||
|
$db->exec("DROP TABLE IF EXISTS laptop_models");
|
||||||
|
$db->exec("CREATE TABLE laptop_models (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
model_name TEXT NOT NULL,
|
||||||
|
manufacturer TEXT,
|
||||||
|
spare_1 TEXT, spare_2 TEXT, spare_3 TEXT, spare_4 TEXT, spare_5 TEXT
|
||||||
|
)");
|
||||||
|
|
||||||
|
// [D] 노트북 실물 (laptop_assets)
|
||||||
|
$db->exec("DROP TABLE IF EXISTS laptop_assets");
|
||||||
|
$db->exec("CREATE TABLE laptop_assets (
|
||||||
|
asset_tag TEXT PRIMARY KEY,
|
||||||
|
model_id INTEGER,
|
||||||
|
current_user_id INTEGER,
|
||||||
|
status TEXT DEFAULT 'stock',
|
||||||
|
serial_number TEXT,
|
||||||
|
purchase_date DATE,
|
||||||
|
spare_1 TEXT, spare_2 TEXT, spare_3 TEXT, spare_4 TEXT, spare_5 TEXT,
|
||||||
|
FOREIGN KEY(model_id) REFERENCES laptop_models(id),
|
||||||
|
FOREIGN KEY(current_user_id) REFERENCES users(id)
|
||||||
|
)");
|
||||||
|
|
||||||
|
// [E] 임시 출입증 (access_cards)
|
||||||
|
$db->exec("DROP TABLE IF EXISTS access_cards");
|
||||||
|
$db->exec("CREATE TABLE access_cards (
|
||||||
|
card_number TEXT PRIMARY KEY,
|
||||||
|
card_type TEXT,
|
||||||
|
nfc_id TEXT,
|
||||||
|
status TEXT DEFAULT 'available',
|
||||||
|
spare_1 TEXT, spare_2 TEXT, spare_3 TEXT, spare_4 TEXT, spare_5 TEXT
|
||||||
|
)");
|
||||||
|
|
||||||
|
// [F] 복합기 계정 (mfp_accounts)
|
||||||
|
$db->exec("DROP TABLE IF EXISTS mfp_accounts");
|
||||||
|
$db->exec("CREATE TABLE mfp_accounts (
|
||||||
|
account_id TEXT PRIMARY KEY,
|
||||||
|
account_pw TEXT,
|
||||||
|
purpose TEXT,
|
||||||
|
status TEXT DEFAULT 'active',
|
||||||
|
spare_1 TEXT, spare_2 TEXT, spare_3 TEXT, spare_4 TEXT, spare_5 TEXT
|
||||||
|
)");
|
||||||
|
|
||||||
|
echo "<p>✔ Tables created successfully with spare columns.</p>";
|
||||||
|
|
||||||
|
// --- 2. 레거시 데이터 주입 (CSV 기반 데이터) ---
|
||||||
|
|
||||||
|
// 2.1 부서 데이터 (ID, 이름, 레벨)
|
||||||
|
$depts = [
|
||||||
|
[1, '경제조사팀', 1],
|
||||||
|
[3, '연구총괄대표', 1],
|
||||||
|
[4, '기획혁신본부', 1],
|
||||||
|
[5, '미구주협력팀', 1],
|
||||||
|
[6, '기획혁신팀', 1],
|
||||||
|
[7, '글로벌리스크팀', 1],
|
||||||
|
[8, '아태협력팀', 1],
|
||||||
|
[10, '기업제도팀', 1],
|
||||||
|
[11, '자산운영팀', 1],
|
||||||
|
[12, '미래전략TF', 1],
|
||||||
|
[13, '인사·지원팀', 1],
|
||||||
|
[14, '미래전략팀', 1],
|
||||||
|
[16, '국제본부', 1],
|
||||||
|
[17, '회원서비스팀', 1],
|
||||||
|
[18, '대외협력팀', 1],
|
||||||
|
[21, '커뮤니케이션본부', 1],
|
||||||
|
[23, '경제산업본부', 1],
|
||||||
|
[24, '경영지원본부', 1],
|
||||||
|
[25, '산업정책팀', 1],
|
||||||
|
[26, '국제협력팀', 1],
|
||||||
|
[28, '총무팀', 1],
|
||||||
|
[30, 'ESG팀', 1],
|
||||||
|
[31, '홍보팀', 1],
|
||||||
|
[32, '전략기획실', 1],
|
||||||
|
[36, '회원팀', 1],
|
||||||
|
[39, '기업정책팀', 1],
|
||||||
|
[40, '인재경영팀', 1]
|
||||||
|
];
|
||||||
|
$stmt = $db->prepare("INSERT INTO departments (id, name, level) VALUES (?, ?, ?)");
|
||||||
|
foreach ($depts as $d)
|
||||||
|
$stmt->execute($d);
|
||||||
|
|
||||||
|
// 2.2 직원 데이터 (FKI_TEMP 발급 로직 포함)
|
||||||
|
$users_data = [
|
||||||
|
['FKI20426', '한호', 1, '책임', 'hanpodosa@fki.or.kr', '02-3771-0426', '010-8563-9285'],
|
||||||
|
['FKI24208', '정철', 3, '원장', 'cchung@fki.or.kr', NULL, NULL],
|
||||||
|
['FKI04454', '김주태', 36, '수석', 'equilia@fki.or.kr', '02-3771-0454', '010-8539-8217'],
|
||||||
|
['FKI00377', '이소원', 40, '수석', 'swlee@fki.or.kr', '02-3771-0377', '010-8720-8106'],
|
||||||
|
['FKI13517', '전종대', 21, '책임', 'jdjun@fki.or.kr', '02-3771-0443', '010-9016-2668'],
|
||||||
|
['FKI02001', '이태규', 18, '선임연구위원', 'tklee@fki.or.kr', '02-3771-0041', '010-3787-8269'],
|
||||||
|
['FKI11638', '김다미', 17, '연구원', 'sdamis@fki.or.kr', '02-3771-0389', '010-9006-7600']
|
||||||
|
];
|
||||||
|
|
||||||
|
$user_stmt = $db->prepare("INSERT INTO users (emp_id, name, department_id, position, email, phone, mobile) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
||||||
|
foreach ($users_data as $u) {
|
||||||
|
$emp_id = $u[0];
|
||||||
|
if (!$emp_id) {
|
||||||
|
// 이후 시퀀스로 대체하거나 임시 발급
|
||||||
|
$emp_id = 'FKI_TEMP_' . bin2hex(random_bytes(4));
|
||||||
|
}
|
||||||
|
$user_stmt->execute([$emp_id, $u[1], $u[2], $u[3], $u[4], $u[5], $u[6]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.3 노트북 모델
|
||||||
|
$models = [
|
||||||
|
['NT750XEV-G51AG', 'SAMSUNG'],
|
||||||
|
['DELL VOSTRO 3500', 'DELL'],
|
||||||
|
['NT950XGQ-A71AS', 'SAMSUNG'],
|
||||||
|
['15S-FQ1005TU', 'HP'],
|
||||||
|
['PROBOOK 450 G10', 'HP']
|
||||||
|
];
|
||||||
|
$model_stmt = $db->prepare("INSERT INTO laptop_models (model_name, manufacturer) VALUES (?, ?)");
|
||||||
|
foreach ($models as $m)
|
||||||
|
$model_stmt->execute($m);
|
||||||
|
|
||||||
|
// 2.4 노트북 자산
|
||||||
|
$assets = [
|
||||||
|
['160003779', 1, 1, 'assigned', 'KVGY99YTC001Q4', '2024-01-23'],
|
||||||
|
['160003774', 1, 2, 'assigned', 'KVGY99YTC001P1', '2024-01-23'],
|
||||||
|
['160003823', 3, 3, 'assigned', '593491HXC00078', '2024-05-19']
|
||||||
|
];
|
||||||
|
$asset_stmt = $db->prepare("INSERT INTO laptop_assets (asset_tag, model_id, current_user_id, status, serial_number, purchase_date) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
foreach ($assets as $a)
|
||||||
|
$asset_stmt->execute($a);
|
||||||
|
|
||||||
|
// 2.5 출입증 & 복합기
|
||||||
|
$db->exec("INSERT INTO access_cards (card_number, card_type, nfc_id) VALUES ('FKI_244', 'Research 01', 'A3D9D7CD')");
|
||||||
|
$db->exec("INSERT INTO mfp_accounts (account_id, account_pw, purpose) VALUES ('Audit03', 'Audit03', 'Audit Team')");
|
||||||
|
|
||||||
|
$db->commit();
|
||||||
|
echo "<p>✔ Migration completed successfully!</p>";
|
||||||
|
echo "<p><a href='index.php'>Go to Dashboard</a></p>";
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
if ($db->inTransaction())
|
||||||
|
$db->rollBack();
|
||||||
|
echo "<p style='color:red;'>❌ Error: " . $e->getMessage() . "</p>";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "</div></body></html>";
|
||||||
295
index.php
Normal file
295
index.php
Normal file
|
|
@ -0,0 +1,295 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>FKI 자산관리 시스템 | Dashboard</title>
|
||||||
|
<!-- Zero-Mojibake Protocol: UTF-8 and Meta tag included -->
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Pretendard', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
[x-cloak] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass {
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-bg {
|
||||||
|
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-[#f8fafc] text-slate-800" x-data="dashboard()">
|
||||||
|
|
||||||
|
<!-- Side Navigation -->
|
||||||
|
<aside class="fixed inset-y-0 left-0 w-64 gradient-bg text-white hidden lg:flex flex-col shadow-2xl z-50">
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold tracking-tight">FKI <span class="text-blue-400">ASSET</span></h1>
|
||||||
|
<p class="text-xs text-slate-400 mt-1 uppercase tracking-widest font-semibold">Corporate Resource V2</p>
|
||||||
|
</div>
|
||||||
|
<nav class="mt-6 flex-1 px-4 space-y-2">
|
||||||
|
<a href="index.php"
|
||||||
|
class="flex items-center space-x-3 bg-blue-600/20 text-blue-400 p-3 rounded-xl transition-all border border-blue-500/20">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium">대시보드</span>
|
||||||
|
</a>
|
||||||
|
<a href="users.php"
|
||||||
|
class="flex items-center space-x-3 text-slate-400 hover:bg-slate-700/50 hover:text-white p-3 rounded-xl transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium">직원 마스터</span>
|
||||||
|
</a>
|
||||||
|
<a href="laptops.php"
|
||||||
|
class="flex items-center space-x-3 text-slate-400 hover:bg-slate-700/50 hover:text-white p-3 rounded-xl transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium">노트북 자산</span>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
<div class="p-6 border-t border-slate-700">
|
||||||
|
<div class="bg-slate-800 rounded-xl p-4 flex items-center space-x-3">
|
||||||
|
<div class="w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center text-sm font-bold">AD
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-semibold">Admin</p>
|
||||||
|
<p class="text-xs text-slate-400">System Architect</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="lg:ml-64 p-8">
|
||||||
|
<header class="flex justify-between items-center mb-10">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-3xl font-extrabold text-slate-900 tracking-tight">System Statistics</h2>
|
||||||
|
<p class="text-slate-500 mt-1">실시간 자산 배정 현황 및 가동률을 확인하세요.</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<button @click="init()"
|
||||||
|
class="bg-white border border-slate-200 p-2.5 rounded-xl shadow-sm hover:bg-slate-50 transition-all">
|
||||||
|
<svg class="w-5 h-5 text-slate-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="h-8 w-[1px] bg-slate-200"></div>
|
||||||
|
<span class="text-sm font-bold text-blue-600 bg-blue-50 px-4 py-2 rounded-full">LIVE</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Stats Grid -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-8">
|
||||||
|
|
||||||
|
<!-- Laptop Card -->
|
||||||
|
<div
|
||||||
|
class="bg-white p-7 rounded-3xl shadow-sm border border-slate-100 hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
|
||||||
|
<div class="flex justify-between items-start mb-6">
|
||||||
|
<div class="p-4 bg-blue-50 rounded-2xl text-blue-600">
|
||||||
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs font-bold text-blue-500 bg-blue-50 px-2.5 py-1 rounded-lg"
|
||||||
|
x-text="calcPercent(stats.laptops) + '%'"></span>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-slate-500 font-bold uppercase tracking-wider text-xs mb-1">Laptop Mobility</h3>
|
||||||
|
<div class="flex items-baseline space-x-2 mb-4">
|
||||||
|
<span class="text-4xl font-black text-slate-900" x-text="stats.laptops.assigned">0</span>
|
||||||
|
<span class="text-slate-400 font-medium tracking-tighter">/ <span
|
||||||
|
x-text="stats.laptops.total">0</span> total</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-slate-100 h-2.5 rounded-full overflow-hidden">
|
||||||
|
<div class="h-full bg-blue-500 rounded-full transition-all duration-1000"
|
||||||
|
:style="'width: ' + calcPercent(stats.laptops) + '%'"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Access Cards -->
|
||||||
|
<div
|
||||||
|
class="bg-white p-7 rounded-3xl shadow-sm border border-slate-100 hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
|
||||||
|
<div class="flex justify-between items-start mb-6">
|
||||||
|
<div class="p-4 bg-emerald-50 rounded-2xl text-emerald-600">
|
||||||
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="text-xs font-bold text-emerald-500 bg-emerald-50 px-2.5 py-1 rounded-lg">Security</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-slate-500 font-bold uppercase tracking-wider text-xs mb-1">Access Cards</h3>
|
||||||
|
<div class="flex items-baseline space-x-2 mb-4">
|
||||||
|
<span class="text-4xl font-black text-slate-900" x-text="stats.cards.assigned">0</span>
|
||||||
|
<span class="text-slate-400 font-medium tracking-tighter">/ <span
|
||||||
|
x-text="stats.cards.total">0</span> units</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-slate-400 text-xs italic">임시 출입증 총 대여 현황</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- MFP Card -->
|
||||||
|
<div
|
||||||
|
class="bg-white p-7 rounded-3xl shadow-sm border border-slate-100 hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
|
||||||
|
<div class="flex justify-between items-start mb-6">
|
||||||
|
<div class="p-4 bg-amber-50 rounded-2xl text-amber-600">
|
||||||
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span class="text-xs font-bold text-amber-500 bg-amber-50 px-2.5 py-1 rounded-lg"
|
||||||
|
x-text="calcPercent(stats.mfp) + '%'"></span>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-slate-500 font-bold uppercase tracking-wider text-xs mb-1">MFP Accounts</h3>
|
||||||
|
<div class="flex items-baseline space-x-2 mb-4">
|
||||||
|
<span class="text-4xl font-black text-slate-900" x-text="stats.mfp.assigned">0</span>
|
||||||
|
<span class="text-slate-400 font-medium tracking-tighter">/ <span x-text="stats.mfp.total">0</span>
|
||||||
|
active</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full bg-slate-100 h-2.5 rounded-full overflow-hidden">
|
||||||
|
<div class="h-full bg-amber-500 rounded-full transition-all duration-1000"
|
||||||
|
:style="'width: ' + calcPercent(stats.mfp) + '%'"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Employee Card -->
|
||||||
|
<div
|
||||||
|
class="bg-white p-7 rounded-3xl shadow-sm border border-slate-100 hover:shadow-xl hover:-translate-y-1 transition-all duration-300">
|
||||||
|
<div class="flex justify-between items-start mb-6">
|
||||||
|
<div class="p-4 bg-indigo-50 rounded-2xl text-indigo-600">
|
||||||
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
class="text-xs font-bold text-indigo-500 bg-indigo-50 px-2.5 py-1 rounded-lg">Accounting</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-slate-500 font-bold uppercase tracking-wider text-xs mb-1">Account Division</h3>
|
||||||
|
<div class="space-y-3 mt-4">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="text-sm font-semibold text-slate-600">일반회계</span>
|
||||||
|
<span class="text-lg font-black text-slate-900" x-text="stats.users.general">0</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="text-sm font-semibold text-slate-600">특별회계</span>
|
||||||
|
<span class="text-lg font-black text-slate-900" x-text="stats.users.special">0</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Links -->
|
||||||
|
<section class="mt-16">
|
||||||
|
<h3 class="text-xl font-bold text-slate-900 mb-6 flex items-center">
|
||||||
|
<svg class="w-6 h-6 mr-2 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||||
|
</svg>
|
||||||
|
퀵 네비게이션
|
||||||
|
</h3>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
|
<a href="users.php"
|
||||||
|
class="group bg-white p-6 rounded-2xl border border-slate-200 hover:border-blue-500 transition-all flex items-center space-x-4">
|
||||||
|
<div class="bg-slate-50 p-3 rounded-xl group-hover:bg-blue-50 transition-colors">
|
||||||
|
<svg class="w-6 h-6 text-slate-400 group-hover:text-blue-500" fill="none" stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="font-black text-slate-900 group-hover:text-blue-600 transition-colors">직원 DB 관리</p>
|
||||||
|
<p class="text-xs text-slate-400">사번/부서/연락처</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a href="laptops.php"
|
||||||
|
class="group bg-white p-6 rounded-2xl border border-slate-200 hover:border-blue-500 transition-all flex items-center space-x-4">
|
||||||
|
<div class="bg-slate-50 p-3 rounded-xl group-hover:bg-blue-50 transition-colors">
|
||||||
|
<svg class="w-6 h-6 text-slate-400 group-hover:text-blue-500" fill="none" stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="font-black text-slate-900 group-hover:text-blue-600 transition-colors">노트북 실사</p>
|
||||||
|
<p class="text-xs text-slate-400">자산태그/배정현황</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div
|
||||||
|
class="opacity-50 cursor-not-allowed bg-slate-50 p-6 rounded-2xl border border-slate-100 flex items-center space-x-4">
|
||||||
|
<div class="bg-slate-100 p-3 rounded-xl">
|
||||||
|
<svg class="w-6 h-6 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="font-black text-slate-500">보안실 연동</p>
|
||||||
|
<p class="text-xs text-slate-400 italic">준비 중</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="opacity-50 cursor-not-allowed bg-slate-50 p-6 rounded-2xl border border-slate-100 flex items-center space-x-4">
|
||||||
|
<div class="bg-slate-100 p-3 rounded-xl">
|
||||||
|
<svg class="w-6 h-6 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="font-black text-slate-500">환경 설정</p>
|
||||||
|
<p class="text-xs text-slate-400 italic">준비 중</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function dashboard() {
|
||||||
|
return {
|
||||||
|
stats: { laptops: {}, cards: {}, mfp: {}, users: {} },
|
||||||
|
init() {
|
||||||
|
fetch('api.php?action=get_dashboard_stats')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
this.stats = data;
|
||||||
|
})
|
||||||
|
.catch(err => console.error("Stats Fetch Error:", err));
|
||||||
|
},
|
||||||
|
calcPercent(obj) {
|
||||||
|
if (!obj || !obj.total || obj.total == 0) return 0;
|
||||||
|
return Math.round((obj.assigned / obj.total) * 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
146
laptops.php
Normal file
146
laptops.php
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>노트북 자산 관리 | FKI ASSET</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Pretendard', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-bg {
|
||||||
|
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-[#f8fafc] text-slate-800" x-data="assetManagement()">
|
||||||
|
|
||||||
|
<!-- Side Navigation -->
|
||||||
|
<aside class="fixed inset-y-0 left-0 w-64 gradient-bg text-white hidden lg:flex flex-col shadow-2xl z-50">
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold tracking-tight">FKI <span class="text-blue-400">ASSET</span></h1>
|
||||||
|
<p class="text-xs text-slate-400 mt-1 uppercase tracking-widest font-semibold">Corporate Resource V2</p>
|
||||||
|
</div>
|
||||||
|
<nav class="mt-6 flex-1 px-4 space-y-2">
|
||||||
|
<a href="index.php"
|
||||||
|
class="flex items-center space-x-3 text-slate-400 hover:bg-slate-700/50 hover:text-white p-3 rounded-xl transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium">대시보드</span>
|
||||||
|
</a>
|
||||||
|
<a href="users.php"
|
||||||
|
class="flex items-center space-x-3 text-slate-400 hover:bg-slate-700/50 hover:text-white p-3 rounded-xl transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium">직원 마스터</span>
|
||||||
|
</a>
|
||||||
|
<a href="laptops.php"
|
||||||
|
class="flex items-center space-x-3 bg-blue-600/20 text-blue-400 p-3 rounded-xl transition-all border border-blue-500/20">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium">노트북 자산</span>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="lg:ml-64 p-8">
|
||||||
|
<header class="mb-10 flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-3xl font-extrabold text-slate-900 tracking-tight">노트북 자산 실사</h2>
|
||||||
|
<p class="text-slate-500 mt-1">사내 모빌리티 자산 상태 및 실배정 내역 관리</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<button
|
||||||
|
class="bg-white border border-slate-200 text-slate-700 px-5 py-2.5 rounded-xl font-bold hover:bg-slate-50 transition-all flex items-center">
|
||||||
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||||
|
</svg>
|
||||||
|
엑셀 내보내기
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="bg-blue-600 text-white px-5 py-2.5 rounded-xl font-bold shadow-lg shadow-blue-200 hover:bg-blue-700 transition-all">
|
||||||
|
자산 등록
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Laptop List Cards -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6">
|
||||||
|
<template x-for="asset in assets" :key="asset.asset_tag">
|
||||||
|
<div class="bg-white rounded-3xl p-6 border border-slate-200 shadow-sm hover:shadow-lg transition-all">
|
||||||
|
<div class="flex justify-between items-start mb-4">
|
||||||
|
<span class="text-xs font-black bg-slate-900 text-white px-3 py-1.5 rounded-lg tracking-wider"
|
||||||
|
x-text="asset.asset_tag"></span>
|
||||||
|
<span
|
||||||
|
:class="asset.status === 'assigned' ? 'bg-blue-100 text-blue-600' : 'bg-slate-100 text-slate-500'"
|
||||||
|
class="px-3 py-1 rounded-full text-xs font-bold"
|
||||||
|
x-text="asset.status === 'assigned' ? '지급됨' : '재고'"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="text-lg font-extrabold text-slate-900 mb-1" x-text="asset.model_name"></h3>
|
||||||
|
<p class="text-xs text-slate-400 font-bold uppercase mb-6" x-text="asset.manufacturer"></p>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-center justify-between text-sm">
|
||||||
|
<span class="text-slate-500">배정 사용자</span>
|
||||||
|
<span class="font-bold text-slate-900" x-text="asset.user_name || '미배정'"></span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between text-sm">
|
||||||
|
<span class="text-slate-500">시리얼 번호</span>
|
||||||
|
<span class="font-mono text-xs text-slate-700" x-text="asset.serial_number"></span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-between text-sm">
|
||||||
|
<span class="text-slate-500">취득일</span>
|
||||||
|
<span class="font-medium text-slate-700" x-text="asset.purchase_date"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-6 pt-6 border-t border-slate-100 flex gap-2">
|
||||||
|
<button
|
||||||
|
class="flex-1 bg-slate-50 text-slate-600 py-2 rounded-xl text-xs font-bold hover:bg-slate-100 transition-all">상세보기</button>
|
||||||
|
<button
|
||||||
|
class="flex-1 bg-blue-50 text-blue-600 py-2 rounded-xl text-xs font-bold hover:bg-blue-100 transition-all">배정수정</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<div x-show="assets.length === 0"
|
||||||
|
class="p-20 text-center bg-white rounded-3xl border border-dashed border-slate-300">
|
||||||
|
<p class="text-slate-400 font-medium italic">등록된 노트북 자산이 없습니다.</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function assetManagement() {
|
||||||
|
return {
|
||||||
|
assets: [],
|
||||||
|
init() {
|
||||||
|
fetch('api.php?action=get_laptops')
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
this.assets = data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
184
users.php
Normal file
184
users.php
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ko">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>직원 마스터 리스트 | FKI ASSET</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Pretendard:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Pretendard', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-bg {
|
||||||
|
background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-[#f8fafc] text-slate-800" x-data="userManagement()">
|
||||||
|
|
||||||
|
<!-- Side Navigation (Same as index.php) -->
|
||||||
|
<aside class="fixed inset-y-0 left-0 w-64 gradient-bg text-white hidden lg:flex flex-col shadow-2xl z-50">
|
||||||
|
<div class="p-6">
|
||||||
|
<h1 class="text-2xl font-bold tracking-tight">FKI <span class="text-blue-400">ASSET</span></h1>
|
||||||
|
<p class="text-xs text-slate-400 mt-1 uppercase tracking-widest font-semibold">Corporate Resource V2</p>
|
||||||
|
</div>
|
||||||
|
<nav class="mt-6 flex-1 px-4 space-y-2">
|
||||||
|
<a href="index.php"
|
||||||
|
class="flex items-center space-x-3 text-slate-400 hover:bg-slate-700/50 hover:text-white p-3 rounded-xl transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium">대시보드</span>
|
||||||
|
</a>
|
||||||
|
<a href="users.php"
|
||||||
|
class="flex items-center space-x-3 bg-blue-600/20 text-blue-400 p-3 rounded-xl transition-all border border-blue-500/20">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium">직원 마스터</span>
|
||||||
|
</a>
|
||||||
|
<a href="laptops.php"
|
||||||
|
class="flex items-center space-x-3 text-slate-400 hover:bg-slate-700/50 hover:text-white p-3 rounded-xl transition-all">
|
||||||
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-medium">노트북 자산</span>
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="lg:ml-64 p-8">
|
||||||
|
<header class="mb-10 flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-3xl font-extrabold text-slate-900 tracking-tight">직원 마스터</h2>
|
||||||
|
<p class="text-slate-500 mt-1">임직원 기본 정보 및 부서 체계 관리</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="relative">
|
||||||
|
<span class="absolute inset-y-0 left-0 pl-3 flex items-center text-slate-400">
|
||||||
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<input type="text" x-model="searchQuery" @input.debounce.300ms="fetchUsers()"
|
||||||
|
class="block w-full pl-10 pr-3 py-2.5 border border-slate-200 rounded-xl leading-5 bg-white placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 sm:text-sm transition-all"
|
||||||
|
placeholder="이름, 사번, 이메일 검색...">
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="bg-blue-600 text-white px-5 py-2.5 rounded-xl font-bold shadow-lg shadow-blue-200 hover:bg-blue-700 transition-all flex items-center">
|
||||||
|
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
||||||
|
</svg>
|
||||||
|
신규 등록
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- User Table -->
|
||||||
|
<div class="bg-white rounded-3xl shadow-sm border border-slate-200 overflow-hidden">
|
||||||
|
<table class="min-w-full divide-y divide-slate-100">
|
||||||
|
<thead class="bg-slate-50/50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-6 py-4 text-left text-xs font-bold text-slate-500 uppercase tracking-wider">사번 /
|
||||||
|
이름</th>
|
||||||
|
<th class="px-6 py-4 text-left text-xs font-bold text-slate-500 uppercase tracking-wider">부서 /
|
||||||
|
직위</th>
|
||||||
|
<th class="px-6 py-4 text-left text-xs font-bold text-slate-500 uppercase tracking-wider">연락처
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-4 text-left text-xs font-bold text-slate-500 uppercase tracking-wider">회계구분
|
||||||
|
</th>
|
||||||
|
<th class="px-6 py-4 text-left text-xs font-bold text-slate-500 uppercase tracking-wider">상태
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-slate-50" x-show="!loading">
|
||||||
|
<template x-for="user in users" :key="user.id">
|
||||||
|
<tr class="hover:bg-slate-50/80 transition-colors">
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="h-10 w-10 flex-shrink-0 bg-blue-100 rounded-full flex items-center justify-center text-blue-600 font-bold"
|
||||||
|
x-text="user.name.charAt(0)"></div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<div class="text-sm font-bold text-slate-900" x-text="user.name"></div>
|
||||||
|
<div class="text-xs text-slate-400" x-text="user.emp_id"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div class="text-sm font-semibold text-slate-700" x-text="user.dept_name"></div>
|
||||||
|
<div class="text-xs text-slate-400" x-text="user.position"></div>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<div class="text-sm text-slate-600" x-text="user.email"></div>
|
||||||
|
<div class="text-xs text-slate-400" x-text="user.mobile || user.phone || '-'"></div>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<span class="px-3 py-1 text-xs font-bold rounded-full bg-slate-100 text-slate-600"
|
||||||
|
x-text="user.accounting_type"></span>
|
||||||
|
</td>
|
||||||
|
<td class="px-6 py-4 whitespace-nowrap">
|
||||||
|
<span x-show="user.status === 'active'"
|
||||||
|
class="px-3 py-1 text-xs font-bold rounded-full bg-emerald-100 text-emerald-600">재직
|
||||||
|
중</span>
|
||||||
|
<span x-show="user.status !== 'active'"
|
||||||
|
class="px-3 py-1 text-xs font-bold rounded-full bg-rose-100 text-rose-600">퇴사</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div x-show="loading" class="p-20 flex justify-center items-center">
|
||||||
|
<svg class="animate-spin h-10 w-10 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||||
|
<path class="opacity-75" fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z">
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Empty State -->
|
||||||
|
<div x-show="!loading && users.length === 0" class="p-20 text-center">
|
||||||
|
<p class="text-slate-400 font-medium">검색 결과가 없습니다.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function userManagement() {
|
||||||
|
return {
|
||||||
|
users: [],
|
||||||
|
loading: true,
|
||||||
|
searchQuery: '',
|
||||||
|
init() {
|
||||||
|
this.fetchUsers();
|
||||||
|
},
|
||||||
|
fetchUsers() {
|
||||||
|
this.loading = true;
|
||||||
|
fetch(`api.php?action=get_users&search=${encodeURIComponent(this.searchQuery)}`)
|
||||||
|
.then(res => res.json())
|
||||||
|
.then(data => {
|
||||||
|
this.users = data;
|
||||||
|
this.loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
0
vite.config.js
Normal file
0
vite.config.js
Normal file
Loading…
Reference in a new issue