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

2025. 1. 29. 19:05프로젝트

728x90
반응형

전문가가 자신에게 배정된 시스템의 자가진단 결과에 피드백을 작성하기 위해 다음과같이 접근해승ㄹ때 기관회원의 응답을 받아오지못하고있다.

 

수정코드

src/pages/manager/SystemManagement.jsx

import React, { useEffect } from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { expertAuthState } from "../../state/authState";
import { systemsState } from "../../state/system";
import { useNavigate } from "react-router-dom";
import axios from "axios";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSignOutAlt } from "@fortawesome/free-solid-svg-icons";

function SystemManagement() {
  const expert = useRecoilValue(expertAuthState);
  const [systems, setSystems] = useRecoilState(systemsState);
  const navigate = useNavigate();

  useEffect(() => {
    const fetchAssignedSystems = async () => {
      if (!expert.user || !expert.user.id) return;

      try {
        const response = await axios.get(
          `http://localhost:3000/assigned-systems?expertId=${expert.user.id}`,
          { withCredentials: true }
        );
        console.log("✅ 매칭된 시스템 데이터:", response.data);

        const uniqueSystems = response.data.data.filter(
          (value, index, self) =>
            index === self.findIndex((t) => t.system_id === value.system_id)
        );
        setSystems(uniqueSystems || []);
      } catch (error) {
        console.error("❌ 매칭된 시스템 가져오기 실패:", error);
      }
    };

    if (expert.isLoggedIn) {
      fetchAssignedSystems();
    }
  }, [expert, setSystems]);

  const handleLogout = async () => {
    try {
      const response = await axios.post(
        "http://localhost:3000/logout/expert",
        {},
        { withCredentials: true }
      );

      if (response.status === 200) {
        alert(response.data.msg || "로그아웃 성공");
        navigate("/login");
      } else {
        alert("로그아웃 실패");
      }
    } catch (error) {
      console.error("❌ 로그아웃 요청 실패:", error);
      alert("로그아웃 중 오류가 발생했습니다.");
    }
  };

  const handleViewResults = (system) => {
    navigate("/completion", {
      state: {
        systemId: system.system_id,
        userId: expert.user?.id,
        userType: "전문가", // 전문가로 접근했음을 표시
      },
    });
  };

  const handleProvideFeedback = (system) => {
    navigate("/DiagnosisfeedbackPage", {
      state: {
        userId: expert.user?.id,
        systemId: system.system_id,
      },
    });
  };

  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-100">
      <div className="w-full max-w-[1000px] flex flex-col items-center">
        <header className="w-full h-[70px] bg-blue-600 flex items-center justify-between px-5 text-white mb-6 shadow-md rounded-lg">
          <h1 className="text-lg font-semibold">전문가 대시보드</h1>
        </header>

        <div className="bg-white rounded-lg w-full min-h-[500px] p-5 shadow-md">
          <h2 className="text-2xl font-semibold text-gray-700 mb-5 text-center">
            배정된 시스템 관리
          </h2>
          <table className="table-auto w-full border-collapse">
            <thead>
              <tr className="bg-gray-200">
                <th className="p-3 border text-left font-semibold">시스템명</th>
                <th className="p-3 border text-left font-semibold">기관명</th>
                <th className="p-3 border text-left font-semibold">
                  피드백 상태
                </th>
                <th className="p-3 border text-center font-semibold">관리</th>
              </tr>
            </thead>
            <tbody>
              {systems.length > 0 ? (
                systems.map((system) => (
                  <tr key={system.system_id} className="hover:bg-gray-50">
                    <td className="p-3 border">{system.system_name}</td>
                    <td className="p-3 border">{system.institution_name}</td>
                    <td className="p-3 border text-center">
                      {system.feedback_status ===
                      "전문가 자문이 반영되었습니다" ? (
                        <span className="text-green-600">
                          {system.feedback_status}
                        </span>
                      ) : (
                        <span className="text-red-600">반영 전</span>
                      )}
                    </td>
                    <td className="p-3 border text-center">
                      <button
                        onClick={() => handleViewResults(system)}
                        className="px-4 py-2 bg-green-500 text-white rounded-lg hover:bg-green-600 mr-2"
                      >
                        결과 보기
                      </button>
                      <button
                        onClick={() => handleProvideFeedback(system)}
                        className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600"
                      >
                        피드백 하기
                      </button>
                    </td>
                  </tr>
                ))
              ) : (
                <tr>
                  <td colSpan="4" className="text-center p-3 text-gray-500">
                    배정된 시스템이 없습니다.
                  </td>
                </tr>
              )}
            </tbody>
          </table>
        </div>
      </div>

      <button
        className="fixed bottom-5 right-5 bg-blue-500 text-white p-4 rounded-full shadow-lg hover:bg-blue-600 w-[100px] h-[100px] flex items-center justify-center flex-col"
        onClick={handleLogout}
      >
        <FontAwesomeIcon icon={faSignOutAlt} size="2xl" />
        <p>로그아웃</p>
      </button>
    </div>
  );
}

