Files
dbx-main/app/modules/project/templates/project/project.html
T
king be9c97e04d style(project): 설명란 이미지 아이콘 크기 키우고 위치 미세조정
16px -> 19px, top:2px 로 살짝 내려서 옆 텍스트 버튼들과 시각적 중심이
맞게 조정.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 01:31:11 +09:00

411 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "erp_base.html" %}
{% block head_extra %}
<link rel="stylesheet" href="/static/project.css?v=20260917d" />
<!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host -->
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
<!-- 타임라인 vis-timeline — self-host -->
<link href="/static/vendor/vis-timeline/vis-timeline-graph2d.min.css" rel="stylesheet" />
{% endblock %}
{% block topbar_actions %}
<a class="pj-bell" href="/project/inbox" title="알림센터">
<span class="material-symbols-outlined">notifications</span>
<span class="pj-bell-badge" id="pj-bell-badge" hidden>0</span>
</a>
{% if can_manage %}
<button type="button" class="pj-btn pj-btn-primary" id="pj-new-task">+ 업무 추가</button>
{% endif %}
{% endblock %}
{% block content %}
<div class="pj-wrap pj-project-view"
data-project-id="{{ project.id }}"
data-can-manage="{{ 'true' if can_manage else 'false' }}"
data-is-admin="{{ 'true' if is_admin else 'false' }}"
data-is-super="{{ 'true' if user.is_super_admin else 'false' }}"
data-me="{{ user.email }}">
<div class="pj-layout">
<!-- ── 좌측: 프로젝트 트리 + 멤버 ── -->
<aside class="pj-side">
<div class="pj-side-block">
<div class="pj-side-head">
<span>프로젝트</span>
{% if is_admin %}
<button type="button" class="pj-icon-btn" id="pj-new-project-side" title="새 프로젝트">+</button>
{% endif %}
</div>
{% if nav_projects %}
<ul class="pj-tree" id="pj-tree-root">
{% for p in nav_projects %}
<li class="pj-tree-li" data-id="{{ p.id }}">
<div class="pj-tree-row {% if p.id == project.id %}is-active{% endif %}">
<a class="pj-tree-link" href="/project/p/{{ p.id }}">
<span class="pj-tree-avatar" style="background: {{ p.color }}">{{ p.name[0] | upper }}</span>
<span class="pj-tree-name">{{ p.name }}</span>
</a>
{% if is_admin %}
<span class="pj-tree-acts">
<button type="button" class="pj-icon-btn pj-tree-del" data-id="{{ p.id }}" data-name="{{ p.name }}" data-creator="{{ p.created_by }}" title="프로젝트 삭제">×</button>
</span>
{% endif %}
</div>
</li>
{% endfor %}
</ul>
{% else %}
<div class="pj-empty-sm">프로젝트 없음</div>
{% endif %}
</div>
<div class="pj-side-block">
<div class="pj-side-head">
<span>멤버</span>
{% if is_admin %}
<button type="button" class="pj-icon-btn" id="pj-add-member" title="멤버 배정">+</button>
{% endif %}
</div>
<ul class="pj-member-list" id="pj-member-list">
{% for m in members %}
<li>
{% if avatars.get(m.user_email | lower) %}
<img class="pj-avatar pj-gicon-sm" src="{{ avatars[m.user_email | lower] }}" alt="" referrerpolicy="no-referrer" />
{% else %}
<span class="material-symbols-outlined pj-gicon">account_circle</span>
{% endif %}
<span class="pj-member-name">{{ m.user_email.split('@')[0] }}</span>
{% if m.role == 'manager' %}<span class="pj-chip pj-chip-sm">관리</span>{% endif %}
{% if is_admin %}
<button type="button" class="pj-icon-btn pj-del-member" data-email="{{ m.user_email }}" title="제외">×</button>
{% endif %}
</li>
{% else %}
<li class="pj-empty-sm">없음</li>
{% endfor %}
</ul>
</div>
</aside>
<!-- ── 우측: 뷰 ── -->
<section class="pj-main">
<div class="pj-toolbar">
<div class="pj-view-tabs" id="pj-view-tabs">
<button type="button" class="pj-tab is-active" data-view="board">보드</button>
<button type="button" class="pj-tab" data-view="list">리스트</button>
<button type="button" class="pj-tab" data-view="calendar">달력</button>
<button type="button" class="pj-tab" data-view="timeline">타임라인</button>
</div>
<h1 class="pj-project-title">{{ project.name }}</h1>
<!-- 아사나처럼 기본은 "이 프로젝트"만 보여준다. 전체 프로젝트 보기는
예전처럼 접근 가능한 모든 프로젝트를 합쳐서 보여주는 모드. -->
<div class="pj-view-tabs" id="pj-scope-toggle">
<button type="button" class="pj-tab is-active" data-scope="project">이 프로젝트</button>
<button type="button" class="pj-tab" data-scope="all">전체 프로젝트</button>
</div>
</div>
<!-- 달력 (휴가 모듈과 동일한 월간 그리드) -->
<div class="pj-view" data-view="calendar" hidden>
<div class="pj-cal-head">
<a class="pj-btn pj-nav-btn" href="/project/p/{{ project.id }}?y={{ prev_y }}&m={{ prev_m }}"></a>
<h2 class="pj-cal-title">{{ year }}년 {{ month }}월</h2>
<a class="pj-btn pj-nav-btn" href="/project/p/{{ project.id }}?y={{ next_y }}&m={{ next_m }}"></a>
<a class="pj-btn pj-today-btn" href="/project/p/{{ project.id }}">오늘</a>
</div>
<div class="pj-cal">
<div class="pj-wd-row">
{% for wd in weekdays %}
<div class="pj-wd {% if loop.index0 == 0 %}pj-red{% elif loop.index0 == 6 %}pj-blue{% endif %}">{{ wd }}</div>
{% endfor %}
</div>
{% for week in weeks %}
<div class="pj-week">
<div class="pj-week-days">
{% for cell in week.days %}
<div class="pj-day {% if not cell.in_month %}pj-out{% endif %} {% if cell.is_today %}pj-today{% endif %}"
data-date="{{ cell.date }}" {% if cell.holiday_name %}title="{{ cell.holiday_name }}"{% endif %}>
<span class="pj-day-num
{% if cell.is_sunday or cell.holiday_name %}pj-red{% elif cell.is_saturday %}pj-blue{% endif %}">{{ cell.day }}</span>
{% if cell.holiday_name %}<span class="pj-day-holiday">{{ cell.holiday_name }}</span>{% endif %}
</div>
{% endfor %}
</div>
<!-- 막대는 클라이언트(JS)가 그린다 — 깔끔한 칩 + 행 제한(+N) + 드래그 이동 -->
<div class="pj-week-bars"></div>
</div>
{% endfor %}
</div>
</div>
<!-- 타임라인 -->
<div class="pj-view" data-view="timeline" hidden>
<div id="pj-timeline"></div>
</div>
<!-- 보드(칸반=세션) — 아사나처럼 프로젝트에 들어오면 기본으로 보이는 화면.
최대 4열 매이슨리로 배치하고, 세션이 늘어나면 pj-board 안에서만
세로 스크롤된다(좌측 프로젝트 목록·우측 내 업무와 스크롤이 서로
안 얽히게 각자 독립). "+ 세션 추가" 버튼은 스크롤 영역 밖(고정
위치)에 있어 항상 보인다. -->
<div class="pj-view" data-view="board">
<div class="pj-board-toolbar">
<button type="button" class="pj-add-stage" id="pj-board-addstage" hidden>+ 세션 추가</button>
</div>
<div class="pj-board" id="pj-board">
<div class="pj-board-canvas" id="pj-board-canvas"></div>
</div>
</div>
<!-- 리스트 -->
<div class="pj-view" data-view="list" hidden>
<table class="pj-table" id="pj-list">
<thead>
<tr><th>업무</th><th>담당자</th><th>세션</th><th>우선순위</th><th>시작</th><th>마감</th></tr>
</thead>
<tbody></tbody>
</table>
</div>
</section>
<!-- ── 우측: 내 업무 (홈 화면과 동일) ── -->
<aside class="pj-home-side pj-project-mytasks">
<div class="pj-section-head"><h2>내 업무</h2></div>
{% if not my_projects %}
<div class="pj-empty pj-empty-sm">배정된 업무가 없습니다.</div>
{% else %}
<div class="pj-mytree">
{% for p in my_projects %}
<div class="pj-mytree-group" data-group-id="{{ p.id }}">
<div class="pj-mytree-head">
<button type="button" class="pj-mytree-toggle" title="펼치기/접기">
<span class="material-symbols-outlined" style="color: {{ p.color }}; font-size:18px;">folder_open</span>
</button>
<a class="pj-mytree-proj" href="/project/p/{{ p.id }}">
<span class="pj-mytree-proj-name">{{ p.name }}</span>
</a>
</div>
<ul class="pj-mytree-tasks">
{% for t in p.tasks %}
<li class="pj-mytree-task {% if t.completed_at %}is-done{% endif %}">
<a href="/project/p/{{ t.project_id }}?task={{ t.id }}" data-task-id="{{ t.id }}" class="pj-task-popup-link">
<span class="pj-mytree-lead">
{% if t.comment_count %}
<span class="pj-cmt-badge" data-task-id="{{ t.id }}" data-count="{{ t.comment_count }}" title="댓글 {{ t.comment_count }}개">
<span class="pj-cmt-ic">💬</span><span class="pj-cmt-n">{{ t.comment_count }}</span>
</span>
{% endif %}
</span>
<span class="pj-mytree-body">
<span class="pj-mytree-title">{{ t.title }}</span>
{% if t.start_date or t.due_date %}
<span class="pj-mytree-dates">
{% if t.start_date and t.due_date %}{{ t.start_label }} ~ {{ t.due_label }}
{% elif t.due_date %}~ {{ t.due_label }}
{% else %}{{ t.start_label }} ~{% endif %}
</span>
{% endif %}
</span>
</a>
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
{% endif %}
</aside>
</div>
</div>
<!-- 업무 모달 -->
<div class="pj-modal" id="pj-modal-task" hidden>
<div class="pj-modal-card pj-task-modal-card">
<!-- 아사나처럼 큰 제목은 아래 무테 입력칸이 대신한다 — 이 표시는 작은
모드 안내(새 업무/업무 편집)만 한다. -->
<h3 id="pj-task-modal-title" class="pj-modal-kicker">새 업무</h3>
<input type="hidden" id="pj-t-id" />
<input type="text" id="pj-t-title" class="pj-t-title-input" placeholder="업무 제목" />
<div class="pj-modal-body">
<div class="pj-modal-col-main">
<!-- 메타 정보 — 담당자·세션·우선순위·일정을 작은 칩 형태로 한 줄에 -->
<div class="pj-meta-row">
<div class="pj-meta-field">
<span class="pj-meta-label">담당자</span>
<select id="pj-t-assignee">
<option value="">(미지정)</option>
{% for m in members %}
<option value="{{ m.user_email }}" data-name="{{ m.user_email.split('@')[0] }}">
{{ m.user_email.split('@')[0] }}
</option>
{% endfor %}
</select>
</div>
<div class="pj-meta-field">
<span class="pj-meta-label">세션</span>
<select id="pj-t-stage">
{% for s in stages %}
<option value="{{ s.id }}">{{ s.name }}</option>
{% endfor %}
</select>
</div>
<div class="pj-meta-field">
<span class="pj-meta-label">우선순위</span>
<select id="pj-t-priority">
<option value="low">낮음</option>
<option value="normal" selected>보통</option>
<option value="high">높음</option>
</select>
</div>
</div>
<div class="pj-meta-row">
<div class="pj-meta-field">
<span class="pj-meta-label">시작일</span>
<input type="date" id="pj-t-start" />
</div>
<div class="pj-meta-field">
<span class="pj-meta-label">마감일</span>
<input type="date" id="pj-t-due" />
</div>
</div>
<label class="pj-time-toggle">
<input type="checkbox" id="pj-t-usetime" /> 시간 지정 (체크 안 하면 종일)
</label>
<div class="pj-form-row" id="pj-time-row">
<label>시작 시간<input type="time" id="pj-t-stime" disabled /></label>
<label>마감 시간<input type="time" id="pj-t-dtime" disabled /></label>
</div>
<!-- 설명 — 간단한 워드프로세서식 서식 도구 + 이미지 삽입 -->
<div class="pj-desc-toolbar">
<span class="pj-extra-label">설명</span>
<span class="pj-muted-sm" id="pj-desc-hint"></span>
</div>
<div class="pj-desc-fmtbar" id="pj-desc-fmtbar">
<button type="button" class="pj-desc-fmtbtn" data-cmd="bold" title="굵게 (Ctrl+B)"><b>B</b></button>
<button type="button" class="pj-desc-fmtbtn" data-cmd="italic" title="기울임 (Ctrl+I)"><i>I</i></button>
<button type="button" class="pj-desc-fmtbtn" data-cmd="underline" title="밑줄 (Ctrl+U)"><u>U</u></button>
<span class="pj-desc-fmtsep"></span>
<button type="button" class="pj-desc-fmtbtn" data-cmd="insertUnorderedList" title="글머리 기호 목록">☰•</button>
<button type="button" class="pj-desc-fmtbtn" data-cmd="insertOrderedList" title="번호 매기기 목록">☰1</button>
<button type="button" class="pj-desc-fmtbtn" data-cmd="formatBlock" data-arg="blockquote" title="인용구"></button>
<span class="pj-desc-fmtsep"></span>
<button type="button" class="pj-desc-fmtbtn" data-cmd="createLink" title="링크 삽입">🔗</button>
<button type="button" class="pj-desc-fmtbtn" data-cmd="removeFormat" title="서식 지우기">✕서식</button>
<span class="pj-desc-fmtsep"></span>
<label class="pj-desc-fmtbtn" id="pj-desc-imgbtn" title="이미지 추가"><span class="material-symbols-outlined">image</span><input type="file" id="pj-desc-img-input" accept="image/*" hidden /></label>
</div>
<div class="pj-desc-wrap">
<div class="pj-desc-editor" id="pj-t-desc" contenteditable="true" data-placeholder="설명을 입력하세요… (이미지는 붙여넣기 또는 위 버튼으로 추가)"></div>
</div>
</div>
<div class="pj-modal-col-side">
<!-- 하위 업무 (기존 업무 편집 시에만) -->
<div class="pj-task-extra" id="pj-subtask-block" hidden>
<div class="pj-extra-head">
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">checklist</span> 하위 업무
</div>
<ul class="pj-subtask-list" id="pj-subtask-list"></ul>
<div class="pj-subtask-form">
<input type="text" id="pj-subtask-input" placeholder="하위 업무 제목…" />
<select id="pj-subtask-add-assignee"><option value="">(미지정)</option></select>
<input type="date" id="pj-subtask-add-due" />
<button type="button" class="pj-btn pj-btn-primary" id="pj-subtask-add">추가</button>
</div>
</div>
<!-- 첨부파일 (기존 업무 편집 시에만) -->
<div class="pj-task-extra" id="pj-attach-block" hidden>
<div class="pj-extra-head">
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">attach_file</span> 첨부파일
<label class="pj-upload-btn">
파일 추가<input type="file" id="pj-attach-input" hidden />
</label>
</div>
<ul class="pj-attach-list" id="pj-attach-list"></ul>
</div>
<!-- 연결된 프로젝트 — 멀티호밍(아사나식). 기존 업무 편집 시에만 -->
<div class="pj-task-extra" id="pj-links-block" hidden>
<div class="pj-extra-head">
<span class="material-symbols-outlined pj-gicon pj-gicon-sm">link</span> 연결된 프로젝트
</div>
<div class="pj-link-chips" id="pj-link-chips"></div>
<div class="pj-link-form" id="pj-link-form">
<select id="pj-link-project"><option value="">프로젝트 선택…</option></select>
<select id="pj-link-stage"><option value="">세션 선택…</option></select>
<button type="button" class="pj-btn pj-btn-primary" id="pj-link-save">추가</button>
</div>
</div>
<!-- 댓글(활동) — 아사나처럼 맨 아래. 기존 업무 편집 시에만 -->
<div class="pj-task-extra" id="pj-comment-block" hidden>
<div class="pj-extra-head">
<span class="pj-cmt-emoji">💬</span> 댓글
</div>
<ul class="pj-comment-list" id="pj-comment-list"></ul>
<div class="pj-comment-form">
<input type="text" id="pj-comment-input" placeholder="댓글 입력…" />
<button type="button" class="pj-btn pj-btn-primary" id="pj-comment-send">등록</button>
</div>
</div>
</div>
</div>
<div class="pj-modal-actions">
<button type="button" class="pj-btn pj-btn-complete" id="pj-toggle-complete" hidden>✓ 완료로 표시</button>
<button type="button" class="pj-btn pj-btn-danger" id="pj-delete-task" hidden>삭제</button>
<span style="flex:1"></span>
<button type="button" class="pj-btn" data-close>취소</button>
<button type="button" class="pj-btn pj-btn-primary" id="pj-save-task">저장</button>
</div>
</div>
</div>
{% if is_admin %}
<!-- 멤버 배정 모달 — 등록된 사용자(프로젝트 권한 보유)를 자동 목록화 -->
<div class="pj-modal" id="pj-modal-member" hidden>
<div class="pj-modal-card">
<h3>멤버 배정</h3>
<p class="pj-modal-hint">관리자 페이지에서 <b>프로젝트 관리</b> 권한을 켠 직원이 후보로 나옵니다.</p>
<label>직원
<select id="pj-m-user"><option value="">불러오는 중…</option></select>
</label>
<label>역할
<select id="pj-m-role">
<option value="member" selected>멤버</option>
<option value="manager">관리(서브·업무 관리)</option>
</select>
</label>
<div class="pj-modal-actions">
<span style="flex:1"></span>
<button type="button" class="pj-btn" data-close>취소</button>
<button type="button" class="pj-btn pj-btn-primary" id="pj-save-member">배정</button>
</div>
</div>
</div>
{% endif %}
<!-- 데이터 -->
<script id="pj-data" type="application/json">
{
"project": {{ project | tojson }},
"currentProjectId": {{ current_project_id | tojson }},
"stages": {{ stages | tojson }},
"tasks": {{ tasks | tojson }},
"members": {{ members | tojson }},
"priorityLabels": {{ priority_labels | tojson }},
"projects": {{ projects_meta | tojson }},
"stagesByProject": {{ stages_by_project | tojson }},
"boardStages": {{ board_stage_names | tojson }},
"avatars": {{ avatars | tojson }},
"myTasks": {{ my_tasks | tojson }}
}
</script>
<script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script>
<script src="/static/project.js?v=20260917c" defer></script>
{% endblock %}