개인정보-컴플라이언스-웹애플리케이션(13) - (정량문항 피드백 , 정성문항피드백) 프론트 코드
DiagnosisFeedbackPag.jsx
import React, { useEffect, useState } from "react";
import { useNavigate, useLocation } from "react-router-dom";
import axios from "axios";
import { useRecoilState } from "recoil";
import {
quantitativeDataState,
currentStepState,
} from "../../state/selfTestState";
import { quantitativeFeedbackState } from "../../state/feedback";
function DiagnosisFeedbackPage() {
const navigate = useNavigate();
const location = useLocation();
const { systemId } = location.state || {};
const [quantitativeData, setQuantitativeData] = useRecoilState(
quantitativeDataState
);
const [feedbacks, setFeedbacks] = useRecoilState(quantitativeFeedbackState);
const [currentStep, setCurrentStep] = useRecoilState(currentStepState);
const [responses, setResponses] = useState({});
useEffect(() => {
if (!systemId) {
console.error("System ID가 누락되었습니다.");
alert("시스템 정보가 없습니다. 대시보드로 이동합니다.");
navigate("/dashboard");
return;
}
const fetchQuantitativeData = async () => {
try {
const response = await axios.get(
"http://localhost:3000/selftest/quantitative",
{ params: { systemId }, withCredentials: true }
);
const data = response.data || [];
setQuantitativeData(data);
const initialResponses = data.reduce((acc, item) => {
acc[item.question_number] = {
response: item.response || "",
feedback: item.feedback || "피드백 없음",
};
return acc;
}, {});
setResponses(initialResponses);
} catch (error) {
console.error("Error fetching quantitative data:", error);
alert("데이터를 불러오는 중 오류가 발생했습니다.");
}
};
fetchQuantitativeData();
}, [systemId, navigate, setQuantitativeData]);
const handleFeedbackChange = (questionNumber, value) => {
setResponses((prev) => ({
...prev,
[questionNumber]: {
...prev[questionNumber],
feedback: value,
},
}));
};
const saveAllFeedbacks = async () => {
const feedbackData = quantitativeData.map((item) => ({
questionNumber: item.question_number,
systemId,
feedback: responses[item.question_number]?.feedback || "피드백 없음",
}));
console.log("Sending feedback data:", feedbackData);
try {
const response = await axios.post(
"http://localhost:3000/selftest/quantitative/feedback",
{ systemId, feedbackResponses: feedbackData },
{ withCredentials: true }
);
alert(response.data.msg || "모든 피드백이 저장되었습니다.");
console.log(
"Navigating to /QualitativeSurveyfeedback with systemId:",
systemId
);
// Navigate with systemId in state
navigate("/QualitativeSurveyfeedback", { state: { systemId } });
} catch (error) {
console.error("Error saving feedback:", error.response?.data || error);
alert(
error.response?.data?.msg ||
"피드백 저장 중 오류가 발생했습니다. 다시 시도해주세요."
);
}
};
const handleNextClick = () => {
if (currentStep < 43) {
setCurrentStep((prev) => prev + 1);
} else {
saveAllFeedbacks();
}
};
const handlePreviousClick = () => {
if (currentStep > 1) setCurrentStep((prev) => prev - 1);
};
const renderCurrentStep = () => {
const currentData = quantitativeData.find(
(item) => item.question_number === currentStep
) || {
question_number: currentStep,
unit: "",
evaluation_method: "",
score: "",
question: "질문 없음",
legal_basis: "",
criteria_and_references: "",
feedback: "피드백 없음",
};
return (
<table className="w-full border-collapse border border-gray-300 mb-6">
<tbody>
<tr>
<td className="bg-gray-200 p-2 border">지표 번호</td>
<td className="p-2 border">{currentData.question_number}</td>
<td className="bg-gray-200 p-2 border">단위</td>
<td className="p-2 border">{currentData.unit || "N/A"}</td>
</tr>
<tr>
<td className="bg-gray-200 p-2 border">평가방법</td>
<td className="p-2 border">
{currentData.evaluation_method || "N/A"}
</td>
<td className="bg-gray-200 p-2 border">배점</td>
<td className="p-2 border">{currentData.score || "N/A"}</td>
</tr>
<tr>
<td className="bg-gray-200 p-2 border">지표</td>
<td colSpan="3" className="p-2 border">
{currentData.question}
</td>
</tr>
<tr>
<td className="bg-gray-200 p-2 border">근거법령</td>
<td colSpan="3" className="p-2 border">
{currentData.legal_basis || "N/A"}
</td>
</tr>
<tr>
<td className="bg-gray-200 p-2 border">평가기준 (착안 사항)</td>
<td colSpan="3" className="p-2 border">
{currentData.criteria_and_references || "N/A"}
</td>
</tr>
<tr>
<td className="bg-gray-200 p-2 border">피드백</td>
<td colSpan="3" className="p-2 border">
<textarea
value={responses[currentStep]?.feedback || "피드백 없음"}
onChange={(e) =>
handleFeedbackChange(currentStep, e.target.value)
}
className="w-full p-2 border border-gray-300 rounded-md"
placeholder="피드백을 입력하세요"
/>
</td>
</tr>
</tbody>
</table>
);
};
return (
<div className="bg-gray-100 min-h-screen flex flex-col items-center">
<div className="container mx-auto max-w-5xl bg-white mt-10 p-6 rounded-lg shadow-lg">
<h2 className="text-xl font-bold mb-6">
정량 피드백 작성 ({currentStep}/43)
</h2>
{renderCurrentStep()}
<div className="flex justify-between mt-6">
<button
onClick={handlePreviousClick}
disabled={currentStep === 1}
className="px-6 py-2 bg-gray-400 text-white rounded-md shadow hover:bg-gray-500"
>
이전
</button>
<button
onClick={handleNextClick}
className="px-6 py-2 bg-blue-600 text-white rounded-md shadow hover:bg-blue-700"
>
{currentStep === 43 ? "완료" : "다음"}
</button>
</div>
</div>
</div>
);
}
export default DiagnosisFeedbackPage;
이코드는 전문가(관리자)가 정량 평가결과에 대한 피드백을 작성하는 페이지입니다.
사용자는 각 질문별 피드백을 입력하고, 모든 질문이 완료되면 서버로 저장한 후 정성 피드백 페이지로 이동합니다.
1. 주요 기능 요약
✅ 서버에서 정량 평가 데이터를 가져와 화면에 표시
✅ 각 질문에 대해 전문가가 피드백 입력 가능
✅ ‘다음’ 버튼을 클릭하면 다음 질문으로 이동
✅ 마지막 질문에서는 모든 피드백을 서버에 저장하고 다음 페이지로 이동
2. 주요 상태 및 변수
const [quantitativeData, setQuantitativeData] = useRecoilState(quantitativeDataState);
const [feedbacks, setFeedbacks] = useRecoilState(quantitativeFeedbackState);
const [currentStep, setCurrentStep] = useRecoilState(currentStepState);
const [responses, setResponses] = useState({});
quantitativeData → 서버에서 불러온 정량 평가 데이터를 저장합니다.
feedbacks → 전문가가 입력한 피드백을 저장하는 상태입니다.
currentStep → 현재 보고 있는 문항 번호 (1~43).
responses → { question_number: { response, feedback } } 형태로 각 문항에 대한 피드백 저장합니다.
3. 서버에서 정량 평가 데이터를 가져오기
useEffect(() => {
if (!systemId) {
console.error("System ID가 누락되었습니다.");
alert("시스템 정보가 없습니다. 대시보드로 이동합니다.");
navigate("/dashboard");
return;
}
const fetchQuantitativeData = async () => {
try {
const response = await axios.get(
"http://localhost:3000/selftest/quantitative",
{ params: { systemId }, withCredentials: true }
);
const data = response.data || [];
setQuantitativeData(data);
const initialResponses = data.reduce((acc, item) => {
acc[item.question_number] = {
response: item.response || "",
feedback: item.feedback || "피드백 없음",
};
return acc;
}, {});
setResponses(initialResponses);
} catch (error) {
console.error("Error fetching quantitative data:", error);
alert("데이터를 불러오는 중 오류가 발생했습니다.");
}
};
fetchQuantitativeData();
}, [systemId, navigate, setQuantitativeData]);
주요 기능
systemId가 없으면 대시보드로 이동합니다.
GET /selftest/quantitative 요청을 보내 시스템의 정량 평가 데이터를 가져옵니다.
가져온 데이터를 quantitativeData 상태에 저장합니다.
responses 상태를 초기화하여 각 문항에 대한 피드백을 저장할 수 있도록 준비합니다.
4. 모든 피드백을 서버에 저장
const saveAllFeedbacks = async () => {
const feedbackData = quantitativeData.map((item) => ({
questionNumber: item.question_number,
systemId,
feedback: responses[item.question_number]?.feedback || "피드백 없음",
}));
console.log("Sending feedback data:", feedbackData);
try {
const response = await axios.post(
"http://localhost:3000/selftest/quantitative/feedback",
{ systemId, feedbackResponses: feedbackData },
{ withCredentials: true }
);
alert(response.data.msg || "모든 피드백이 저장되었습니다.");
console.log("Navigating to /QualitativeSurveyfeedback with systemId:", systemId);
navigate("/QualitativeSurveyfeedback", { state: { systemId } });
} catch (error) {
console.error("Error saving feedback:", error.response?.data || error);
alert(
error.response?.data?.msg ||
"피드백 저장 중 오류가 발생했습니다. 다시 시도해주세요."
);
}
};
주요 기능
feedbackData 배열을 생성해 모든 문항의 피드백을 정리합니다.
POST /selftest/quantitative/feedback 요청을 보내 서버에 저장합니다.
저장 완료 후 정성 평가 피드백 페이지(/QualitativeSurveyfeedback)로 이동합니다.
QualitativeSurveyFeedback.jsx
import React, { useEffect, useState } from "react";
import axios from "axios";
import { useLocation, useNavigate } from "react-router-dom";
function QualitativeSurveyFeedback() {
const [qualitativeData, setQualitativeData] = useState([]);
const [feedbacks, setFeedbacks] = useState({});
const [currentStep, setCurrentStep] = useState(1);
const [files, setFiles] = useState({});
const location = useLocation();
const navigate = useNavigate();
const { systemId } = location.state || {};
useEffect(() => {
const fetchQualitativeData = async () => {
try {
const response = await axios.get(
"http://localhost:3000/selftest/qualitative",
{ params: { systemId }, withCredentials: true }
);
setQualitativeData(response.data || []);
const initialFeedbacks = (response.data || []).reduce((acc, item) => {
acc[item.question_number] = {
feedback: item.feedback || "",
additionalComment: item.additional_comment || "",
response: item.response || "",
file: item.file_path || "",
};
return acc;
}, {});
setFeedbacks(initialFeedbacks);
} catch (error) {
console.error("Error fetching qualitative data:", error);
alert("데이터를 불러오는 중 오류가 발생했습니다.");
}
};
fetchQualitativeData();
}, [systemId]);
const handleFeedbackChange = (field, questionNumber, value) => {
setFeedbacks((prev) => ({
...prev,
[questionNumber]: {
...prev[questionNumber],
[field]: value,
},
}));
};
const handleNextClick = async () => {
if (currentStep < qualitativeData.length) {
setCurrentStep((prev) => prev + 1);
} else {
try {
// 상태 업데이트 요청
const response = await axios.post(
"http://localhost:3000/selftest/qualitative/update-status",
{ systemId },
{ withCredentials: true }
);
console.log("Feedback status updated:", response.data.msg);
alert("피드백 상태가 성공적으로 업데이트되었습니다.");
navigate("/system-management");
} catch (error) {
console.error(
"Error updating feedback status:",
error.response?.data || error.message
);
alert("피드백 상태 업데이트 중 오류가 발생했습니다.");
}
}
};
const handlePreviousClick = () => {
if (currentStep > 1) {
setCurrentStep((prev) => prev - 1);
}
};
const renderCurrentStep = () => {
if (qualitativeData.length === 0) {
return <p>데이터를 불러오는 중입니다...</p>;
}
const currentData = qualitativeData.find(
(item) => item.question_number === currentStep
) || {
question_number: currentStep,
indicator: "질문 없음",
indicator_definition: "",
evaluation_criteria: "",
reference_info: "",
};
return (
<table className="w-full border-collapse border border-gray-300 mb-6">
<tbody>
<tr>
<td className="border border-gray-300 p-2 bg-gray-200 w-1/5">
지표 번호
</td>
<td className="border border-gray-300 p-2 w-4/5">{currentStep}</td>
</tr>
<tr>
<td className="border border-gray-300 p-2 bg-gray-200">지표</td>
<td className="border border-gray-300 p-2">
{currentData.indicator || "N/A"}
</td>
</tr>
<tr>
<td className="border border-gray-300 p-2 bg-gray-200">
지표 정의
</td>
<td className="border border-gray-300 p-2 h-24">
{currentData.indicator_definition || "N/A"}
</td>
</tr>
<tr>
<td className="border border-gray-300 p-2 bg-gray-200">
평가기준 (착안사항)
</td>
<td className="border border-gray-300 p-2 h-24">
{currentData.evaluation_criteria || "N/A"}
</td>
</tr>
<tr>
<td className="border border-gray-300 p-2 bg-gray-200">참고사항</td>
<td className="border border-gray-300 p-2 h-20">
{currentData.reference_info || "N/A"}
</td>
</tr>
<tr>
<td className="border border-gray-300 p-2 bg-gray-200">
파일첨부(선택)
</td>
<td className="border border-gray-300 p-2">
<div className="flex items-center">
<input
type="file"
className="w-full p-2 border rounded-md"
disabled
/>
<p className="text-sm text-gray-500 mt-2">
파일 첨부는 비활성화되었습니다.
</p>
</div>
</td>
</tr>
<tr>
<td className="border border-gray-300 p-2 bg-gray-200">평가</td>
<td className="border border-gray-300 p-2">
<div className="flex items-center space-x-4">
<label>
<input
type="radio"
name={`response-${currentStep}`}
value="자문 필요"
checked={feedbacks[currentStep]?.response === "자문 필요"}
readOnly
disabled
className="mr-2"
/>
자문 필요
</label>
<label>
<input
type="radio"
name={`response-${currentStep}`}
value="해당 없음"
checked={feedbacks[currentStep]?.response === "해당 없음"}
readOnly
disabled
className="mr-2"
/>
해당 없음
</label>
</div>
</td>
</tr>
<tr>
<td className="border border-gray-300 p-2 bg-gray-200">
자문 내용
</td>
<td className="border border-gray-300 p-2">
<textarea
placeholder="자문 내용을 입력하세요"
value={feedbacks[currentStep]?.additionalComment || ""}
readOnly
disabled
className="w-full p-2 border rounded-md"
></textarea>
</td>
</tr>
<tr>
<td className="border border-gray-300 p-2 bg-gray-200">피드백</td>
<td className="border border-gray-300 p-2">
<textarea
placeholder="피드백을 입력하세요"
value={feedbacks[currentStep]?.feedback || ""}
onChange={(e) =>
handleFeedbackChange("feedback", currentStep, e.target.value)
}
className="w-full p-2 border rounded-md"
></textarea>
</td>
</tr>
</tbody>
</table>
);
};
return (
<div className="container mx-auto p-6">
<h2 className="text-2xl font-bold mb-6">
정성 피드백 작성 ({currentStep}/{qualitativeData.length})
</h2>
{renderCurrentStep()}
<div className="flex justify-between mt-6">
<button
onClick={handlePreviousClick}
disabled={currentStep === 1}
className="px-6 py-2 bg-gray-400 text-white rounded-md shadow hover:bg-gray-500"
>
이전
</button>
<button
onClick={handleNextClick}
className="px-6 py-2 bg-blue-600 text-white rounded-md shadow hover:bg-blue-700"
>
{currentStep === qualitativeData.length ? "저장 후 완료" : "다음"}
</button>
</div>
</div>
);
}
export default QualitativeSurveyFeedback;
이코드는 전문가(관리자)가 정성 평가(qualitative) 결과에 대한 피드백을 작성하는 페이지입니다.
사용자들이 작성한 기존 응답을 불러와 표시하고, 전문가가 피드백을 입력할 수 있도록 합니다.
문항을 앞뒤로 이동하면서 피드백을 작성 가능합니다.
마지막 문항에서 모든 피드백을 저장하고 상태를 업데이트합니다.
1. 주요 상태 및 변수
const [qualitativeData, setQualitativeData] = useState([]);
const [feedbacks, setFeedbacks] = useState({});
const [currentStep, setCurrentStep] = useState(1);
const [files, setFiles] = useState({});
const location = useLocation();
const navigate = useNavigate();
const { systemId } = location.state || {};
qualitativeData → 서버에서 가져온 정성 평가 데이터 저장합니다.
feedbacks → 전문가가 입력한 피드백을 저장하는 상태입니다.
currentStep → 현재 보고 있는 문항 번호 (1~최대 문항 개수)입니다.
files → 파일 업로드 기능을 위한 상태 (현재는 비활성화)입니다.
systemId → 평가 중인 시스템의 고유 ID (URL 상태에서 가져옴)입니다.
2. 서버에서 정성 평가 데이터를 가져오기
useEffect(() => {
const fetchQualitativeData = async () => {
try {
const response = await axios.get(
"http://localhost:3000/selftest/qualitative",
{ params: { systemId }, withCredentials: true }
);
setQualitativeData(response.data || []);
const initialFeedbacks = (response.data || []).reduce((acc, item) => {
acc[item.question_number] = {
feedback: item.feedback || "",
additionalComment: item.additional_comment || "",
response: item.response || "",
file: item.file_path || "",
};
return acc;
}, {});
setFeedbacks(initialFeedbacks);
} catch (error) {
console.error("Error fetching qualitative data:", error);
alert("데이터를 불러오는 중 오류가 발생했습니다.");
}
};
fetchQualitativeData();
}, [systemId]);
주요 기능
GET /selftest/qualitative 요청을 보내 정성 평가 데이터를 가져옵니다.
qualitativeData 상태에 데이터를 저장합니다.
가져온 데이터를 기반으로 feedbacks 상태를 초기화하여 기존 피드백을 미리 채웁니다.
3. 피드백 입력 처리
const handleFeedbackChange = (field, questionNumber, value) => {
setFeedbacks((prev) => ({
...prev,
[questionNumber]: {
...prev[questionNumber],
[field]: value,
},
}));
};
기능
사용자가 피드백을 입력하면 feedbacks 상태를 업데이트합니다.
field 값을 기반으로 특정 필드(feedback, additionalComment)를 업데이트합니다.
4. 피드백 입력 필드
<tr>
<td className="border border-gray-300 p-2 bg-gray-200">피드백</td>
<td className="border border-gray-300 p-2">
<textarea
placeholder="피드백을 입력하세요"
value={feedbacks[currentStep]?.feedback || ""}
onChange={(e) =>
handleFeedbackChange("feedback", currentStep, e.target.value)
}
className="w-full p-2 border rounded-md"
></textarea>
</td>
</tr>
기능
현재 문항의 피드백을 textarea로 입력 가능합니다.
입력값이 변경되면 handleFeedbackChange()를 호출해 상태 업데이트합니다.