Skip to content

Commit 2710cb7

Browse files
authored
Merge pull request #19 from Fac2Real/feature/detail-page
[feat] 공간별 상세정보 페이지
2 parents a083b4a + 8549a68 commit 2710cb7

8 files changed

Lines changed: 776 additions & 59 deletions

File tree

src/assets/refresh_icon.svg

Lines changed: 19 additions & 0 deletions
Loading

src/components/LogTable.jsx

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
export default function LogTable({ logs }) {
2+
return (
3+
<>
4+
<div className="table-container">
5+
<table className="logs-table">
6+
<thead>
7+
<tr className="table-header">
8+
<th style={{ width: "2%" }}>분류</th>
9+
<th style={{ width: "2%" }}>세분류</th>
10+
<th style={{ width: "1%" }}>위험도</th>
11+
<th style={{ width: "5%" }}>발생 시각</th>
12+
<th style={{ width: "2%" }}>센서ID</th>
13+
<th style={{ width: "2%" }}>측정값</th>
14+
</tr>
15+
</thead>
16+
<tbody>
17+
{logs.map((l, i) => {
18+
return (
19+
<tr
20+
key={i}
21+
className={
22+
l.dangerLevel == 2
23+
? "critical"
24+
: l.dangerLevel == 1
25+
? "warning"
26+
: ""
27+
}
28+
>
29+
<td>{l.targetType}</td>
30+
<td>{l.abnormalType}</td>
31+
<td>{l.dangerLevel}</td>
32+
<td>{l.timestamp}</td>
33+
<td>{l.targetId}</td>
34+
<td>{l.value}</td>
35+
</tr>
36+
);
37+
})}
38+
</tbody>
39+
</table>
40+
</div>
41+
</>
42+
);
43+
}

