feat(project): 홈 카드 세션 없으면 업무추가 버튼 비활성화
세션이 하나도 없으면 업무를 넣을 세션이 없으므로 아이콘 버튼(disabled)과 우클릭 메뉴 둘 다 막는다. "세션 추가"로 첫 세션을 만들면 새로고침 없이 바로 활성화(dataset.hasStages 갱신 + disabled 해제). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -419,6 +419,8 @@ async def index(request: Request) -> HTMLResponse:
|
|||||||
or store.norm_str(user["email"], lower=True) in member_emails
|
or store.norm_str(user["email"], lower=True) in member_emails
|
||||||
or store.norm_str(p.get("owner_email"), lower=True) == store.norm_str(user["email"], lower=True)
|
or store.norm_str(p.get("owner_email"), lower=True) == store.norm_str(user["email"], lower=True)
|
||||||
)
|
)
|
||||||
|
# 세션이 하나도 없으면 업무를 넣을 곳이 없다 — "업무 추가" 버튼 비활성화용.
|
||||||
|
p["has_stages"] = bool(st.list_stages(project_id=p["id"]))
|
||||||
|
|
||||||
my_tasks = st.list_tasks(assignee_email=user["email"], limit=500)
|
my_tasks = st.list_tasks(assignee_email=user["email"], limit=500)
|
||||||
for t in my_tasks:
|
for t in my_tasks:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<link rel="stylesheet" href="/static/project.css?v=20260917t" />
|
<link rel="stylesheet" href="/static/project.css?v=20260917u" />
|
||||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -45,5 +45,5 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/project.js?v=20260917u" defer></script>
|
<script src="/static/project.js?v=20260917v" defer></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<link rel="stylesheet" href="/static/project.css?v=20260917t" />
|
<link rel="stylesheet" href="/static/project.css?v=20260917u" />
|
||||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@@ -20,7 +20,8 @@
|
|||||||
data-due="{{ p.due_date or '' }}"
|
data-due="{{ p.due_date or '' }}"
|
||||||
data-color="{{ p.color }}"
|
data-color="{{ p.color }}"
|
||||||
data-status="{{ p.status or 'active' }}"
|
data-status="{{ p.status or 'active' }}"
|
||||||
data-creator="{{ p.created_by or '' }}">
|
data-creator="{{ p.created_by or '' }}"
|
||||||
|
data-has-stages="{{ 'true' if p.has_stages else 'false' }}">
|
||||||
<a class="pj-project-cardlink" href="/project/p/{{ p.id }}">
|
<a class="pj-project-cardlink" href="/project/p/{{ p.id }}">
|
||||||
<div class="pj-project-header">
|
<div class="pj-project-header">
|
||||||
<span class="pj-project-name">{{ p.name }}
|
<span class="pj-project-name">{{ p.name }}
|
||||||
@@ -56,7 +57,9 @@
|
|||||||
<button type="button" class="pj-icon-btn pj-card-addstage" data-project-id="{{ p.id }}" data-project-name="{{ p.name }}" title="세션 추가">
|
<button type="button" class="pj-icon-btn pj-card-addstage" data-project-id="{{ p.id }}" data-project-name="{{ p.name }}" title="세션 추가">
|
||||||
<span class="material-symbols-outlined">view_column</span>
|
<span class="material-symbols-outlined">view_column</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="pj-icon-btn pj-card-addtask" data-project-id="{{ p.id }}" title="업무 추가">
|
<button type="button" class="pj-icon-btn pj-card-addtask" data-project-id="{{ p.id }}"
|
||||||
|
{% if not p.has_stages %}disabled{% endif %}
|
||||||
|
title="{{ '세션을 먼저 추가하세요' if not p.has_stages else '업무 추가' }}">
|
||||||
<span class="material-symbols-outlined">add_task</span>
|
<span class="material-symbols-outlined">add_task</span>
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -421,5 +424,5 @@
|
|||||||
window.PJ_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772"] | tojson }};
|
window.PJ_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772"] | tojson }};
|
||||||
window.PJ_STAGE_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772","#e8398a","#a15c43"] | tojson }};
|
window.PJ_STAGE_COLORS = {{ ["#4573d2","#37a3a3","#62a420","#e8a33d","#e8384f","#aa62e3","#f06a6a","#5a6772","#e8398a","#a15c43"] | tojson }};
|
||||||
</script>
|
</script>
|
||||||
<script src="/static/project.js?v=20260917u" defer></script>
|
<script src="/static/project.js?v=20260917v" defer></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends "erp_base.html" %}
|
{% extends "erp_base.html" %}
|
||||||
|
|
||||||
{% block head_extra %}
|
{% block head_extra %}
|
||||||
<link rel="stylesheet" href="/static/project.css?v=20260917t" />
|
<link rel="stylesheet" href="/static/project.css?v=20260917u" />
|
||||||
<!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host -->
|
<!-- 구글 머티리얼 심볼(담당자 아이콘 등) — self-host -->
|
||||||
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
<link rel="stylesheet" href="/static/vendor/material-symbols/material-symbols.css" />
|
||||||
<!-- 타임라인 vis-timeline — self-host -->
|
<!-- 타임라인 vis-timeline — self-host -->
|
||||||
@@ -538,5 +538,5 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script>
|
<script src="/static/vendor/vis-timeline/vis-timeline-graph2d.min.js"></script>
|
||||||
<script src="/static/project.js?v=20260917u" defer></script>
|
<script src="/static/project.js?v=20260917v" defer></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -94,6 +94,10 @@
|
|||||||
.pj-card-actions .pj-icon-btn .material-symbols-outlined { font-size: 18px; }
|
.pj-card-actions .pj-icon-btn .material-symbols-outlined { font-size: 18px; }
|
||||||
.pj-project-card:hover .pj-card-actions .pj-icon-btn { opacity: 1; }
|
.pj-project-card:hover .pj-card-actions .pj-icon-btn { opacity: 1; }
|
||||||
.pj-card-actions .pj-icon-btn:hover { background: rgba(255,255,255,.25); color: #fff; }
|
.pj-card-actions .pj-icon-btn:hover { background: rgba(255,255,255,.25); color: #fff; }
|
||||||
|
/* 세션이 없는 프로젝트는 업무를 넣을 곳이 없다 — 흐리게 + 클릭 불가 */
|
||||||
|
.pj-project-card:hover .pj-card-actions .pj-icon-btn:disabled { opacity: .35; cursor: not-allowed; }
|
||||||
|
.pj-card-actions .pj-icon-btn:disabled { pointer-events: none; }
|
||||||
|
.pj-card-actions .pj-icon-btn:disabled:hover { background: none; }
|
||||||
.pj-project-name { font-weight: 700; font-size: 15px; }
|
.pj-project-name { font-weight: 700; font-size: 15px; }
|
||||||
.pj-project-desc { color: #6b7280; font-size: 12.5px; }
|
.pj-project-desc { color: #6b7280; font-size: 12.5px; }
|
||||||
/* 프로젝트 이름 아래 — 배정 멤버(아이콘+이름) */
|
/* 프로젝트 이름 아래 — 배정 멤버(아이콘+이름) */
|
||||||
|
|||||||
+21
-9
@@ -565,7 +565,8 @@
|
|||||||
' data-id="' + p.id + '" data-name="' + escapeHtml(p.name || "") + '"' +
|
' data-id="' + p.id + '" data-name="' + escapeHtml(p.name || "") + '"' +
|
||||||
' data-desc="' + escapeHtml(p.description || "") + '"' +
|
' data-desc="' + escapeHtml(p.description || "") + '"' +
|
||||||
' data-start="' + (p.start_date || "") + '" data-due="' + (p.due_date || "") + '"' +
|
' data-start="' + (p.start_date || "") + '" data-due="' + (p.due_date || "") + '"' +
|
||||||
' data-color="' + color + '" data-status="active" data-creator="' + escapeHtml(p.created_by || "") + '">' +
|
' data-color="' + color + '" data-status="active" data-creator="' + escapeHtml(p.created_by || "") + '"' +
|
||||||
|
' data-has-stages="false">' +
|
||||||
'<a class="pj-project-cardlink" href="/project/p/' + p.id + '">' +
|
'<a class="pj-project-cardlink" href="/project/p/' + p.id + '">' +
|
||||||
'<div class="pj-project-header"><span class="pj-project-name">' + escapeHtml(p.name || "") + "</span></div>" +
|
'<div class="pj-project-header"><span class="pj-project-name">' + escapeHtml(p.name || "") + "</span></div>" +
|
||||||
'<div class="pj-project-body">' +
|
'<div class="pj-project-body">' +
|
||||||
@@ -576,7 +577,7 @@
|
|||||||
'<div class="pj-card-actions">' +
|
'<div class="pj-card-actions">' +
|
||||||
'<button type="button" class="pj-icon-btn pj-card-addstage" title="세션 추가">' +
|
'<button type="button" class="pj-icon-btn pj-card-addstage" title="세션 추가">' +
|
||||||
'<span class="material-symbols-outlined">view_column</span></button>' +
|
'<span class="material-symbols-outlined">view_column</span></button>' +
|
||||||
'<button type="button" class="pj-icon-btn pj-card-addtask" data-project-id="' + p.id + '" title="업무 추가">' +
|
'<button type="button" class="pj-icon-btn pj-card-addtask" data-project-id="' + p.id + '" disabled title="세션을 먼저 추가하세요">' +
|
||||||
'<span class="material-symbols-outlined">add_task</span></button>' +
|
'<span class="material-symbols-outlined">add_task</span></button>' +
|
||||||
'<button type="button" class="pj-icon-btn pj-card-edit" title="프로젝트 수정">' +
|
'<button type="button" class="pj-icon-btn pj-card-edit" title="프로젝트 수정">' +
|
||||||
'<span class="material-symbols-outlined">edit</span></button></div>' +
|
'<span class="material-symbols-outlined">edit</span></button></div>' +
|
||||||
@@ -610,6 +611,13 @@
|
|||||||
try {
|
try {
|
||||||
await api("POST", "/project/api/projects/" + stageModalProjectId + "/stages", { name: name, color: color });
|
await api("POST", "/project/api/projects/" + stageModalProjectId + "/stages", { name: name, color: color });
|
||||||
closeModal(stageModal);
|
closeModal(stageModal);
|
||||||
|
// 이 프로젝트에 세션이 처음 생겼으면 "업무 추가" 버튼을 바로 활성화.
|
||||||
|
const targetCard = document.querySelector('.pj-project-card[data-id="' + stageModalProjectId + '"]');
|
||||||
|
if (targetCard) {
|
||||||
|
targetCard.dataset.hasStages = "true";
|
||||||
|
const addTaskBtn = targetCard.querySelector(".pj-card-addtask");
|
||||||
|
if (addTaskBtn) { addTaskBtn.disabled = false; addTaskBtn.title = "업무 추가"; }
|
||||||
|
}
|
||||||
showToast("세션이 추가되었습니다.", "success");
|
showToast("세션이 추가되었습니다.", "success");
|
||||||
} catch (e) { showToast("추가 실패: " + e.message, "error"); }
|
} catch (e) { showToast("추가 실패: " + e.message, "error"); }
|
||||||
});
|
});
|
||||||
@@ -643,14 +651,18 @@
|
|||||||
card.addEventListener("contextmenu", function (e) {
|
card.addEventListener("contextmenu", function (e) {
|
||||||
if (!card.querySelector(".pj-card-edit")) return; // 관리 권한 없는 카드는 메뉴 없음
|
if (!card.querySelector(".pj-card-edit")) return; // 관리 권한 없는 카드는 메뉴 없음
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
showContextMenu(e.clientX, e.clientY, [
|
const items = [
|
||||||
{ label: "열기", onClick: function () { location.href = "/project/p/" + card.dataset.id; } },
|
{ label: "열기", onClick: function () { location.href = "/project/p/" + card.dataset.id; } },
|
||||||
{ label: "업무 추가", onClick: function () { if (openNewTaskModalRef) openNewTaskModalRef(card.dataset.id, card.dataset.name); } },
|
];
|
||||||
{ label: "세션 추가", onClick: function () { openStageModalForProject(card.dataset.id, card.dataset.name); } },
|
// 세션이 없으면 업무를 넣을 곳이 없다 — 메뉴에서도 뺀다(아이콘 버튼과 동일 원칙).
|
||||||
{ label: "수정", onClick: function () { openCardEditModal(card); } },
|
if (card.dataset.hasStages !== "false") {
|
||||||
null,
|
items.push({ label: "업무 추가", onClick: function () { if (openNewTaskModalRef) openNewTaskModalRef(card.dataset.id, card.dataset.name); } });
|
||||||
{ label: "삭제", danger: true, onClick: function () { deleteProjectCard(card); } },
|
}
|
||||||
]);
|
items.push({ label: "세션 추가", onClick: function () { openStageModalForProject(card.dataset.id, card.dataset.name); } });
|
||||||
|
items.push({ label: "수정", onClick: function () { openCardEditModal(card); } });
|
||||||
|
items.push(null);
|
||||||
|
items.push({ label: "삭제", danger: true, onClick: function () { deleteProjectCard(card); } });
|
||||||
|
showContextMenu(e.clientX, e.clientY, items);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user