Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/assets/refresh_icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions src/components/LogTable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export default function LogTable({ logs }) {
return (
<>
<div className="table-container">
<table className="logs-table">
<thead>
<tr className="table-header">
<th style={{ width: "2%" }}>분류</th>
<th style={{ width: "2%" }}>세분류</th>
<th style={{ width: "1%" }}>위험도</th>
<th style={{ width: "5%" }}>발생 시각</th>
<th style={{ width: "2%" }}>센서ID</th>
<th style={{ width: "2%" }}>측정값</th>
</tr>
</thead>
<tbody>
{logs.map((l, i) => {
return (
<tr
key={i}
className={
l.dangerLevel == 2
? "critical"
: l.dangerLevel == 1
? "warning"
: ""
}
>
<td>{l.targetType}</td>
<td>{l.abnormalType}</td>
<td>{l.dangerLevel}</td>
<td>{l.timestamp}</td>
<td>{l.targetId}</td>
<td>{l.value}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</>
);
}
143 changes: 143 additions & 0 deletions src/components/WorkerTable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { useState } from "react";

export default function WorkerTable({
worker_list,
isDetail = false, // "현재 위치" 포함 여부 (Y=false, N=true)
selectWorker,
openModal,
isManager = false,
}) {
const [searchType, setSearchType] = useState("byName");
const [search, setSearch] = useState("");
const [selectedStatus, setSelectedStatus] = useState("전체");

const filteredWorkers = worker_list.filter((worker) => {
if (searchType === "byName") {
return worker.name.includes(search.trim());
} else if (searchType === "byStatus") {
if (selectedStatus === "전체") {
return worker_list;
}
return worker.status === selectedStatus;
}
return true;
});

const directCall = (email, phone) => {
const confirmed = window.confirm(`작업자를 호출하시겠습니까?`);
if (confirmed) {
/* To-Do: 긴급 호출 기능 구현하면 됨!! */
console.log("긴급 호출!!!!!", `${email} ${phone}`);
}
};
return (
<>
<div className="table-container">
<table className="worker-table">
<thead>
{!isManager && (
<tr className="table-search">
<th colSpan={6}>
<div className="search-container">
{/* 이름검색 라디오버튼 */}
<div>
<label>
<input
className="radio"
type="radio"
name="searchType"
value="byName"
checked={searchType == "byName"}
onChange={() => {
setSearchType("byName");
setSearch("");
}}
></input>
이름으로 검색
</label>
<input
id="search"
name="search"
className="search-field"
value={search}
onChange={(e) => setSearch(e.target.value)}
disabled={searchType !== "byName"}
/>
</div>
{/* 상태별 분류 라디오버튼 */}
<div>
<label>
<input
className="radio"
type="radio"
name="searchType"
value="byStatus"
checked={searchType == "byStatus"}
onChange={() => {
setSearchType("byStatus");
setSelectedStatus("전체");
}}
></input>
상태별 분류
</label>
<select
id="status"
className="search-field"
value={selectedStatus}
onChange={(e) => setSelectedStatus(e.target.value)}
disabled={searchType !== "byStatus"}
>
<option value="전체">전체</option>
<option value="정상">정상</option>
<option value="위험">위험</option>
</select>
</div>
</div>
</th>
</tr>
)}
<tr className="table-header">
<th>상태</th>
<th>이름</th>
{!isDetail && <th>현재 위치</th>}
<th className="id-row">웨어러블 ID</th>
<th>연락처</th>
<th>호출</th>
</tr>
</thead>
<tbody>
{filteredWorkers.map((worker, i) => {
let tmp = "";
if (worker.status == "위험") {
tmp = "critical";
}
return (
<tr key={i} className={tmp}>
<td>{worker.status}</td>
<td>{worker.name}</td>
{!isDetail && <td>{worker.zone}</td>}
<td className="id-row">{worker.wearableId}</td>
<td
style={{ cursor: "pointer", textDecoration: "underline" }}
onClick={() => {
selectWorker(worker);
openModal(true);
}}
>
조회
</td>
<td
style={{ fontSize: "1.2rem", cursor: "pointer" }}
onClick={() => directCall(worker.email, worker.phone)}
>
🚨
</td>
</tr>
);
})}
</tbody>
</table>
</div>
</>
);
}
52 changes: 52 additions & 0 deletions src/components/modal/WorkerInfoModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import XIcon from "../../assets/x_icon.svg?react";

function ContactTable({ email, phone, id }) {
return (
<div className="table-container">
<table className="contact-table">
<thead>
<tr>
<th>이메일</th>
<td>{email}</td>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">휴대폰 번호</th>
<td>{phone}</td>
</tr>
<tr>
<th scope="row">웨어러블 ID</th>
<td>{id}</td>
</tr>
</tbody>
</table>
</div>
);
}

export default function WorkerInfoModal({ isOpen, onClose, workerInfo }) {
if (isOpen) {
console.log(workerInfo);
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-box" onClick={(e) => e.stopPropagation()}>
<div onClick={onClose}>
<XIcon width="1.5rem" height="1.5rem" />
</div>
<div className="modal-contents" style={{ marginBottom: "5.5rem" }}>
<p style={{ fontSize: "1.5rem" }}>
{workerInfo.name}의 연락처 정보
</p>
<ContactTable
email={workerInfo.email}
phone={workerInfo.phone}
id={workerInfo.wearableId}
/>
</div>
</div>
</div>
);
}
return <></>;
}
105 changes: 75 additions & 30 deletions src/pages/Safety.jsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,83 @@
import { useCallback, useEffect, useState } from "react";
import axiosInstance from "../api/axiosInstance";
import WorkerTable from "../components/WorkerTable";
import WorkerInfoModal from "../components/modal/WorkerInfoModal";

export default function Safety() {
const mock_workers = {
normal_workers: [],
abnormal_workers: [],
disconnected_workers: [], // 논의필요!
const [workerList, setWorkerList] = useState([]);

const [isOpen, setIsOpen] = useState(false);
const onClose = () => {
setSelectedWorker();
setIsOpen(false);
};
const [selectedWorkerInfo, setSelectedWorker] = useState();

const mock_workers = [
{
name: "김00",
// role: "사원",
status: "위험",
zone: "포장 구역 A",
wearableId: "WEARABLE000111000",
email: "[email protected]",
phone: "010111111111",
},
{
name: "윤00",
// role: "공장장",
status: "정상",
zone: "휴게실",
wearableId: "인식되지 않음",
email: "[email protected]",
phone: "010222222222",
},
{
name: "정00",
// role: "반장",
status: "정상",
zone: "조립 구역 B",
wearableId: "WEARABLE111111111",
email: "[email protected]",
phone: "01033333333",
},
];

const fetchWorkers = useCallback(() => {
axiosInstance
.get("/api/workers")
.then(() => {
console.log("작업자 정보 get!");
})
.catch((e) => {
console.log("작업자 정보 조회 실패 - mock data를 불러옵니다", e);
setWorkerList(mock_workers);
});
});

useEffect(() => {
fetchWorkers();
const interval = setInterval(() => {
fetchWorkers();
}, 60000); // 1분!
return () => clearInterval(interval);
}, []);

console.log("rerendering");
return (
<>
<WorkerInfoModal
isOpen={isOpen}
onClose={onClose}
workerInfo={selectedWorkerInfo}
/>
<h1>작업자 안전관리</h1>
{/* 정상 작업자 */}
<div className="box-wrapper">
<div className="top-box" style={{ backgroundColor: "#ebf8fe" }}>
정상
</div>
<div className="bottom-box">
{/* 작업자 목록
* 아니 뭐더라 */}
</div>
</div>
{/* 이상 작업자 */}
<div className="box-wrapper">
<div className="top-box" style={{ backgroundColor: "#FFEBCE" }}>
주의
</div>
<div className="bottom-box">
{/* 작업자 목록
* 아니 뭐더라 */}
</div>
</div>
{/* 연결되지 않은 작업자 */}
<div className="box-wrapper">
<div className="top-box" style={{ backgroundColor: "#f6f6f6" }}>
연결되지 않은 사용자
</div>
<div className="bottom-box"></div>
<div className="safety-body">
<WorkerTable
worker_list={workerList}
selectWorker={setSelectedWorker}
openModal={setIsOpen}
/>
</div>
</>
);
Expand Down
Loading
Loading