fix: complete feedback confirmation flow
Deploy EG-BIM QA Gateway / deploy (push) Successful in 40s

This commit is contained in:
root committed 2026-09-29 20:35:48 +09:00
1 parent a332ed0d8e
commit efd1bf2e3c
6 files changed
+122 -24

No files matched your search

+66 -17
View File
@@ -45,6 +45,11 @@
return "";
}
function toBoolean(value) {
if (value === true || value === 1) return true;
return ["true", "1", "yes", "y"].indexOf(String(value || "").trim().toLowerCase()) > -1;
}
function normalizeCategory(value) {
const raw = String(value || "").trim();
return ({
@@ -60,13 +65,17 @@
function normalizeRemotePost(item) {
item = item || {};
const requester = item.requester || item.author || item.user || {};
const fields = item.fields && typeof item.fields === "object" && !Array.isArray(item.fields) ? item.fields : {};
const fields = [item.fields, item.metadata, item.meta, item.extra_fields, item.data].find(function (candidate) { return candidate && typeof candidate === "object" && !Array.isArray(candidate); }) || {};
function value(keys) {
return firstValue(item, keys) || firstValue(fields, keys) || firstValue(requester, keys);
}
const createdAt = value(["createdAt", "created_at", "date"]) || new Date().toISOString();
const secret = value(["is_secret", "isSecret", "secret"]);
const requesterEmail = value(["requester_email", "requesterEmail", "email"]);
const completionRequested = value(["completion_requested", "completionRequested"]);
const completionConfirmed = value(["completion_confirmed", "completionConfirmed"]);
const completionRequestedAt = value(["completion_requested_at", "completionRequestedAt"]);
const completionConfirmedAt = value(["completion_confirmed_at", "completionConfirmedAt"]);
return {
id: value(["id", "feedbackId", "feedback_id"]),
feedbackId: value(["id", "feedbackId", "feedback_id"]),
@@ -82,7 +91,11 @@
date: String(createdAt).slice(0, 10),
createdAt: createdAt,
status: normalizeStatus(value(["feedback_status", "feedbackStatus", "status_code", "status"])),
secret: secret === true || String(secret).toLowerCase() === "true" || String(secret) === "1",
secret: toBoolean(secret),
completionRequested: toBoolean(completionRequested) || Boolean(completionRequestedAt),
completionRequestedAt: completionRequestedAt,
completionConfirmed: toBoolean(completionConfirmed) || Boolean(completionConfirmedAt),
completionConfirmedAt: completionConfirmedAt,
attachments: value(["attachments", "images"]) || [],
raw: item
};
@@ -356,7 +369,7 @@
async function requestJson(path, options) {
const requestPath = String(path).split("?")[0];
const sameOriginProxy = !config.apiBaseUrl && (requestPath === config.createFeedbackPath || requestPath === "/api/feedbacks" || /^\/api\/feedbacks\/[^\/]+(?:\/comments)?$/.test(requestPath));
const sameOriginProxy = !config.apiBaseUrl && (requestPath === config.createFeedbackPath || requestPath === "/api/feedbacks" || /^\/api\/feedbacks\/[^\/]+(?:\/comments|\/completion-confirmation)?$/.test(requestPath));
if (!config.apiBaseUrl && !sameOriginProxy) return { local: true };
const target = config.apiBaseUrl ? config.apiBaseUrl.replace(/\/$/, '') + path : path;
const response = await fetch(target, Object.assign({ credentials: 'include' }, options));
@@ -529,15 +542,15 @@
if (detailListLink) detailListLink.textContent = '목록으로';
const bottomBackButton = qs('#backButton', detail);
if (bottomBackButton && bottomBackButton.parentElement) bottomBackButton.parentElement.remove();
const id = new URLSearchParams(window.location.search).get('id') || 'EG-1048';
let post = getPosts().find(function (item) { return item.id === id; });
if (!post) {
try {
const result = await requestJson("/api/feedbacks/" + encodeURIComponent(id), { method: "GET" });
post = normalizeRemotePost(result.feedback || result.data || result);
} catch (error) {
console.warn("[QA] feedback detail load failed:", error.message);
}
const id = new URLSearchParams(window.location.search).get("id") || "EG-1048";
const storedPost = getPosts().find(function (item) { return item.id === id; });
let post = storedPost;
try {
const result = await requestJson("/api/feedbacks/" + encodeURIComponent(id), { method: "GET" });
const remotePost = normalizeRemotePost(result.feedback || result.data || result);
if (remotePost.id) post = Object.assign({}, storedPost || {}, remotePost);
} catch (error) {
console.warn("[QA] feedback detail load failed:", error.message);
}
if (!post) {
qs('[data-detail-title]').textContent = '문의 내용을 찾을 수 없습니다.';
@@ -546,10 +559,18 @@
}
qs('[data-detail-category]').textContent = post.category;
qs('[data-detail-title]').textContent = post.title;
qs('[data-detail-status]').textContent = normalizeStatus(post.status) || '공지';
const renderDetailStatus = function () {
const statusElement = qs("[data-detail-status]");
const status = normalizeStatus(post.status) || "공지";
if (!statusElement) return;
statusElement.textContent = status;
statusElement.classList.toggle("done", status === "완료");
statusElement.classList.toggle("review", status !== "완료");
};
renderDetailStatus();
qs('[data-detail-date]').textContent = post.date;
qs('[data-detail-author]').textContent = post.author;
const detailUser = getAuthUser();
let detailUser = getAuthUser();
qs('[data-detail-company]').textContent = getPostCompanyLabel(post, detailUser);
qs('[data-detail-department]').textContent = post.department;
qs('[data-detail-content]').textContent = post.content;
@@ -563,13 +584,41 @@
const deleteButton = qs('#deleteButton');
const editForm = qs('#editForm');
const editError = qs('#editError');
const completionNotice = qs("#completionNotice");
const completionButton = qs("#completionButton");
const renderCompletionNotice = function () {
const owner = isPostOwner(post, detailUser || getAuthUser());
const visible = owner && post.completionRequested && !post.completionConfirmed;
if (completionNotice) completionNotice.hidden = !visible;
};
const ownerActions = function () {
const owner = isPostOwner(post, getAuthUser());
const owner = isPostOwner(post, detailUser || getAuthUser());
if (editButton) editButton.hidden = !owner;
if (deleteButton) deleteButton.hidden = !owner;
renderCompletionNotice();
};
ownerActions();
if (authReady) authReady.then(ownerActions);
if (authReady) authReady.then(function (user) { detailUser = user || detailUser; ownerActions(); renderCompletionNotice(); });
if (completionButton) completionButton.addEventListener("click", async function () {
if (!post.completionRequested || post.completionConfirmed) return;
completionButton.disabled = true;
completionButton.textContent = "확인 처리 중...";
try {
await requestJson("/api/feedbacks/" + encodeURIComponent(post.id) + "/completion-confirmation", { method: "POST" });
post.completionRequested = false;
post.completionConfirmed = true;
post.status = "완료";
updateStoredPost(post);
renderDetailStatus();
renderCompletionNotice();
showToast("문의가 완료 처리되었습니다.");
} catch (error) {
showToast(error.message || "문의 완료 확인에 실패했습니다.");
} finally {
completionButton.disabled = false;
completionButton.textContent = "완료 확인";
}
});
if (editButton && editForm) editButton.addEventListener('click', function () {
qs('#editCategory').value = post.category || '일반문의';
qs('#editTitle').value = post.title || '';
@@ -741,7 +790,7 @@
}).join('');
});
refreshRemoteStatus(post).then(function (changed) {
if (changed) qs('[data-detail-status]').textContent = normalizeStatus(post.status) || '공지';
if (changed) renderDetailStatus();
});
}