export default SystemManagement;

 

 

src/pages/feedback/DiagnosisfeedbackPage.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 {
        // 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;

 

src/pages/feedback/QualitativeSurveyfeedback.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 {
        // 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;
-- 데이터베이스 설정
DROP DATABASE IF EXISTS test2;
CREATE DATABASE test2;
USE test2;

-- 데이터베이스 설정
ALTER DATABASE test2 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 회원 테이블
CREATE TABLE User (
    id INT AUTO_INCREMENT PRIMARY KEY, 
    institution_name VARCHAR(255) NOT NULL COMMENT '기관명',
    institution_address VARCHAR(255) NOT NULL COMMENT '기관 주소',
    representative_name VARCHAR(255) NOT NULL COMMENT '대표자 이름',
    email VARCHAR(255) NOT NULL UNIQUE COMMENT '이메일',
    password VARCHAR(255) NOT NULL COMMENT '비밀번호',
    phone_number VARCHAR(15) NOT NULL COMMENT '전화번호',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '가입 날짜',
    member_type VARCHAR(20) NOT NULL DEFAULT '기관회원' COMMENT '회원 유형',
    email_verified BOOLEAN NOT NULL DEFAULT FALSE COMMENT '이메일 인증 여부',
    email_token VARCHAR(255) DEFAULT NULL COMMENT '이메일 인증 토큰',
    email_token_expiration DATETIME DEFAULT NULL COMMENT '이메일 토큰 만료 시간'
);

-- 전문가 회원 테이블
CREATE TABLE Expert (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '전문가 ID',
    name VARCHAR(255) NOT NULL COMMENT '전문가 이름',
    institution_name VARCHAR(255) NOT NULL COMMENT '소속 기관명',
    ofcps VARCHAR(255) NOT NULL COMMENT '전문가 직책',
    phone_number VARCHAR(255) NOT NULL COMMENT '전화번호',
    email VARCHAR(255) NOT NULL UNIQUE COMMENT '이메일',
    major_carrea VARCHAR(255) NOT NULL COMMENT '전문 경력',
    password VARCHAR(255) NOT NULL COMMENT '비밀번호'
);

-- 슈퍼유저 테이블
CREATE TABLE SuperUser (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '슈퍼유저 ID',
    name VARCHAR(255) NOT NULL COMMENT '이름',
    email VARCHAR(255) NOT NULL UNIQUE COMMENT '이메일',
    password VARCHAR(255) NOT NULL COMMENT '비밀번호',
    phone_number VARCHAR(255) NOT NULL COMMENT '전화번호',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '가입 날짜',
    member_type VARCHAR(50) NOT NULL DEFAULT 'superuser' COMMENT '회원 유형'
);

-- 시스템 테이블
CREATE TABLE Systems (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '시스템 ID',
    user_id INT NOT NULL COMMENT '회원 ID',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '등록 날짜',
    name VARCHAR(255) NOT NULL COMMENT '시스템 이름',
    min_subjects INT NOT NULL COMMENT '최소 문항 수',
    max_subjects INT NOT NULL COMMENT '최대 문항 수',
    purpose VARCHAR(255) NOT NULL COMMENT '처리 목적',
    is_private BOOLEAN NOT NULL COMMENT '민감 정보 포함 여부',
    is_unique BOOLEAN NOT NULL COMMENT '고유 식별 정보 포함 여부',
    is_resident BOOLEAN NOT NULL COMMENT '주민등록번호 포함 여부',
    reason ENUM('동의', '법적 근거', '기타') NOT NULL COMMENT '수집 근거',
    assessment_status ENUM('시작전', '완료') NOT NULL COMMENT '평가 상태',
    FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE
);

-- 자가진단 입력 테이블
CREATE TABLE SelfAssessment (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '입력 ID',
    user_id INT NOT NULL COMMENT '회원 ID',
    system_id INT NOT NULL COMMENT '시스템 ID',
    user_scale VARCHAR(255) NOT NULL COMMENT '사용자 규모',
    organization ENUM('교육기관', '공공기관', '국가기관') NOT NULL COMMENT '공공기관 분류',
    personal_info_system ENUM('있음', '없음') NOT NULL COMMENT '개인정보처리 시스템 여부',
    homepage_privacy VARCHAR(255) DEFAULT '없음',
    submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '제출 시간',
    FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE,
    FOREIGN KEY (system_id) REFERENCES Systems(id) ON DELETE CASCADE
);

-- 전문가 회원 - 시스템 (N:M) 담당 테이블
CREATE TABLE Assignment (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '담당 ID',
    expert_id INT NOT NULL COMMENT '전문가 ID',
    system_id INT NOT NULL COMMENT '시스템 ID',
    assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '할당 날짜',
    feedback_status BOOLEAN NOT NULL COMMENT '피드백 완료 여부',
    FOREIGN KEY (expert_id) REFERENCES Expert(id) ON DELETE CASCADE,
    FOREIGN KEY (system_id) REFERENCES Systems(id) ON DELETE CASCADE,
    UNIQUE KEY unique_assignment (expert_id, system_id)
);

-- 정량 문항 테이블
CREATE TABLE QuantitativeQuestions (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '문항 ID',
    question_number INT NOT NULL UNIQUE COMMENT '문항 번호',
    question TEXT NOT NULL COMMENT '문항 내용',
    evaluation_criteria TEXT DEFAULT NULL COMMENT '평가기준',
    legal_basis TEXT DEFAULT NULL COMMENT '근거 법령',
    score DECIMAL(5,2) DEFAULT NULL COMMENT '배점'
);

-- 정량 응답 테이블
CREATE TABLE QuantitativeResponses (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '응답 ID',
    system_id INT NOT NULL COMMENT '시스템 ID',
    user_id INT NOT NULL COMMENT '회원 ID',
    question_id INT NOT NULL COMMENT '문항 ID',
    response ENUM('이행', '미이행', '해당없음', '자문 필요') DEFAULT NULL COMMENT '응답',
    additional_comment TEXT DEFAULT NULL COMMENT '추가 의견',
    file_path VARCHAR(255) DEFAULT NULL COMMENT '파일 업로드 경로',
    feedback TEXT COMMENT '전문가 피드백',
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (system_id) REFERENCES Systems(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE,
    FOREIGN KEY (question_id) REFERENCES QuantitativeQuestions(id) ON DELETE CASCADE,
    UNIQUE KEY unique_response (system_id, user_id, question_id)
);

-- 정성 문항 테이블
CREATE TABLE QualitativeQuestions (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '문항 ID',
    question_number INT NOT NULL UNIQUE COMMENT '문항 번호',
    indicator TEXT NOT NULL COMMENT '지표',
    indicator_definition TEXT DEFAULT NULL COMMENT '지표 정의',
    evaluation_criteria TEXT DEFAULT NULL COMMENT '평가기준',
    reference_info TEXT DEFAULT NULL COMMENT '참고사항'
);

-- 정성 응답 테이블
CREATE TABLE QualitativeResponses (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '응답 ID',
    system_id INT NOT NULL COMMENT '시스템 ID',
    user_id INT NOT NULL COMMENT '회원 ID',
    question_id INT NOT NULL COMMENT '문항 ID',
    response ENUM('자문 필요', '해당없음') DEFAULT NULL COMMENT '응답 상태',
    additional_comment TEXT DEFAULT NULL COMMENT '추가 의견',
    file_path VARCHAR(255) DEFAULT NULL COMMENT '파일 업로드 경로',
    feedback TEXT COMMENT '전문가 피드백',
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (system_id) REFERENCES Systems(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE,
    FOREIGN KEY (question_id) REFERENCES QualitativeQuestions(id) ON DELETE CASCADE
);

-- 자가진단 결과 테이블
CREATE TABLE AssessmentResult (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '결과 ID',
    system_id INT NOT NULL COMMENT '시스템 ID',
    user_id INT NOT NULL COMMENT '회원 ID',
    assessment_id INT NOT NULL COMMENT '자가진단 입력 ID',
    score INT NOT NULL COMMENT '점수',
    feedback_status ENUM('전문가 자문이 반영되기 전입니다', '전문가 자문이 반영되었습니다') NOT NULL COMMENT '피드백 상태',
    completed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '완료 시간',
    grade ENUM('S', 'A', 'B', 'C', 'D') NOT NULL COMMENT '등급',
    FOREIGN KEY (system_id) REFERENCES Systems(id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE,
    FOREIGN KEY (assessment_id) REFERENCES SelfAssessment(id) ON DELETE CASCADE
);

-- 피드백 테이블
CREATE TABLE Feedback (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '피드백 ID',
    assessment_result_id INT NOT NULL COMMENT '자가진단 결과 ID',
    assignment_id INT NOT NULL COMMENT '담당 시스템 ID',
    feedback_content TEXT NOT NULL COMMENT '피드백 내용',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '피드백 생성 날짜',
    FOREIGN KEY (assessment_result_id) REFERENCES AssessmentResult(id) ON DELETE CASCADE,
    FOREIGN KEY (assignment_id) REFERENCES Assignment(id) ON DELETE CASCADE
);

-- ✅ 자가진단 문항 데이터들은 미리삽입 
INSERT INTO qualitative_questions (question_number, indicator, indicator_definition, evaluation_criteria, reference_info)
VALUES 
(1, '개인정보 보호 정책이 적절한가?', '개인정보 보호 정책이 기관 내에서 효과적으로 적용되는지 여부', '보호 정책 준수 여부', '개인정보 보호법 제29조'),
(2, '개인정보 보호 교육이 효과적인가?', '기관에서 시행하는 개인정보 보호 교육의 효과성', '교육 참여율 및 만족도', '개인정보 보호법 제30조'),
(3, '개인정보 보호 책임자의 역할이 명확한가?', '기관 내 보호책임자의 역할과 책임이 명확한지', '책임자의 업무 수행 여부', '개인정보 보호법 제31조'),
(4, '개인정보 보관 및 삭제 기준이 명확한가?', '보관 및 삭제 절차가 체계적으로 운영되는지 여부', '보관 및 삭제 정책 준수 여부', '개인정보 보호법 제32조'),
(5, '외부 위탁업체의 개인정보 보호가 적절한가?', '개인정보 처리 업무 위탁 시 보호 조치의 적절성', '위탁업체의 보호 대책', '개인정보 보호법 제33조'),
(6, '개인정보 보호를 위한 내부 감사가 이루어지는가?', '정기적으로 내부 감사를 수행하는지 여부', '내부 감사 수행 빈도 및 보고서', '개인정보 보호법 제34조'),
(7, '개인정보 보호 사고 발생 시 대응이 적절한가?', '유출 사고 발생 시 신속한 대응 및 후속 조치 여부', '사고 대응 절차 및 개선 조치', '개인정보 보호법 제35조'),
(8, '개인정보 보호 관련 법령 개정 사항을 반영하고 있는가?', '최신 법령 개정 사항을 보호 대책에 반영하는지 여부', '법률 개정 반영 여부', '개인정보 보호법 제36조');


INSERT INTO quantitative_questions (question_number, question, evaluation_criteria, legal_basis, score)
VALUES 
(1, '개인정보 보호 정책이 수립되어 있는가?', '정책 문서화 여부', '개인정보 보호법 제29조', 5),
(2, '개인정보 보호 교육이 정기적으로 이루어지는가?', '교육 시행 여부', '개인정보 보호법 제30조', 5),
(3, '개인정보 보호책임자가 지정되어 있는가?', '책임자 지정 여부', '개인정보 보호법 제31조', 5),
(4, '개인정보 보호 대책이 명확하게 정의되어 있는가?', '보호 대책 명확성', '개인정보 보호법 제32조', 5),
(5, '비밀번호 정책이 시행되고 있는가?', '비밀번호 설정 및 변경 정책', '개인정보 보호법 제33조', 5),
(6, '개인정보 암호화가 적절히 수행되는가?', '암호화 적용 여부', '개인정보 보호법 제34조', 5),
(7, '개인정보 접근 통제 정책이 마련되어 있는가?', '접근 통제 여부', '개인정보 보호법 제35조', 5),
(8, '개인정보 보관 및 파기 정책이 수립되어 있는가?', '보관 기간 및 파기 기준', '개인정보 보호법 제36조', 5),
(9, '개인정보 이용 동의 절차가 적절히 운영되고 있는가?', '이용 동의 절차 여부', '개인정보 보호법 제37조', 5),
(10, '개인정보 처리방침이 공시되어 있는가?', '처리방침 공개 여부', '개인정보 보호법 제38조', 5),
(11, '개인정보 보호 관련 내부 점검이 정기적으로 이루어지는가?', '내부 점검 주기 및 결과 관리', '개인정보 보호법 제39조', 5),
(12, '개인정보 보호 대책이 최신 법령을 반영하고 있는가?', '법령 반영 여부', '개인정보 보호법 제40조', 5),
(13, '개인정보 보호를 위한 모니터링 시스템이 운영되고 있는가?', '모니터링 시스템 구축 여부', '개인정보 보호법 제41조', 5),
(14, '개인정보 보호를 위한 보안 장비가 도입되어 있는가?', '보안 장비 도입 여부', '개인정보 보호법 제42조', 5),
(15, '개인정보 처리 시스템에 대한 보안 점검이 이루어지는가?', '시스템 보안 점검 여부', '개인정보 보호법 제43조', 5),
(16, '개인정보 보호를 위한 위협 대응 체계가 마련되어 있는가?', '위협 대응 절차 여부', '개인정보 보호법 제44조', 5),
(17, '개인정보 보호를 위한 내부 감사를 수행하는가?', '내부 감사 실시 여부', '개인정보 보호법 제45조', 5),
(18, '개인정보 유출 사고 대응 계획이 마련되어 있는가?', '유출 사고 대응 여부', '개인정보 보호법 제46조', 5),
(19, '개인정보 보호책임자 교육이 정기적으로 이루어지는가?', '책임자 교육 여부', '개인정보 보호법 제47조', 5),
(20, '개인정보 처리자가 보안 서약을 하고 있는가?', '보안 서약 실시 여부', '개인정보 보호법 제48조', 5),
(21, '개인정보 처리 업무가 외부 위탁될 경우 계약이 적절히 이루어지는가?', '위탁 계약 체결 여부', '개인정보 보호법 제49조', 5),
(22, '외부 위탁 업체의 개인정보 보호 수준을 정기적으로 점검하는가?', '위탁 업체 점검 여부', '개인정보 보호법 제50조', 5),
(23, '개인정보 보호 대책이 국제 표준을 준수하고 있는가?', '국제 표준 준수 여부', '개인정보 보호법 제51조', 5),
(24, '개인정보 보호 조치가 비용 대비 효과적인가?', '비용 대비 효과 분석 여부', '개인정보 보호법 제52조', 5),
(25, '개인정보 보호 관련 법률 개정 사항을 반영하고 있는가?', '법률 개정 반영 여부', '개인정보 보호법 제53조', 5),
(26, '개인정보 보호 교육이 모든 직원에게 제공되고 있는가?', '교육 제공 여부', '개인정보 보호법 제54조', 5),
(27, '개인정보 보호 정책이 지속적으로 개선되고 있는가?', '지속적인 개선 여부', '개인정보 보호법 제55조', 5),
(28, '개인정보 보호 대책이 기술 발전을 반영하고 있는가?', '최신 기술 반영 여부', '개인정보 보호법 제56조', 5),
(29, '개인정보 보호 사고 사례가 공유되고 있는가?', '사고 사례 공유 여부', '개인정보 보호법 제57조', 5),
(30, '개인정보 보호 조치가 내부 규정에 따라 점검되고 있는가?', '내부 규정 준수 여부', '개인정보 보호법 제58조', 5),
(31, '개인정보 보호 계획이 적절히 이행되고 있는가?', '계획 이행 여부', '개인정보 보호법 제59조', 5),
(32, '개인정보 보호를 위한 보안 인증을 취득하였는가?', '보안 인증 여부', '개인정보 보호법 제60조', 5),
(33, '개인정보 보호 대책이 최신 법률 및 가이드라인을 따르고 있는가?', '법률 준수 여부', '개인정보 보호법 제61조', 5),
(34, '개인정보 보호 정책이 전체 직원에게 전달되고 있는가?', '정책 전달 여부', '개인정보 보호법 제62조', 5),
(35, '개인정보 보호를 위한 기술이 적절히 활용되고 있는가?', '보호 기술 활용 여부', '개인정보 보호법 제63조', 5),
(36, '개인정보 보호 대책이 기업 문화로 정착되고 있는가?', '기업 문화 정착 여부', '개인정보 보호법 제64조', 5),
(37, '개인정보 보호를 위한 보안 절차가 준수되고 있는가?', '보안 절차 준수 여부', '개인정보 보호법 제65조', 5),
(38, '개인정보 보호 계획이 경영진의 승인 하에 이루어지는가?', '경영진 승인 여부', '개인정보 보호법 제66조', 5),
(39, '개인정보 보호 조치가 데이터 보호 요구 사항을 충족하는가?', '데이터 보호 충족 여부', '개인정보 보호법 제67조', 5),
(40, '개인정보 보호 교육이 외부 전문가에 의해 제공되는가?', '외부 전문가 교육 여부', '개인정보 보호법 제68조', 5),
(41, '개인정보 보호 점검 결과가 보고되고 있는가?', '점검 보고 여부', '개인정보 보호법 제69조', 5),
(42, '개인정보 보호 위반 시 제재 조치가 마련되어 있는가?', '제재 조치 마련 여부', '개인정보 보호법 제70조', 5),
(43, '개인정보 보호 조치가 산업별 규제를 따르고 있는가?', '산업 규제 준수 여부', '개인정보 보호법 제71조', 5);


-- 슈퍼유저 만들기
INSERT INTO SuperUser (name, email, password, phone_number) 
VALUES ('김동욱', 'test@test', '5397', '010-1234-5678');

UPDATE SuperUser
SET member_type = 'superuser';