프로젝트

개인정보-컴플라이언스-웹애플리케이션(13) - (정량문항 피드백 , 정성문항피드백) 프론트 코드

알럽유 2025. 1. 29. 16:29
728x90

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()를 호출해 상태 업데이트합니다.