convert to gitea
This commit is contained in:
193
apps/web/templates/base.html
Normal file
193
apps/web/templates/base.html
Normal file
@ -0,0 +1,193 @@
|
||||
<!-- /data/gyber/apps/web/templates/base.html -->
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko" {% if is_dark_theme %}data-bs-theme="dark"{% endif %}>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}Gyber 자산 관리{% endblock %}</title>
|
||||
|
||||
<!-- Bootstrap 5.3+ CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
<!-- Select2 Core CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
|
||||
<!-- Select2 Bootstrap 5 Theme CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" />
|
||||
<!-- Font Awesome CSS -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<!-- 커스텀 CSS (Bootstrap 및 라이브러리 CSS 이후에 로드) -->
|
||||
<link rel="stylesheet" href="{% static 'css/custom_styles.css' %}">
|
||||
|
||||
{% block extra_head %}{% endblock %}
|
||||
|
||||
<style>
|
||||
/* base.html 에 직접 작성하는 스타일은 최소화하고 custom_styles.css을 활용하자! */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{# Navbar #}
|
||||
<nav class="navbar navbar-expand-lg bg-body-tertiary mb-4 shadow-sm">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand fw-bold" href="{% url 'gyber:dashboard' %}"><i class="fas fa-cubes me-1"></i>Gyber</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
|
||||
{# ... (메뉴 항목은 이전과 동일) ... #}
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.app_name == 'gyber' and request.resolver_match.url_name == 'dashboard' %}active{% endif %}" href="{% url 'gyber:dashboard' %}"><i class="fas fa-tachometer-alt me-1"></i>대시보드</a>
|
||||
</li>
|
||||
{% if user_is_viewer_group_member %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.app_name == 'gyber' and request.resolver_match.url_name == 'resource_list' %}active{% endif %}" href="{% url 'gyber:resource_list' %}"><i class="fas fa-box me-1"></i>자산 목록</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.app_name == 'gyber' and request.resolver_match.url_name == 'user_list' %}active{% endif %}" href="{% url 'gyber:user_list' %}"><i class="fas fa-users me-1"></i>사용자 목록</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.app_name == 'gyber' and request.resolver_match.url_name == 'group_list' %}active{% endif %}" href="{% url 'gyber:group_list' %}"><i class="fas fa-sitemap me-1"></i>그룹(부서) 관리</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if request.resolver_match.app_name == 'gyber' and request.resolver_match.url_name == 'category_list' %}active{% endif %}" href="{% url 'gyber:category_list' %}"><i class="fas fa-tags me-1"></i>카테고리 관리</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if user_is_admin_group_member %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% if 'log_list' in request.resolver_match.url_name %}active{% endif %}" href="#" id="navbarLogDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fas fa-clipboard-list me-1"></i>로그 조회
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="navbarLogDropdown">
|
||||
<li><a class="dropdown-item {% if request.resolver_match.url_name == 'resource_log_list' %}active{% endif %}" href="{% url 'gyber:resource_log_list' %}">자산 로그</a></li>
|
||||
<li><a class="dropdown-item {% if request.resolver_match.url_name == 'user_log_list' %}active{% endif %}" href="{% url 'gyber:user_log_list' %}">사용자 로그</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item {% if request.resolver_match.url_name == 'group_log_list' %}active{% endif %}" href="{% url 'gyber:group_log_list' %}">그룹(부서) 로그</a></li>
|
||||
<li><a class="dropdown-item {% if request.resolver_match.url_name == 'category_log_list' %}active{% endif %}" href="{% url 'gyber:category_log_list' %}">카테고리 로그</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/" target="_blank"><i class="fas fa-cog me-1"></i>Admin</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
<ul class="navbar-nav ms-auto align-items-center">
|
||||
{# ... (사용자 정보, 로그아웃, 테마 버튼은 이전과 동일) ... #}
|
||||
{% if user.is_authenticated %}
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarUserDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fas fa-user-circle me-1"></i>{{ user.first_name|default:user.username }}
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarUserDropdown">
|
||||
<li>
|
||||
<form action="{% url 'gyber:custom_logout' %}" method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="dropdown-item"><i class="fas fa-sign-out-alt me-1"></i>로그아웃</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'oidc_authentication_init' %}">로그인</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item ms-2">
|
||||
<button id="theme-toggle-button" class="btn btn-outline-secondary btn-sm" type="button" title="테마 전환">
|
||||
<i id="theme-icon" class="fas"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{# 메시지 표시 영역 (이전과 동일) #}
|
||||
{% if messages %}
|
||||
<div class="container mt-3 mb-3">
|
||||
{% for message in messages %}
|
||||
{% with message_icon_class=message.tags|lower %}
|
||||
<div class="alert alert-{% if message_icon_class == 'error' %}danger{% elif message_icon_class == 'warning' %}warning{% elif message_icon_class == 'success' %}success{% else %}info{% endif %} alert-dismissible fade show" role="alert">
|
||||
{% if message_icon_class == 'error' %}<i class="fas fa-times-circle me-2"></i>
|
||||
{% elif message_icon_class == 'warning' %}<i class="fas fa-exclamation-triangle me-2"></i>
|
||||
{% elif message_icon_class == 'success' %}<i class="fas fa-check-circle me-2"></i>
|
||||
{% else %}<i class="fas fa-info-circle me-2"></i>
|
||||
{% endif %}
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<main class="container">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="mt-5 py-3 text-center text-body-secondary border-top">
|
||||
<p class="mb-0">© <span id="footer-year">2023</span> Gyber Corp. All rights reserved.</p>
|
||||
</footer>
|
||||
|
||||
<!-- JavaScript 파일들 (body 닫기 직전으로 이동) -->
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
|
||||
|
||||
{# 테마 전환 JavaScript #}
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
const htmlElement = document.documentElement; // html 태그 선택
|
||||
const themeToggleButton = document.getElementById('theme-toggle-button');
|
||||
const themeIcon = document.getElementById('theme-icon');
|
||||
const storedTheme = localStorage.getItem('theme');
|
||||
|
||||
const getPreferredTheme = () => {
|
||||
if (storedTheme) return storedTheme;
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||||
};
|
||||
|
||||
const setTheme = (theme) => {
|
||||
if (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
htmlElement.setAttribute('data-bs-theme', 'dark');
|
||||
} else {
|
||||
htmlElement.setAttribute('data-bs-theme', theme);
|
||||
}
|
||||
if (themeIcon) {
|
||||
themeIcon.className = theme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
|
||||
}
|
||||
};
|
||||
|
||||
setTheme(getPreferredTheme()); // 초기 테마 설정
|
||||
|
||||
if (themeToggleButton) {
|
||||
themeToggleButton.addEventListener('click', () => {
|
||||
const currentTheme = htmlElement.getAttribute('data-bs-theme');
|
||||
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
||||
localStorage.setItem('theme', newTheme);
|
||||
setTheme(newTheme);
|
||||
});
|
||||
}
|
||||
|
||||
// 시스템 테마 변경 감지 (localStorage에 저장된 테마가 없을 때만)
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
|
||||
if (!localStorage.getItem('theme')) {
|
||||
setTheme(e.matches ? 'dark' : 'light');
|
||||
}
|
||||
});
|
||||
|
||||
// Footer 연도 자동 업데이트
|
||||
const footerYear = document.getElementById('footer-year');
|
||||
if (footerYear) footerYear.textContent = new Date().getFullYear();
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
28
apps/web/templates/includes/confirm_delete_modal.html
Normal file
28
apps/web/templates/includes/confirm_delete_modal.html
Normal file
@ -0,0 +1,28 @@
|
||||
{# /data/gyber/apps/web/templates/includes/confirm_delete_modal.html #}
|
||||
{# 삭제 확인 공통 모달 #}
|
||||
{# 필요 변수: modal_id_prefix, item_id, item_name, item_type, delete_url #}
|
||||
|
||||
{# ★ 수정: ID를 내부에서 조합 (addstr 필터 불필요) #}
|
||||
<div class="modal fade" id="{{ modal_id_prefix|default:'confirm-delete-modal-' }}{{ item_id }}" tabindex="-1" aria-labelledby="{{ modal_id_prefix|default:'confirm-delete-modal-' }}{{ item_id }}Label" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="{{ modal_id_prefix|default:'confirm-delete-modal-' }}{{ item_id }}Label">{{ item_type|default:"항목" }} 삭제 확인</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p><strong>ID:</strong> {{ item_id|default:"-" }}</p>
|
||||
<p><strong>{{ item_type|default:"항목" }}명:</strong> {{ item_name|default:"해당 항목" }}</p>
|
||||
<p class="text-danger">이 {{ item_type|default:"항목" }}을(를) 정말 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.</p>
|
||||
{% block modal_extra_warning %}{% endblock %}
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">취소</button>
|
||||
<form action="{{ delete_url }}" method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-danger">삭제 실행</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
94
apps/web/templates/includes/gyber/resource_controls.html
Normal file
94
apps/web/templates/includes/gyber/resource_controls.html
Normal file
@ -0,0 +1,94 @@
|
||||
{# /data/gyber/apps/web/templates/includes/gyber/resource_controls.html #}
|
||||
{# 자산 목록 페이지 상단 컨트롤 영역 #}
|
||||
<table class="table table-borderless mb-3 controls-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
{# 카테고리 필터 #}
|
||||
<td style="width: auto;">
|
||||
<form method="get" action="{% url 'gyber:resource_list' %}" id="filterFormCategory" class="d-inline-flex align-items-center">
|
||||
{% comment %} 다른 필터/정렬/검색값 유지 위한 hidden inputs {% endcomment %}
|
||||
{% if search_query %}<input type="hidden" name="query" value="{{ search_query }}">{% endif %}
|
||||
<input type="hidden" name="page_size" value="{{ page_size|default:'20' }}">
|
||||
{% if sort_by != 'id' %}<input type="hidden" name="sort" value="{{ sort_by }}">{% endif %}
|
||||
{% if sort_dir != 'desc' %}<input type="hidden" name="dir" value="{{ sort_dir }}">{% endif %}
|
||||
{% if request.GET.group %}<input type="hidden" name="group" value="{{ request.GET.group }}">{% endif %}
|
||||
{% if request.GET.user_id %}<input type="hidden" name="user_id" value="{{ request.GET.user_id }}">{% endif %}
|
||||
<label for="filterCategory" class="form-label me-1 mb-0 text-nowrap">카테고리:</label>
|
||||
<select name="category" id="filterCategory" class="form-select form-select-sm" style="width: auto;" onchange="this.form.submit()">
|
||||
<option value="" {% if not current_category %}selected{% endif %}>-- 전체 --</option>
|
||||
{% for cat in category_list %}
|
||||
<option value="{{ cat.category_id }}" {% if current_category == cat.category_id %}selected{% endif %}>{{ cat.category_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</td>
|
||||
{# 부서 필터 #}
|
||||
<td style="width: auto;">
|
||||
<form method="get" action="{% url 'gyber:resource_list' %}" id="filterFormGroup" class="d-inline-flex align-items-center">
|
||||
{% comment %} 다른 필터/정렬/검색값 유지 위한 hidden inputs {% endcomment %}
|
||||
{% if search_query %}<input type="hidden" name="query" value="{{ search_query }}">{% endif %}
|
||||
<input type="hidden" name="page_size" value="{{ page_size|default:'20' }}">
|
||||
{% if sort_by != 'id' %}<input type="hidden" name="sort" value="{{ sort_by }}">{% endif %}
|
||||
{% if sort_dir != 'desc' %}<input type="hidden" name="dir" value="{{ sort_dir }}">{% endif %}
|
||||
{% if request.GET.category %}<input type="hidden" name="category" value="{{ request.GET.category }}">{% endif %}
|
||||
{% if request.GET.user_id %}<input type="hidden" name="user_id" value="{{ request.GET.user_id }}">{% endif %}
|
||||
<label for="filterGroup" class="form-label me-1 mb-0 text-nowrap">부서:</label>
|
||||
<select name="group" id="filterGroup" class="form-select form-select-sm" style="width: auto;" onchange="this.form.submit()">
|
||||
<option value="" {% if not current_group %}selected{% endif %}>-- 전체 --</option>
|
||||
{% for grp in group_list %}
|
||||
<option value="{{ grp.group_id }}" {% if current_group == grp.group_id %}selected{% endif %}>{{ grp.group_name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</td>
|
||||
{# 페이지 크기 #}
|
||||
<td style="width: auto;">
|
||||
<form method="get" action="{% url 'gyber:resource_list' %}" id="pageSizeForm" class="d-inline-flex align-items-center">
|
||||
{% comment %} 다른 필터/정렬/검색값 유지 위한 hidden inputs {% endcomment %}
|
||||
{% if search_query %}<input type="hidden" name="query" value="{{ search_query }}">{% endif %}
|
||||
{% if request.GET.category %}<input type="hidden" name="category" value="{{ request.GET.category }}">{% endif %}
|
||||
{% if request.GET.group %}<input type="hidden" name="group" value="{{ request.GET.group }}">{% endif %}
|
||||
{% if request.GET.user_id %}<input type="hidden" name="user_id" value="{{ request.GET.user_id }}">{% endif %}
|
||||
{% if sort_by != 'id' %}<input type="hidden" name="sort" value="{{ sort_by }}">{% endif %}
|
||||
{% if sort_dir != 'desc' %}<input type="hidden" name="dir" value="{{ sort_dir }}">{% endif %}
|
||||
<label for="pageSizeSelect" class="form-label me-2 mb-0 text-nowrap">표시:</label>
|
||||
<select name="page_size" id="pageSizeSelect" class="form-select form-select-sm" style="width: auto;" onchange="this.form.submit()">
|
||||
{% for size in valid_page_sizes %}
|
||||
<option value="{{ size }}" {% if size == page_size %}selected{% endif %}>{{ size }}개씩</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</form>
|
||||
</td>
|
||||
{# 검색 입력 그룹 #}
|
||||
<td style="width: 100%;">
|
||||
<form method="get" action="{% url 'gyber:resource_list' %}" id="searchForm" class="mb-0">
|
||||
{% comment %} 다른 필터/정렬값 유지 위한 hidden inputs {% endcomment %}
|
||||
<input type="hidden" name="page_size" value="{{ page_size|default:'20' }}">
|
||||
{% if request.GET.category %}<input type="hidden" name="category" value="{{ request.GET.category }}">{% endif %}
|
||||
{% if request.GET.group %}<input type="hidden" name="group" value="{{ request.GET.group }}">{% endif %}
|
||||
{% if request.GET.user_id %}<input type="hidden" name="user_id" value="{{ request.GET.user_id }}">{% endif %}
|
||||
{% if sort_by != 'id' %}<input type="hidden" name="sort" value="{{ sort_by }}">{% endif %}
|
||||
{% if sort_dir != 'desc' %}<input type="hidden" name="dir" value="{{ sort_dir }}">{% endif %}
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" name="query" class="form-control form-control-sm" placeholder="검색어 입력..." value="{{ search_query|default:'' }}" aria-label="Search query">
|
||||
<button type="submit" class="btn btn-primary btn-sm"><i class="fas fa-search"></i> 검색</button>
|
||||
{% if search_query %}
|
||||
<a href="{% url 'gyber:resource_list' %}?{{ query_params_no_search }}" class="btn btn-secondary btn-sm">초기화</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
{% if current_category or current_group %}
|
||||
<a href="{% url 'gyber:resource_list' %}?{{ query_params_no_filter }}" class="btn btn-sm btn-outline-secondary text-nowrap">필터 초기화</a>
|
||||
{% else %} {% endif %}
|
||||
</td>
|
||||
<td> </td>
|
||||
<td class="text-end">
|
||||
<div class="form-text"><small>검색 대상: 제품명, 시리얼, 코드, 사용자, 제조사, 비고</small></div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
66
apps/web/templates/includes/gyber/resource_table.html
Normal file
66
apps/web/templates/includes/gyber/resource_table.html
Normal file
@ -0,0 +1,66 @@
|
||||
{# /data/gyber/apps/web/templates/includes/gyber/resource_table.html #}
|
||||
{# 자산 목록 테이블 - include 로 사용됨 #}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover table-bordered table-sm">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
{# 테이블 헤더 (정렬 링크 포함) #}
|
||||
<th><a href="?{{ query_params_all }}&sort=id&dir={% if sort_by == 'id' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">ID {% if sort_by == 'id' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}</a></th>
|
||||
<th><a href="?{{ query_params_all }}&sort=name&dir={% if sort_by == 'name' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">제품명 {% if sort_by == 'name' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}</a></th>
|
||||
<th class="hide-on-mobile"><a href="?{{ query_params_all }}&sort=category&dir={% if sort_by == 'category' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">카테고리 {% if sort_by == 'category' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}</a></th>
|
||||
<th class="hide-on-mobile"><a href="?{{ query_params_all }}&sort=code&dir={% if sort_by == 'code' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">관리 코드 {% if sort_by == 'code' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}</a></th>
|
||||
<th><a href="?{{ query_params_all }}&sort=user&dir={% if sort_by == 'user' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">사용자 {% if sort_by == 'user' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}</a></th>
|
||||
<th><a href="?{{ query_params_all }}&sort=group&dir={% if sort_by == 'group' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">부서 {% if sort_by == 'group' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}</a></th>
|
||||
<th class="ellipsis-cell"><a href="?{{ query_params_all }}&sort=serial&dir={% if sort_by == 'serial' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">시리얼 번호 {% if sort_by == 'serial' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}</a></th>
|
||||
<th style="width: 5%; text-align: center;"><a href="?{{ query_params_all }}&sort=is_locked&dir={% if sort_by == 'is_locked' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}" title="자동 동기화 제외 여부">잠금 {% if sort_by == 'is_locked' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}</a></th>
|
||||
<th class="hide-on-mobile"><a href="?{{ query_params_all }}&sort=purchased&dir={% if sort_by == 'purchased' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">구매일 {% if sort_by == 'purchased' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}</a></th>
|
||||
<th class="hide-on-mobile"><a href="?{{ query_params_all }}&sort=registered&dir={% if sort_by == 'registered' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">등록일 {% if sort_by == 'registered' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}</a></th>
|
||||
<th class="hide-on-mobile"><a href="?{{ query_params_all }}&sort=updated&dir={% if sort_by == 'updated' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">수정일 {% if sort_by == 'updated' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}</a></th>
|
||||
<th>액션</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for resource in resource_list %}
|
||||
<tr>
|
||||
<td>{{ resource.resource_id }}</td>
|
||||
<td><a href="{% url 'gyber:resource_detail' resource.resource_id %}">{{ resource.resource_name }}</a></td>
|
||||
<td class="hide-on-mobile">{{ resource.category_name|default:"-" }}</td>
|
||||
<td class="hide-on-mobile">{{ resource.resource_code|default:"-" }}</td>
|
||||
<td>{{ resource.user_display_name|default:"<span class='text-muted'>미지정</span>"|safe }}</td>
|
||||
<td>{{ resource.group_name|default:"-" }}</td>
|
||||
<td class="ellipsis-cell" title="{{ resource.serial_num|default:'' }}">{{ resource.serial_num|default:"-" }}</td>
|
||||
<td style="text-align: center;">
|
||||
{% if resource.is_locked %}
|
||||
<i class="fas fa-lock text-danger" title="잠김 (자동 동기화 제외)"></i>
|
||||
{% else %}
|
||||
<i class="fas fa-unlock-alt text-success" title="잠금 해제 (자동 동기화 대상)"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="hide-on-mobile">{{ resource.purchase_date|date:"Y-m-d"|default:"-" }}</td>
|
||||
<td class="hide-on-mobile">{{ resource.register_date|date:"Y-m-d H:i"|default:"-" }}</td>
|
||||
<td class="hide-on-mobile">{{ resource.update_date|date:"Y-m-d H:i"|default:"-" }}</td>
|
||||
<td class="text-nowrap">
|
||||
{% if user_is_admin_group_member %}
|
||||
<a href="{% url 'gyber:resource_detail' resource.resource_id %}" class="btn btn-sm btn-info" title="상세보기"><i class="fas fa-eye"></i></a>
|
||||
<a href="{% url 'gyber:resource_edit' resource.resource_id %}" class="btn btn-sm btn-warning" title="수정하기"><i class="fas fa-edit"></i></a>
|
||||
{# ★ 수정: data-bs-target ID 형식 변경 #}
|
||||
<button type="button" class="btn btn-sm btn-danger" data-bs-toggle="modal" data-bs-target="#resource-delete-modal-{{ resource.resource_id }}" title="삭제하기">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="12" class="text-center">
|
||||
{% if search_query or current_category or current_group or current_user_id %}
|
||||
조건에 맞는 자산이 없습니다.
|
||||
{% else %}
|
||||
등록된 자산이 없습니다.
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
29
apps/web/templates/includes/pagination.html
Normal file
29
apps/web/templates/includes/pagination.html
Normal file
@ -0,0 +1,29 @@
|
||||
{# /data/gyber/apps/web/templates/includes/pagination.html #}
|
||||
{# 페이지네이션 UI 컴포넌트 #}
|
||||
{% if total_pages > 1 %}
|
||||
<nav aria-label="Page navigation">
|
||||
<ul class="pagination justify-content-center">
|
||||
<li class="page-item {% if current_page == 1 %}disabled{% endif %}">
|
||||
<a class="page-link" href="?page=1&{{ query_params_all }}" aria-label="First">««</a>
|
||||
</li>
|
||||
<li class="page-item {% if not has_previous %}disabled{% endif %}">
|
||||
<a class="page-link" href="?page={{ previous_page_number }}&{{ query_params_all }}" aria-label="Previous">«</a>
|
||||
</li>
|
||||
{% for page_num in page_numbers %}
|
||||
<li class="page-item {% if page_num == current_page %}active{% endif %}">
|
||||
{% if page_num == current_page %}
|
||||
<span class="page-link">{{ page_num }}</span>
|
||||
{% else %}
|
||||
<a class="page-link" href="?page={{ page_num }}&{{ query_params_all }}">{{ page_num }}</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li class="page-item {% if not has_next %}disabled{% endif %}">
|
||||
<a class="page-link" href="?page={{ next_page_number }}&{{ query_params_all }}" aria-label="Next">»</a>
|
||||
</li>
|
||||
<li class="page-item {% if current_page == total_pages %}disabled{% endif %}">
|
||||
<a class="page-link" href="?page={{ total_pages }}&{{ query_params_all }}" aria-label="Last">»»</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
53
apps/web/templates/includes/user/user_controls.html
Normal file
53
apps/web/templates/includes/user/user_controls.html
Normal file
@ -0,0 +1,53 @@
|
||||
{# /data/gyber/apps/web/templates/includes/user/user_controls.html #}
|
||||
{# 사용자 목록 페이지 상단 컨트롤 영역 #}
|
||||
<form method="get" action="{% url 'gyber:user_list' %}" class="row gy-2 gx-3 align-items-center mb-4">
|
||||
{# 검색어 입력 #}
|
||||
<div class="col-auto">
|
||||
<label class="visually-hidden" for="query">검색어</label>
|
||||
<input type="text" class="form-control form-control-sm" id="query" name="query" placeholder="이름, 계정, 부서 검색" value="{{ search_query|default:'' }}">
|
||||
</div>
|
||||
|
||||
{# 부서 필터 드롭다운 #}
|
||||
<div class="col-auto">
|
||||
<label class="visually-hidden" for="group">부서</label>
|
||||
{# ★ onchange 이벤트 제거하고 검색 버튼으로 통일? 또는 유지? 여기서는 유지 #}
|
||||
<select class="form-select form-select-sm" id="group" name="group" onchange="this.form.submit()">
|
||||
<option value="">-- 전체 부서 --</option>
|
||||
{% for group_item in group_list %} {# 변수 이름 충돌 피하기 위해 group_item 사용 #}
|
||||
<option value="{{ group_item.group_id }}" {% if group_item.group_id == current_group %}selected{% endif %}>
|
||||
{{ group_item.group_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{# 페이지 크기 선택 #}
|
||||
<div class="col-auto">
|
||||
<label class="visually-hidden" for="page_size">페이지 크기</label>
|
||||
<select class="form-select form-select-sm" id="page_size" name="page_size" onchange="this.form.submit()">
|
||||
{% for size in valid_page_sizes %}
|
||||
<option value="{{ size }}" {% if size == page_size %}selected{% endif %}>{{ size }}개씩 보기</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{# 검색 버튼 #}
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-primary btn-sm">
|
||||
<i class="fas fa-search"></i> 검색
|
||||
</button>
|
||||
</div>
|
||||
{# 검색/필터 초기화 버튼 #}
|
||||
{% if search_query or current_group %}
|
||||
<div class="col-auto">
|
||||
{# 초기화 링크 #}
|
||||
<a href="{% url 'gyber:user_list' %}" class="btn btn-secondary btn-sm">
|
||||
<i class="fas fa-times"></i> 초기화
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{# 다른 필터/정렬값 유지 위한 hidden inputs (필요시 추가) #}
|
||||
{% if sort_by != 'name' %}<input type="hidden" name="sort" value="{{ sort_by }}">{% endif %}
|
||||
{% if sort_dir != 'asc' %}<input type="hidden" name="dir" value="{{ sort_dir }}">{% endif %}
|
||||
{# page_size 는 select 에서 전달됨 #}
|
||||
</form>
|
||||
72
apps/web/templates/includes/user/user_table.html
Normal file
72
apps/web/templates/includes/user/user_table.html
Normal file
@ -0,0 +1,72 @@
|
||||
{# /data/gyber/apps/web/templates/includes/user/user_table.html #}
|
||||
{# 사용자 목록 테이블 #}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover table-sm align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
{# 테이블 헤더 (정렬 링크 포함) #}
|
||||
<th>
|
||||
<a href="?{{ query_params_all }}&sort=name&dir={% if sort_by == 'name' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">
|
||||
사용자 (표시이름 [계정명])
|
||||
{% if sort_by == 'name' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="?{{ query_params_all }}&sort=account&dir={% if sort_by == 'account' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">
|
||||
계정명
|
||||
{% if sort_by == 'account' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}
|
||||
</a>
|
||||
</th>
|
||||
<th>
|
||||
<a href="?{{ query_params_all }}&sort=group&dir={% if sort_by == 'group' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">
|
||||
부서
|
||||
{% if sort_by == 'group' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}
|
||||
</a>
|
||||
</th>
|
||||
<th class="text-end">
|
||||
<a href="?{{ query_params_all }}&sort=assets&dir={% if sort_by == 'assets' and sort_dir == 'asc' %}desc{% else %}asc{% endif %}">
|
||||
보유 자산 수
|
||||
{% if sort_by == 'assets' %}<i class="fas fa-sort-{{ sort_dir|lower }}"></i>{% endif %}
|
||||
</a>
|
||||
</th>
|
||||
<th style="width: 15%;">액션</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user_item in user_list %} {# 변수 이름 변경 #}
|
||||
<tr>
|
||||
<td>{{ user_item.user_display_name|default:"-" }}</td>
|
||||
<td>{{ user_item.account_name|default:"-" }}</td>
|
||||
<td>{{ user_item.group_name|default:"-" }}</td>
|
||||
<td class="text-end">{{ user_item.assigned_asset_count }}</td>
|
||||
<td class="text-nowrap"> {# 액션 버튼 줄바꿈 방지 #}
|
||||
{# 자산 보기 링크 #}
|
||||
<a href="{% url 'gyber:resource_list' %}?user_id={{ user_item.user_id }}" class="btn btn-sm btn-outline-primary me-1" title="{{ user_item.user_display_name|default:'사용자' }}님의 자산 목록 보기">
|
||||
<i class="fas fa-list"></i> <span class="d-none d-md-inline">자산</span>
|
||||
</a>
|
||||
{# 사용자 수정 버튼 #}
|
||||
{% if user_is_admin_group_member %}
|
||||
<a href="{% url 'gyber:user_edit' user_item.user_id %}" class="btn btn-sm btn-outline-secondary me-1" title="사용자 정보 수정">
|
||||
<i class="fas fa-edit"></i> <span class="d-none d-md-inline">수정</span>
|
||||
</a>
|
||||
{# 삭제 버튼 (모달 트리거) #}
|
||||
<button type="button" class="btn btn-sm btn-outline-danger" data-bs-toggle="modal" data-bs-target="#user-delete-modal-{{ user_item.user_id }}" title="사용자 삭제">
|
||||
<i class="fas fa-trash-alt"></i> <span class="d-none d-md-inline">삭제</span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center">
|
||||
{% if search_query or current_group %}
|
||||
조건에 맞는 사용자가 없습니다.
|
||||
{% else %}
|
||||
등록된 사용자가 없습니다.
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
Reference in New Issue
Block a user