개인정보-컴플라이언스-웹애플리케이션 (수정사항 3)

2025. 1. 29. 18:23프로젝트

728x90

정량 데이터 가져오기 코드 수정부분

const getQuantitativeData = async (req, res) => {
  const { systemId } = req.query;

  if (!systemId) {
    return res.status(400).json({ message: "System ID is required." });
  }

  try {
    const query = `
      SELECT question_number, unit, evaluation_method, score, question,
             legal_basis, criteria_and_references, file_upload, response, feedback
      FROM quantitative
      WHERE system_id = ?
    `;
    const [results] = await pool.query(query, [systemId]);

    res.status(200).json(results);
  } catch (error) {
    console.error("Error fetching quantitative data:", error.message);
    res
      .status(500)
      .json({ message: "Internal server error.", error: error.message });
  }
};

 

이제 전문가가 기관회원이 입력한 "이행 여부", "첨부 파일", "평가기준" 등을 확인할 수 있음.

 

정량 피드백 코드 수정전

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;

 

수정후 코드

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 {
        // 1️⃣ 기관회원 userId 조회
        const ownerResponse = await axios.get(
          "http://localhost:3000/system-owner",
          { params: { systemId }, withCredentials: true }
        );

        if (!ownerResponse.data.userId) {
          console.error("❌ 기관회원 ID 조회 실패:", ownerResponse.data);
          alert("기관회원 정보를 가져올 수 없습니다.");
          return;
        }

        const userId = ownerResponse.data.userId;
        console.log("✅ 기관회원 ID 조회 성공:", userId);

        // 2️⃣ 정량 질문 조회
        const questionResponse = await axios.get(
          "http://localhost:3000/selftest/quantitative",
          { params: { systemId }, withCredentials: true }
        );
        const questions = questionResponse.data || [];

        // 3️⃣ 정량 응답 조회 (userId 추가)
        const responseResponse = await axios.get(
          "http://localhost:3000/selftest/quantitative/responses",
          { params: { systemId, userId }, withCredentials: true }
        );
        const responses = responseResponse.data || [];

        // 4️⃣ 응답 데이터를 질문 데이터와 병합 (🚨 추가적인 `additional_comment` 반영)
        const responseMap = responses.reduce((acc, item) => {
          acc[item.question_number] = item;
          return acc;
        }, {});

        const mergedData = questions.map((question) => ({
          ...question,
          response:
            responseMap[question.question_number]?.response || "응답 없음",
          feedback:
            responseMap[question.question_number]?.feedback || "피드백 없음",
          additional_comment:
            responseMap[question.question_number]?.additional_comment || "",
          file_path: responseMap[question.question_number]?.file_path || "",
        }));

        setQuantitativeData(mergedData);
        console.log("✅ 병합된 정량 데이터:", mergedData);

        // 5️⃣ responses 상태도 업데이트 (UI 반영용)
        const initialResponses = mergedData.reduce((acc, item) => {
          acc[item.question_number] = {
            response: item.response,
            feedback: item.feedback,
            additionalComment: item.additional_comment || "", // ✅ 추가
          };
          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,
        // ✅ "자문 필요" 선택 시 기존의 `additional_comment` 유지
        additionalComment:
          responses[questionNumber]?.response === "자문 필요"
            ? responses[questionNumber]?.additionalComment || ""
            : "",
      },
    }));
  };

  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: "N/A",
      evaluation_method: "N/A",
      score: "N/A",
      question: "질문 없음",
      legal_basis: "N/A",
      criteria_and_references: "N/A",
      file_upload: "",
      response: "",
      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}</td>
          </tr>
          <tr>
            <td className="bg-gray-200 p-2 border">배점</td>
            <td className="p-2 border">{currentData.score}</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}
            </td>
          </tr>
          <tr>
            <td className="bg-gray-200 p-2 border">평가기준</td>
            <td colSpan="3" className="p-2 border">
              {currentData.evaluation_criteria}
            </td>
          </tr>

          <tr>
            <td className="bg-gray-200 p-2 border">파일 첨부</td>
            <td colSpan="3" className="p-2 border">
              {currentData.file_upload ? (
                <a
                  href={currentData.file_upload}
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  첨부 파일 보기
                </a>
              ) : (
                "파일 없음"
              )}
            </td>
          </tr>
          <tr>
            <td className="bg-gray-200 p-2 border">기관회원 응답</td>
            <td colSpan="3" className="p-2 border">
              <input
                type="text"
                value={currentData.response || "응답 없음"}
                readOnly
                className="w-full p-2 border border-gray-300 bg-gray-100"
              />
            </td>
          </tr>
          {/* 🚨 "자문 필요"일 경우 추가적인 자문 내용 표시 */}
          {currentData.response === "자문 필요" && (
            <tr>
              <td className="bg-gray-200 p-2 border">자문 내용</td>
              <td colSpan="3" className="p-2 border">
                <textarea
                  value={currentData.additional_comment || "자문 내용 없음"}
                  readOnly
                  className="w-full p-2 border border-gray-300 bg-gray-100"
                />
              </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;

주요 차이점 비교

비교 항목첫 번째 코드 (개선된 버전) 두 번째 코드 (기존 버전)

시스템 정보 체크 systemId가 없을 경우 navigate("/dashboard") 동일
데이터 요청 방식 ✅ system-owner API를 추가 호출하여 기관회원 userId 먼저 조회 ❌ userId 없이 systemId만으로 데이터 요청
정량 평가 데이터 요청 axios.get("selftest/quantitative/responses") 사용하여 기관회원 응답과 병합 axios.get("selftest/quantitative") 요청 후 응답 없이 사용
상태 관리 방식 ✅ setResponses()에서 additionalComment 유지 ❌ additionalComment 유지 X
로딩 처리 (loading 상태 추가) ✅ loading 상태를 추가하여 데이터 요청 중 UI 변경 ❌ loading 상태 없음
UI (로딩 화면 추가) ✅ loading 상태에 따라 로딩 중... 텍스트 표시 ❌ 로딩 상태 없이 바로 데이터 표시