담당자
diff --git a/src/mock_data/mock_zonelist.js b/src/mock_data/mock_zonelist.js
new file mode 100644
index 0000000..fa8f19a
--- /dev/null
+++ b/src/mock_data/mock_zonelist.js
@@ -0,0 +1,27 @@
+/* mock data */
+const mockZoneList = [
+ {
+ title: "보일러실",
+ env_sensor: [
+ { name: "온도 센서", thres: 60 },
+ { name: "습도 센서", thres: 75 },
+ ],
+ fac_sensor: [],
+ master: "김00",
+ level: 2,
+ },
+ {
+ title: "휴게실",
+ env_sensor: [],
+ fac_sensor: ["진동 센서"],
+ master: "윤00",
+ level: 0
+ },
+ { title: "테스트룸A", env_sensor: [], fac_sensor: [], master: "정00", level: 0 },
+ { title: "테스트룸B", env_sensor: [], fac_sensor: [], master: "강00", level: 0 },
+ { title: "테스트룸C", env_sensor: [], fac_sensor: [], master: "이00", level: 0 },
+ { title: "테스트룸D", env_sensor: [], fac_sensor: [], master: "박00", level: 0 },
+];
+
+
+export default mockZoneList;
\ No newline at end of file
diff --git a/src/pages/Certifiaction.jsx b/src/pages/Certifiaction.jsx
new file mode 100644
index 0000000..243e114
--- /dev/null
+++ b/src/pages/Certifiaction.jsx
@@ -0,0 +1,7 @@
+export default function Certification() {
+ return (
+ <>
+
Certifiaction
+ >
+ );
+}
diff --git a/src/pages/Monitoring.jsx b/src/pages/Monitoring.jsx
new file mode 100644
index 0000000..bf0e3b0
--- /dev/null
+++ b/src/pages/Monitoring.jsx
@@ -0,0 +1,31 @@
+import { useEffect, useState } from "react";
+import MonitorBox from "../components/MonitorBox";
+import mockZoneList from "../mock_data/mock_zonelist";
+
+export default function Monitoring() {
+ const [zoneList, setZoneList] = useState([]);
+ // useEffect(() => {
+ // fetch("../mock_data/mock_zonelist.json")
+ // .then((res) => {
+ // setZoneList(res.data);
+ // })
+ // .catch((err) => {
+ // console.error("데이터 불러오기 실패", err);
+ // });
+ // }, []);
+ useEffect(() => {
+ setZoneList(mockZoneList);
+ });
+ return (
+ <>
+
Monitoring
+
+
+ {zoneList.map((z, i) => (
+
+ ))}
+
+
+ >
+ );
+}
diff --git a/src/pages/Settings.jsx b/src/pages/Settings.jsx
index 6ca0af9..da9802c 100644
--- a/src/pages/Settings.jsx
+++ b/src/pages/Settings.jsx
@@ -1,129 +1,154 @@
import { useEffect, useState } from "react";
-import Modal from "../components/Modal";
+import SensorModal from "../components/SensorModal";
import ZoneInfoBox from "../components/ZoneInfoBox";
import axios from "axios";
-/* ────────────────────────────────
- 1. 센서 타입 → 한글 명/분류 매핑
-──────────────────────────────── */
-const ENV_TYPES = ["temp", "humid"]; // 환경센서
-const FAC_TYPES = [ "dust", "current", "vibration"]; // 설비센서
-
-const toKoName = (type) => {
- switch (type) {
- case "temp": return "온도 센서";
- case "humid": return "습도 센서";
- case "dust": return "먼지 센서";
- case "current": return "전류 센서";
- case "vibration": return "진동 센서";
- default: return type;
- }
-};
+import FacilityModal from "../components/FacilityModal";
+// /* ────────────────────────────────
+// 1. 센서 타입 → 한글 명/분류 매핑
+// ──────────────────────────────── */
+// const ENV_TYPES = ["temp", "humid"]; // 환경센서
+// const FAC_TYPES = ["dust", "current", "vibration"]; // 설비센서
+
+// const toKoName = (type) => {
+// switch (type) {
+// case "temp":
+// return "온도 센서";
+// case "humid":
+// return "습도 센서";
+// case "dust":
+// return "먼지 센서";
+// case "current":
+// return "전류 센서";
+// case "vibration":
+// return "진동 센서";
+// default:
+// return type;
+// }
+// };
export default function Settings() {
- /* mock data */
+ const [sensorInfo, setSensorInfo] = useState(); // 모달 전달용
+ const [selectedZone, setSelectedZone] = useState(); // 모달 전달용
+ const [isSensorModalOpen, setSensorModalOpen] = useState(false); // 모달 열기
+ const [isFacilityModalOpen, setFacilityModalOpen] = useState(false); // 모달 열기
+ const [zoneList, setZoneList] = useState([]); // 존 추가하고 열기
+
const initialZoneList = [
{
title: "보일러실",
env_sensor: [
- { name: "온도 센서", thres: 60 },
- { name: "습도 센서", thres: 75 },
+ { name: "온도 센서", thres: 60, sensorId: "TEMP001" },
+ { name: "습도 센서", thres: 75, sensorId: "HUMID001" },
+ ],
+ facility: [
+ {
+ name: "설비A",
+ fac_sensor: [
+ { name: "진동 센서", id: "UA10T-VIB-24060890" },
+ { name: "온도 센서", id: "UA10T-TEM-24060890" },
+ ],
+ },
],
- fac_sensor: [],
master: "김00",
},
{
title: "휴게실",
env_sensor: [],
- fac_sensor: ["진동 센서"],
+ facility: [
+ {
+ name: "설비B",
+ fac_sensor: [
+ { name: "진동 센서", id: "UA10T-VIB-24060891" },
+ { name: "온도 센서", id: "UA10T-TEM-24060891" },
+ ],
+ },
+ ],
+ master: "윤00",
+ },
+ { title: "테스트룸A", env_sensor: [], facility: [], master: "정00" },
+ {
+ title: "테스트룸B",
+ env_sensor: [],
+ facility: [{ name: "설비A" }],
master: "윤00",
},
- { title: "테스트룸A", env_sensor: [], fac_sensor: [], master: "정00" },
];
- const [sensorInfo, setSensorInfo] = useState();
- const [isModalOpen, setModalOpen] = useState(false);
- const [zoneList, setZoneList] = useState([]);
-
useEffect(() => {
// axios
// .get("/api/zones")
// .then((res) => console.log(res))
// .catch((e) => console.log(e));
+ if (zoneList.length === 0) {
+ setZoneList(initialZoneList);
+ }
+ }, []);
- // axios.get(`http://localhost:8080/api/zones`) // ← GET /api/zones
- // .then((res) => {
- // const list = res.data.map((z) => ({
- // id: z.zoneId, // ← key 용
- // title: z.zoneName, // ← 화면에 찍힐 이름
- // // get 요청에서 데이터가 없으면 빈 배열 혹은 "" 할당해주기
- // env_sensor: z.env_sensor || [],
- // fac_sensor: z.fac_sensor || [],
- // master: z.master || "",
- // }));
- // setZoneList(list);
- // })
- // .catch((e) => console.error(e));
- // // db에 존 데이터가 없을 경우 초기 예시 데이터 보여줌
- // if (zoneList.length === 0) {
- // setZoneList(initialZoneList);
- // }
-
- /* ────────────────────────────────
+ // useEffect(() => {
+ /* ────────────────────────────────
2. ① 공간 + ② 센서를 한 번에 받아서 매핑
───────────────────────────────── */
- Promise.all([
- axios.get("http://localhost:8080/api/zones"),
- axios.get("http://localhost:8080/api/sensors") // ← location 컬럼 포함
- ])
- .then(([zoneRes, sensorRes]) => {
- const sensors = sensorRes.data;
-
- /* 센서를 zoneId 기준으로 그룹핑 → { 5: {env:[], fac:[]}, … } */
- const mapByZone = sensors.reduce((acc, s) => {
- const zoneId = s.location?.trim(); // ← 공백 대비
- if (!zoneId) return acc;
-
- if (!acc[zoneId]) acc[zoneId] = { env: [], fac: [] };
-
- const koName = toKoName(s.sensorType); // ※ 백엔드 키 sensorType
- //const item = { name: koName, thres: s.threshold ?? "-" };
- const item = { // ← 센서 공통 구조
- id: s.sensorId, // 새 필드
- name: toKoName(s.sensorType),
- thres: s.threshold ?? "-"
- };
-
- if (ENV_TYPES.includes(s.sensorType)) {
- acc[zoneId].env.push(item); // env → 객체 그대로
- } else if (FAC_TYPES.includes(s.sensorType)) {
- acc[zoneId].fac.push(item); // fac → 문자열만 push
- }
- return acc;
- }, {});
-
- /* zones 에 센서 배열을 끼워 넣어 최종 zoneList 작성 */
- const list = zoneRes.data.map((z) => ({
- id: z.zoneId.trim(),
- title: z.zoneName,
- env_sensor: mapByZone[z.zoneId]?.env || [],
- fac_sensor: mapByZone[z.zoneId]?.fac || [],
- master: z.master || "",
- }));
-
- /* 레코드가 하나도 없으면 예시 데이터 사용 */
- setZoneList(list.length ? list : initialZoneList);
- })
- .catch(console.error);
- }, []);
+ // Promise.all([
+ // axios.get("http://localhost:8080/api/zones"),
+ // axios.get("http://localhost:8080/api/sensors"), // ← location 컬럼 포함
+ // ])
+ // .then(([zoneRes, sensorRes]) => {
+ // const sensors = sensorRes.data;
+
+ // /* 센서를 zoneId 기준으로 그룹핑 → { 5: {env:[], fac:[]}, … } */
+ // const mapByZone = sensors.reduce((acc, s) => {
+ // const zoneId = s.location?.trim(); // ← 공백 대비
+ // if (!zoneId) return acc;
+
+ // if (!acc[zoneId]) acc[zoneId] = { env: [], fac: [] };
+
+ // const koName = toKoName(s.sensorType); // ※ 백엔드 키 sensorType
+ // const item = {
+ // // ← 센서 공통 구조
+ // id: s.sensorId, // 새 필드
+ // name: toKoName(s.sensorType),
+ // thres: s.threshold ?? "-",
+ // };
- const handleOpenModal = (zoneName, sensorName, thres) => {
+ // if (ENV_TYPES.includes(s.sensorType)) {
+ // acc[zoneId].env.push(item); // env → 객체 그대로
+ // } else if (FAC_TYPES.includes(s.sensorType)) {
+ // acc[zoneId].fac.push(item); // fac → 문자열만 push
+ // }
+ // return acc;
+ // }, {});
+
+ // /* zones 에 센서 배열을 끼워 넣어 최종 zoneList 작성 */
+ // const list = zoneRes.data.map((z) => ({
+ // id: z.zoneId.trim(),
+ // title: z.zoneName,
+ // env_sensor: mapByZone[z.zoneId]?.env || [],
+ // fac_sensor: mapByZone[z.zoneId]?.fac || [],
+ // master: z.master || "",
+ // }));
+
+ // /* 레코드가 하나도 없으면 예시 데이터 사용 */
+ // setZoneList(list.length ? list : initialZoneList);
+ // })
+ // .catch(console.error);
+ // }, []);
+
+ /* 모달 여는 동작 전용 함수 */
+ const handleOpenSensorModal = (zoneName, sensorName, thres) => {
setSensorInfo({ zoneName, sensorName, thres });
- setModalOpen(true);
+ setSensorModalOpen(true);
+ };
+
+ const handleOpenFacilityModal = (zoneName) => {
+ setSelectedZone(zoneName);
+ setFacilityModalOpen(true);
};
const handleThresUpdate = (newValue) => {
+ /* TODO :: 임계값 업데이트하기 */
+ /* 화면 표현하기 (완료) */
const value = Number(newValue);
- const li = zoneList.map((zone) => {
+ const updated = zoneList.map((zone) => {
if (zone.title !== sensorInfo.zoneName) return zone;
return {
...zone,
@@ -133,38 +158,58 @@ export default function Settings() {
}),
};
});
- console.log(li);
- setZoneList(li);
- setModalOpen(false);
+ console.log(updated);
+ setZoneList(updated);
+ setSensorModalOpen(false);
+ };
+
+ const handleFacilityUpdate = (newValue) => {
+ console.log(`공간명: ${selectedZone} 설비명: ${newValue}`);
+ /* TODO :: 설비 목록 업데이트하기 */
+ /* 화면 표현하기 */
+ const updated = zoneList.map((z) => {
+ if (z.title !== selectedZone) return z;
+ return {
+ ...z,
+ facility: [
+ ...(z.facility || []),
+ {
+ name: newValue,
+ fac_sensor: [],
+ },
+ ],
+ };
+ });
+ setZoneList(updated);
+ setFacilityModalOpen(false);
};
const handleAddZone = async (newZone) => {
const confirmed = window.confirm(`[${newZone}]을 추가하시겠습니까?`);
if (!confirmed) return;
- else{
+ else {
try {
- /* 1) 백엔드에 공간 생성 요청 */
- const { data } = await axios.post(
- "http://localhost:8080/api/zones",
- { zoneName: newZone } // 요청하는 zoneName이 title이 됨
- );
-
- /* 2) 전체 요소를를 화면에 출력하기위한 데이터터 */
+ // const { data } = await axios.post("http://localhost:8080/api/zones", {
+ // zoneName: newZone,
+ // });
+
+ // const newItem = {
+ // id: data.zoneId,
+ // title: data.zoneName,
+ // env_sensor: [],
+ // fac_sensor: [],
+ // master: "",
+ // };
+
const newItem = {
- id: data.zoneId, // key 로도 사용
- title: data.zoneName, // UI 에 표시할 이름
+ title: newZone,
env_sensor: [],
- fac_sensor: [],
+ facility: [],
master: "",
};
-
- /* 3) 화면 상태 업데이트 */
+
setZoneList((prev) => [...prev, newItem]);
-
- /* 4) 로그 (센서 API와 동일한 패턴) */
- console.log(
- `ZONE 생성: ${data.zoneId} / ${data.zoneName}`
- );
+ // console.log(`ZONE 생성: ${data.zoneId} / ${data.zoneName}`);
} catch (err) {
console.error(err);
alert("공간 생성에 실패했습니다.");
@@ -174,15 +219,26 @@ export default function Settings() {
return (
<>
-
setModalOpen(false)}
+ setSensorModalOpen(false)}
sensorInfo={sensorInfo}
onUpdate={handleThresUpdate}
/>
+ setFacilityModalOpen(false)}
+ zoneInfo={selectedZone}
+ onUpdate={handleFacilityUpdate}
+ />
센서 관리
{zoneList.map((z, i) => (
-
+
))}
>
diff --git a/src/styles/style.css b/src/styles/style.css
index 404b67f..c63058b 100644
--- a/src/styles/style.css
+++ b/src/styles/style.css
@@ -6,7 +6,7 @@
--c4: #ebf8fe;
}
-/* 기본 마진 삭제 */
+/* 기본 설정 삭제 */
body {
margin: 0;
min-height: 100vh;
@@ -20,6 +20,15 @@ a:visited {
color: white;
}
+a {
+ color: inherit;
+ text-decoration: none;
+}
+
+a:visited {
+ color: inherit;
+}
+
/* 전체 화면 기본 틀 정의 */
.main {
display: flex;
@@ -155,9 +164,13 @@ p.sidebar-open {
flex-direction: row;
}
+.arrow {
+ cursor: pointer;
+}
+
.moving-box:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
- transform: translateY(-5px);
+ transform: translateY(-3px);
}
.mini-box:active {
@@ -434,3 +447,61 @@ p.sidebar-open {
.modal-contents > span {
font-size: 1.2rem;
}
+
+/* 모니터링 페이지 */
+.monitor-body {
+ height: auto;
+ width: 100%;
+ background-color: var(--c4);
+ border-radius: 2rem;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
+ transform: translateY(1px);
+}
+.monitor-body > div {
+ margin: 1rem;
+ height: auto;
+ width: 100%;
+ gap: 0.5rem;
+ display: flex;
+ justify-content: flex-start;
+ flex-wrap: wrap;
+}
+
+.monitor-box {
+ background-color: rgb(205, 255, 205);
+ height: 12rem;
+ width: calc((100% - 3rem) / 3);
+ border-radius: 2rem;
+ box-sizing: border-box;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
+ transform: translateY(1px);
+}
+
+.monitor-box.urgent {
+ background-color: #ffd2cc;
+}
+
+.monitor-box.warn {
+ background-color: #fde1ad;
+}
+
+.no-flex-button {
+ width: 6.5rem;
+ height: auto;
+ padding: 0.3rem;
+ border-radius: 1rem;
+ border: none;
+ color: white;
+ background-color: #82b5f7;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+.no-flex-button:hover {
+ background-color: #5ba2ff;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
+}
+
+.no-flex-button:active {
+ background-color: #3389fa;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
+ transform: translateY(2px);
+}