src/components/WorkerTable.jsx

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { useState } from "react";
2+
3+
export default function WorkerTable({
4+
worker_list,
5+
isDetail = false, // "현재 위치" 포함 여부 (Y=false, N=true)
6+
selectWorker,
7+
openModal,
8+
isManager = false,
9+
}) {
10+
const [searchType, setSearchType] = useState("byName");
11+
const [search, setSearch] = useState("");
12+
const [selectedStatus, setSelectedStatus] = useState("전체");
13+
14+
const filteredWorkers = worker_list.filter((worker) => {
15+
if (searchType === "byName") {
16+
return worker.name.includes(search.trim());
17+
} else if (searchType === "byStatus") {
18+
if (selectedStatus === "전체") {
19+
return worker_list;
20+
}
21+
return worker.status === selectedStatus;
22+
}
23+
return true;
24+
});
25+
26+
const directCall = (email, phone) => {
27+
const confirmed = window.confirm(`작업자를 호출하시겠습니까?`);
28+
if (confirmed) {
29+
/* To-Do: 긴급 호출 기능 구현하면 됨!! */
30+
console.log("긴급 호출!!!!!", `${email} ${phone}`);
31+
}
32+
};
33+
return (
34+
<>
35+
<div className="table-container">
36+
<table className="worker-table">
37+
<thead>
38+
{!isManager && (
39+
<tr className="table-search">
40+
<th colSpan={6}>
41+
<div className="search-container">
42+
{/* 이름검색 라디오버튼 */}
43+
<div>
44+
<label>
45+
<input
46+
className="radio"
47+
type="radio"
48+
name="searchType"
49+
value="byName"
50+
checked={searchType == "byName"}
51+
onChange={() => {
52+
setSearchType("byName");
53+
setSearch("");
54+
}}
55+
></input>
56+
이름으로 검색
57+
</label>
58+
<input
59+
id="search"
60+
name="search"
61+
className="search-field"
62+
value={search}
63+
onChange={(e) => setSearch(e.target.value)}
64+
disabled={searchType !== "byName"}
65+
/>
66+
</div>
67+
{/* 상태별 분류 라디오버튼 */}
68+
<div>
69+
<label>
70+
<input
71+
className="radio"
72+
type="radio"
73+
name="searchType"
74+
value="byStatus"
75+
checked={searchType == "byStatus"}
76+
onChange={() => {
77+
setSearchType("byStatus");
78+
setSelectedStatus("전체");
79+
}}
80+
></input>
81+
상태별 분류
82+
</label>
83+
<select
84+
id="status"
85+
className="search-field"
86+
value={selectedStatus}
87+
onChange={(e) => setSelectedStatus(e.target.value)}
88+
disabled={searchType !== "byStatus"}
89+
>
90+
<option value="전체">전체</option>
91+
<option value="정상">정상</option>
92+
<option value="위험">위험</option>
93+
</select>
94+
</div>
95+
</div>
96+
</th>
97+
</tr>
98+
)}
99+
<tr className="table-header">
100+
<th>상태</th>
101+
<th>이름</th>
102+
{!isDetail && <th>현재 위치</th>}
103+
<th className="id-row">웨어러블 ID</th>
104+
<th>연락처</th>
105+
<th>호출</th>
106+
</tr>
107+
</thead>
108+
<tbody>
109+
{filteredWorkers.map((worker, i) => {
110+
let tmp = "";
111+
if (worker.status == "위험") {
112+
tmp = "critical";
113+
}
114+
return (
115+
<tr key={i} className={tmp}>
116+
<td>{worker.status}</td>
117+
<td>{worker.name}</td>
118+
{!isDetail && <td>{worker.zone}</td>}
119+
<td className="id-row">{worker.wearableId}</td>
120+
<td
121+
style={{ cursor: "pointer", textDecoration: "underline" }}
122+
onClick={() => {
123+
selectWorker(worker);
124+
openModal(true);
125+
}}
126+
>
127+
조회
128+
</td>
129+
<td
130+
style={{ fontSize: "1.2rem", cursor: "pointer" }}
131+
onClick={() => directCall(worker.email, worker.phone)}
132+
>
133+
🚨
134+
</td>
135+
</tr>
136+
);
137+
})}
138+
</tbody>
139+
</table>
140+
</div>
141+
</>
142+
);
143+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import XIcon from "../../assets/x_icon.svg?react";
2+
3+
function ContactTable({ email, phone, id }) {
4+
return (
5+
<div className="table-container">
6+
<table className="contact-table">
7+
<thead>
8+
<tr>
9+
<th>이메일</th>
10+
<td>{email}</td>
11+
</tr>
12+
</thead>
13+
<tbody>
14+
<tr>
15+
<th scope="row">휴대폰 번호</th>
16+
<td>{phone}</td>
17+
</tr>
18+
<tr>
19+
<th scope="row">웨어러블 ID</th>
20+
<td>{id}</td>
21+
</tr>
22+
</tbody>
23+
</table>
24+
</div>
25+
);
26+
}
27+
28+
export default function WorkerInfoModal({ isOpen, onClose, workerInfo }) {
29+
if (isOpen) {
30+
console.log(workerInfo);
31+
return (
32+
<div className="modal-overlay" onClick={onClose}>
33+
<div className="modal-box" onClick={(e) => e.stopPropagation()}>
34+
<div onClick={onClose}>
35+
<XIcon width="1.5rem" height="1.5rem" />
36+
</div>
37+
<div className="modal-contents" style={{ marginBottom: "5.5rem" }}>
38+
<p style={{ fontSize: "1.5rem" }}>
39+
{workerInfo.name}의 연락처 정보
40+
</p>
41+
<ContactTable
42+
email={workerInfo.email}
43+
phone={workerInfo.phone}
44+
id={workerInfo.wearableId}
45+
/>
46+
</div>
47+
</div>
48+
</div>
49+
);
50+
}
51+
return <></>;
52+
}

src/pages/Safety.jsx

Lines changed: 75 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,83 @@
1+
import { useCallback, useEffect, useState } from "react";
2+
import axiosInstance from "../api/axiosInstance";
3+
import WorkerTable from "../components/WorkerTable";
4+
import WorkerInfoModal from "../components/modal/WorkerInfoModal";
5+
16
export default function Safety() {
2-
const mock_workers = {
3-
normal_workers: [],
4-
abnormal_workers: [],
5-
disconnected_workers: [], // 논의필요!
7+
const [workerList, setWorkerList] = useState([]);
8+
9+
const [isOpen, setIsOpen] = useState(false);
10+
const onClose = () => {
11+
setSelectedWorker();
12+
setIsOpen(false);
613
};
14+
const [selectedWorkerInfo, setSelectedWorker] = useState();
15+
16+
const mock_workers = [
17+
{
18+
name: "김00",
19+
// role: "사원",
20+
status: "위험",
21+
zone: "포장 구역 A",
22+
wearableId: "WEARABLE000111000",
23+
24+
phone: "010111111111",
25+
},
26+
{
27+
name: "윤00",
28+
// role: "공장장",
29+
status: "정상",
30+
zone: "휴게실",
31+
wearableId: "인식되지 않음",
32+
33+
phone: "010222222222",
34+
},
35+
{
36+
name: "정00",
37+
// role: "반장",
38+
status: "정상",
39+
zone: "조립 구역 B",
40+
wearableId: "WEARABLE111111111",
41+
42+
phone: "01033333333",
43+
},
44+
];
45+
46+
const fetchWorkers = useCallback(() => {
47+
axiosInstance
48+
.get("/api/workers")
49+
.then(() => {
50+
console.log("작업자 정보 get!");
51+
})
52+
.catch((e) => {
53+
console.log("작업자 정보 조회 실패 - mock data를 불러옵니다", e);
54+
setWorkerList(mock_workers);
55+
});
56+
});
57+
58+
useEffect(() => {
59+
fetchWorkers();
60+
const interval = setInterval(() => {
61+
fetchWorkers();
62+
}, 60000); // 1분!
63+
return () => clearInterval(interval);
64+
}, []);
65+
66+
console.log("rerendering");
767
return (
868
<>
69+
<WorkerInfoModal
70+
isOpen={isOpen}
71+
onClose={onClose}
72+
workerInfo={selectedWorkerInfo}
73+
/>
974
<h1>작업자 안전관리</h1>
10-
{/* 정상 작업자 */}
11-
<div className="box-wrapper">
12-
<div className="top-box" style={{ backgroundColor: "#ebf8fe" }}>
13-
정상
14-
</div>
15-
<div className="bottom-box">
16-
{/* 작업자 목록
17-
* 아니 뭐더라 */}
18-
</div>
19-
</div>
20-
{/* 이상 작업자 */}
21-
<div className="box-wrapper">
22-
<div className="top-box" style={{ backgroundColor: "#FFEBCE" }}>
23-
주의
24-
</div>
25-
<div className="bottom-box">
26-
{/* 작업자 목록
27-
* 아니 뭐더라 */}
28-
</div>
29-
</div>
30-
{/* 연결되지 않은 작업자 */}
31-
<div className="box-wrapper">
32-
<div className="top-box" style={{ backgroundColor: "#f6f6f6" }}>
33-
연결되지 않은 사용자
34-
</div>
35-
<div className="bottom-box"></div>
75+
<div className="safety-body">
76+
<WorkerTable
77+
worker_list={workerList}
78+
selectWorker={setSelectedWorker}
79+
openModal={setIsOpen}
80+
/>
3681
</div>
3782
</>
3883
);

0 commit comments

Comments
 (0)