-
+
} />
- } />
- } />
} />
} />
+ } />
+ } />
-
+
);
}
diff --git a/src/components/AbsenceEmployees.jsx b/src/components/AbsenceEmployees.jsx
new file mode 100644
index 0000000..fa5bb28
--- /dev/null
+++ b/src/components/AbsenceEmployees.jsx
@@ -0,0 +1,127 @@
+import { useContext, useState } from "react";
+import { DataContext } from "../context/DataContext";
+import EmployeeCard from "./EmployeeCard";
+import { EMPLOYEE_SKELETON_ARRAY } from "../data/skeleton";
+import SkeletonEmployeeCard from "./skeleton/SkeletonEmployeeCard";
+
+export default function AbsenceEmployees() {
+ const [reason, setReason] = useState("전체");
+ const [currentPage, setCurrentPage] = useState(1);
+ const { employees } = useContext(DataContext);
+
+ if (!employees) {
+ return
Loading...
;
+ }
+
+ {/* 만약 employees가 없으면 로딩 중임을 나타내는 화면을 반환 */}
+ const absenceEmployees = employees.filter(
+ (employee) => employee.isWorking === false
+ );
+
+ function getFilteredEmployees() {
+ switch (reason) {
+ case "전체":
+ return absenceEmployees;
+ case "미기입":
+ return absenceEmployees.filter(
+ (employee) => !employee.reasonForAbsence
+ );
+ default:
+ return absenceEmployees.filter(
+ (employee) => employee.reasonForAbsence === reason
+ );
+ }
+ }
+
+ // 페이지당 표시할 항목 수
+ const itemsPerPage = 8;
+
+ // 전체 필터된 직원 목록
+ const filteredEmployees = getFilteredEmployees();
+
+ // 현재 페이지의 데이터 추출
+ const indexOfLastEmployee = currentPage * itemsPerPage;
+ const indexOfFirstEmployee = indexOfLastEmployee - itemsPerPage;
+ const currentEmployees = filteredEmployees.slice(
+ indexOfFirstEmployee,
+ indexOfLastEmployee
+ );
+
+ // 페이지 변경 핸들러
+ function handlePageChange(pageNumber) {
+ setCurrentPage(pageNumber);
+ }
+
+ function handleChange(e) {
+ setReason(e.target.value);
+ setCurrentPage(1); // 필터 변경 시 페이지를 첫 페이지로 리셋
+ }
+
+ const isLoading = employees.length === 0;
+
+ if (isLoading) {
+ return (
+
+
+ not working now !
+
+
+
+
+
+ {EMPLOYEE_SKELETON_ARRAY.map((i) => (
+
+ ))}
+
+
+ );
+ }
+
+ return (
+
+
+ not working now !
+
+
+
+
+
+ {currentEmployees.map((employee) => (
+
+ ))}
+
+ {/* 페이지네이션 컴포넌트 */}
+
+ {Array.from({ length: Math.ceil(filteredEmployees.length / itemsPerPage) }).map((_, index) => (
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/EmployeeCard.jsx b/src/components/EmployeeCard.jsx
new file mode 100644
index 0000000..61f4e2a
--- /dev/null
+++ b/src/components/EmployeeCard.jsx
@@ -0,0 +1,46 @@
+import { Link } from "react-router-dom";
+
+export default function EmployeeCard({ employee }) {
+ const {
+ name,
+ email,
+ age,
+ department,
+ image,
+ isWorking,
+ workingHours,
+ reasonForAbsence,
+ id,
+ } = employee;
+
+ const isReason = isWorking || reasonForAbsence;
+
+ return (
+
+
+
+
+
{name}
+
{email}
+
{age}
+
{department}
+
{workingHours}
+ {!isReason ? (
+
+ ) : (
+
+ {reasonForAbsence}
+
+ )}
+
+
+
+ );
+}
diff --git a/src/components/Header.jsx b/src/components/Header.jsx
index 47e53d7..0de982e 100644
--- a/src/components/Header.jsx
+++ b/src/components/Header.jsx
@@ -1,33 +1,118 @@
-import { Link } from "react-router-dom";
-import { useContext } from "react";
-import { EmployeeContext } from "../context/EmployeeContext";
+import { Link, useNavigate } from "react-router-dom";
+import { getAuth, signOut } from "firebase/auth";
+import { useContext, useRef, useState } from "react";
+import Modal from "./Modal";
+import Timer from "./Timer";
+import { DataContext } from "../context/DataContext";
+import { updateEmployee } from "../sanity/employee";
+import Toggle from "./Toggle";
+import { buttonStyle } from "../style/button";
+import symbol from "/symbol.png";
export default function Header() {
- const { loginUser } = useContext(EmployeeContext);
+ const { loginUser } = useContext(DataContext);
+
+ const [dropdownIsOpen, setDropdownIsOpen] = useState(false);
+
+ const navigate = useNavigate();
+
+ const modalRef = useRef();
+
+ function openModal() {
+ modalRef.current.open();
+ }
+
+ function signOutHandler() {
+ const auth = getAuth();
+ signOut(auth)
+ .then(() => {
+ navigate("/login");
+ })
+ .catch((error) => {
+ console.log(error);
+ });
+ }
+
+ const handleChange = () => {
+ updateEmployee(loginUser.id, "isWorking", !loginUser.isWorking);
+ };
return (
-
-
- Logo
+
+
+
+ intranet five
-
- {!loginUser ? (
-
+ {loginUser && (
+
+
+
setDropdownIsOpen(!dropdownIsOpen)}
+ >
+
+
{loginUser?.name}
+
+
+
+
+ 마이페이지
+
+
+
+
+
+ )}
+ {!loginUser && (
+
Login
- ) : (
-
-
현재 시간(컴포넌트화)
-
-
{loginUser?.name}
-
-
-
)}
+
+
+
+ {loginUser?.department}
+
+
+
+
+ {loginUser?.name}님은 현재{" "}
+ {loginUser?.isWorking ? "근무중" : "부재중"}
+ 입니다.
+
+
+
+
);
}
diff --git a/src/components/ImageUpload.jsx b/src/components/ImageUpload.jsx
new file mode 100644
index 0000000..75c9496
--- /dev/null
+++ b/src/components/ImageUpload.jsx
@@ -0,0 +1,31 @@
+export default function ImageUpload({ message, handleChange, file }) {
+ return (
+
+
Image
+
+
+ {message && (
+
+ {message}
+
+ )}
+
+ );
+}
diff --git a/src/components/Input.jsx b/src/components/Input.jsx
new file mode 100644
index 0000000..0e584b6
--- /dev/null
+++ b/src/components/Input.jsx
@@ -0,0 +1,25 @@
+import { forwardRef } from "react";
+
+const Input = forwardRef(function Input(
+ { message = "", label, type = "text" },
+ ref
+) {
+ return (
+
+
{label}
+
+ {message && (
+
+ {message}
+
+ )}
+
+ );
+});
+
+export default Input;
diff --git a/src/components/Modal.jsx b/src/components/Modal.jsx
new file mode 100644
index 0000000..798e564
--- /dev/null
+++ b/src/components/Modal.jsx
@@ -0,0 +1,34 @@
+import { useRef, forwardRef, useImperativeHandle } from "react";
+import { createPortal } from "react-dom";
+
+const Modal = forwardRef(function Modal({ children }, ref) {
+ useImperativeHandle(ref, () => {
+ return {
+ open() {
+ dialog.current.showModal();
+ },
+ };
+ });
+
+ const dialog = useRef();
+
+ function closeModal(e) {
+ if (dialog.current === e.target) {
+ dialog.current.close();
+ }
+ }
+
+ return createPortal(
+ ,
+ document.getElementById("modal")
+ );
+});
+
+export default Modal;
diff --git a/src/components/NoticeCard.jsx b/src/components/NoticeCard.jsx
new file mode 100644
index 0000000..0046c26
--- /dev/null
+++ b/src/components/NoticeCard.jsx
@@ -0,0 +1,20 @@
+import { Link } from "react-router-dom";
+
+export default function NoticeCard({ notice }) {
+ const { title, thumbnail, id } = notice;
+
+ return (
+
+
+
+
+
+
{title}
+
+
+ );
+}
diff --git a/src/components/NoticeGallery.jsx b/src/components/NoticeGallery.jsx
new file mode 100644
index 0000000..b4892db
--- /dev/null
+++ b/src/components/NoticeGallery.jsx
@@ -0,0 +1,51 @@
+import { useContext } from "react";
+import Slider from "react-slick";
+import "slick-carousel/slick/slick.css";
+import "slick-carousel/slick/slick-theme.css";
+import NoticeCard from "./NoticeCard";
+import { DataContext } from "../context/DataContext";
+import SkeletonNoticeCard from "./skeleton/SkeletonNoticeCard";
+import { NOTICE_SKELETON_ARRAY } from "../data/skeleton";
+
+
+export default function NoticeGallery() {
+ const { notices } = useContext(DataContext);
+
+ const isLoading = notices.length === 0;
+
+ const settings = {
+ infinite: true,
+ speed: 500,
+ slidesToShow: 4,
+ slidesToScroll: 1,
+ };
+
+ if (isLoading) {
+ return (
+
+
+ notice gallery
+
+
+ {NOTICE_SKELETON_ARRAY.map((i) => (
+
+ ))}
+
+
+ );
+ }
+
+ return (
+
+
+ notice gallery
+
+ {/* slick 라이브러리 로직 */}
+
+ {notices.map((notice) => (
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx
index 8a0592a..1a9ef16 100644
--- a/src/components/Sidebar.jsx
+++ b/src/components/Sidebar.jsx
@@ -26,7 +26,7 @@ export default function Sidebar() {
페이지
-
- 직원 관리
+ 직원 관
-
page2
@@ -53,4 +53,4 @@ export default function Sidebar() {
);
-}
+}
\ No newline at end of file
diff --git a/src/components/TimePicker.jsx b/src/components/TimePicker.jsx
new file mode 100644
index 0000000..d77e84c
--- /dev/null
+++ b/src/components/TimePicker.jsx
@@ -0,0 +1,26 @@
+import { useRef } from "react";
+
+export default function TimePicker({ label, timeChangeHandler, pickTime }) {
+ const timeRef = useRef();
+ return (
+ loading...
;
+ }
+
+ function employeeIntroduction() {
+ if (employee.department === "developer") {
+ return introduction.developer;
+ } else if (employee.department === "designer") {
+ return introduction.designer;
+ } else if (employee.department === "planner") {
+ return introduction.planner;
+ }
+ }
+
+ function deleteHandler() {
+ deleteUser(user)
+ .then(() => {
+ deleteEmployee(loginUser.id);
+ navigater("/");
+ })
+ .catch((error) => {
+ console.error(error);
+ });
+ }
+
+ return (
+
-
- 기업 공지 모음 갤러리
-
-
- 근무중인 전 직원 조회
-
-
- 부재중인 직원 중 부재 항목에 따른 카테고리 메뉴로 데이터 필터링 기능
- 구현
-
+
);
}
diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx
index 25712ab..47c3ed2 100644
--- a/src/pages/Login.jsx
+++ b/src/pages/Login.jsx
@@ -1,59 +1,86 @@
-import { useRef } from "react";
+import { useContext, useRef, useState } from "react";
import { getAuth, signInWithEmailAndPassword } from "firebase/auth";
import { Link, useNavigate } from "react-router-dom";
+import Input from "../components/Input";
+import { DataContext } from "../context/DataContext";
+import { buttonStyle } from "../style/button";
export default function Login() {
+ const { employees } = useContext(DataContext);
+
+ const [errorMessage, setErrorMessage] = useState({
+ emailMessage: "",
+ passwordMessage: "",
+ });
+
const emailRef = useRef();
const passwordRef = useRef();
const navigate = useNavigate();
- function signInHandler() {
+ function signInHandler(e) {
+ e.preventDefault();
+
const auth = getAuth();
- signInWithEmailAndPassword(
- auth,
- emailRef.current.value,
- passwordRef.current.value
- )
+
+ const email = emailRef.current.value;
+ const password = passwordRef.current.value;
+
+ if (!employees.some((employee) => employee.email === email)) {
+ setErrorMessage((prev) => {
+ return { ...prev, emailMessage: "존재하는 직원 이메일이 아닙니다." };
+ });
+ return;
+ }
+
+ signInWithEmailAndPassword(auth, email, password)
.then(() => {
navigate("/");
})
.catch((error) => {
+ setErrorMessage({
+ emailMessage: "",
+ passwordMessage: "비밀번호가 틀립니다.",
+ });
console.log(error);
});
}
const contentHeight = `${window.innerHeight - 80}px`;
- const inputStyle = "border-[1px] border-gray-200 w-full rounded-md py-1 px-2";
-
return (
-
-
Welcome to Intranet Five !
-
-
+
+
);
}
diff --git a/src/pages/Management.jsx b/src/pages/Management.jsx
deleted file mode 100644
index 2650e5e..0000000
--- a/src/pages/Management.jsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Management() {
- return
management
;
-}
diff --git a/src/pages/MyPage.jsx b/src/pages/MyPage.jsx
deleted file mode 100644
index a803b85..0000000
--- a/src/pages/MyPage.jsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import { useContext } from "react";
-import { EmployeeContext } from "../context/EmployeeContext";
-
-export default function MyPage() {
- const { loginUser } = useContext(EmployeeContext);
-
- return (
-
-
-
- );
-}
diff --git a/src/pages/NoticePage.jsx b/src/pages/NoticePage.jsx
new file mode 100644
index 0000000..d0f006b
--- /dev/null
+++ b/src/pages/NoticePage.jsx
@@ -0,0 +1,50 @@
+import { useContext } from "react";
+import { useParams } from "react-router";
+import { DataContext } from "../context/DataContext";
+
+export default function NoticePage() {
+ const { id } = useParams();
+ const { notices } = useContext(DataContext);
+ const notice = notices.find((notice) => notice.id === id);
+
+ if (!notice) {
+ return
Loading...
;
+ }
+ const { title, description, thumbnail, createdAt, updatedAt } = notice;
+
+ function dateFormatter(date) {
+ const utcDate = new Date(date);
+
+ const koreaDateTimeFormat = new Intl.DateTimeFormat("ko-KR", {
+ year: "numeric",
+ month: "numeric",
+ day: "numeric",
+ hour: "numeric",
+ minute: "numeric",
+ second: "numeric",
+ timeZone: "Asia/Seoul",
+ });
+
+ return koreaDateTimeFormat.format(utcDate);
+ }
+
+ return (
+
+
+
+
+
{title}
+
{description}
+
+
작성일 : {dateFormatter(createdAt)}
+
수정일 : {dateFormatter(updatedAt)}
+
+
+
+
+ );
+}
diff --git a/src/pages/SignUp.jsx b/src/pages/SignUp.jsx
index 0f20081..70b82a0 100644
--- a/src/pages/SignUp.jsx
+++ b/src/pages/SignUp.jsx
@@ -1,34 +1,78 @@
-import { useRef, useState } from "react";
-import {
- getAuth,
- createUserWithEmailAndPassword,
- signOut,
-} from "firebase/auth";
+import { useContext, useRef, useState } from "react";
+import { getAuth, createUserWithEmailAndPassword } from "firebase/auth";
import { useNavigate } from "react-router-dom";
import { addEmployee } from "../sanity/employee";
+import Input from "../components/Input";
+import ImageUpload from "../components/ImageUpload";
+import { DataContext } from "../context/DataContext";
+import { buttonStyle } from "../style/button";
+import TimePicker from "../components/TimePicker";
export default function SignUp() {
+ const { employees } = useContext(DataContext);
const [file, setFile] = useState();
+ const [workingHours, setWorkingHours] = useState({});
+ const [errorMessage, setErrorMessage] = useState({
+ imageMessage: "",
+ emailMessage: "",
+ passwordMessage: "",
+ });
const emailRef = useRef();
const passwordRef = useRef();
const nameRef = useRef();
const ageRef = useRef();
const departmentRef = useRef();
- const startTimeRef = useRef();
- const endTimeRef = useRef();
const navigate = useNavigate();
- function signInHandler() {
+ function signInHandler(e) {
+ e.preventDefault();
+
const auth = getAuth();
const email = emailRef.current.value;
const password = passwordRef.current.value;
const name = nameRef.current.value;
const age = ageRef.current.value;
const department = departmentRef.current.value;
- const startTime = startTimeRef.current.value;
- const endTime = endTimeRef.current.value;
+ const startTime = workingHours.start;
+ const endTime = workingHours.end;
+
+ if (!file) {
+ setErrorMessage((prev) => {
+ return { ...prev, imageMessage: "사진을 추가해주세요." };
+ });
+ return;
+ }
+
+ if (employees.some((employee) => email === employee.email)) {
+ setErrorMessage((prev) => {
+ return { ...prev, emailMessage: "이미 존재하는 이메일입니다." };
+ });
+ return;
+ }
+
+ if (password.length < 6) {
+ setErrorMessage((prev) => {
+ return {
+ ...prev,
+ passwordMessage: "비밀번호는 6자리 이상이어야 합니다.",
+ };
+ });
+ return;
+ }
+
+ const specialCharacters = /[!@#$%^&*(),.?":{}|<>]/;
+
+ if (specialCharacters.test(password)) {
+ setErrorMessage((prev) => {
+ return {
+ ...prev,
+ passwordMessage: "비밀번호에 특수문자를 포함할 수 없습니다.",
+ };
+ });
+ return;
+ }
const userData = {
name,
@@ -42,12 +86,13 @@ export default function SignUp() {
createUserWithEmailAndPassword(auth, email, password)
.then(() => {
addEmployee(userData);
- signOut(auth); // createUserWithEmailAndPassword함수가 동작하면 회원가입 후 자동으로 로그인함. 이를 막기위해 임시로 signOut함수사용
+ navigate("/");
})
.catch((error) => {
console.log(error);
+ console.log(error.code);
+ console.log(error.message);
});
- navigate("/login");
}
const handleChange = (e) => {
@@ -55,84 +100,80 @@ export default function SignUp() {
const files = e.target.files;
if (files && files[0]) {
setFile(files[0]);
+ setErrorMessage((prev) => {
+ return { ...prev, imageMessage: "" };
+ });
}
};
- const labelStyle = "mb-2 text-gray-400";
- const inputStyle = "border-[1px] border-gray-200 w-full rounded-md py-1 px-2";
+ const timeChangeHandler = (type, e) => {
+ setWorkingHours((prev) => {
+ return { ...prev, [type]: e.target.value };
+ });
+ };
+
+ const contentHeight = `${window.innerHeight - 80}px`;
return (
-
-
-
직원 등록
-
);
diff --git a/src/sanity/client.js b/src/sanity/client.js
index e775654..2f1903e 100644
--- a/src/sanity/client.js
+++ b/src/sanity/client.js
@@ -1,4 +1,5 @@
import { createClient } from "@sanity/client";
+import imageUrlBuilder from "@sanity/image-url";
const client = createClient({
projectId: "y3b22irq",
@@ -10,4 +11,10 @@ const client = createClient({
ignoreBrowserTokenWarning: true,
});
+const builder = imageUrlBuilder(client);
+
+export function urlFor(source) {
+ return builder.image(source);
+}
+
export default client;
diff --git a/src/sanity/employee.js b/src/sanity/employee.js
index d51bb1c..8f519fa 100644
--- a/src/sanity/employee.js
+++ b/src/sanity/employee.js
@@ -29,8 +29,21 @@ export async function addEmployee({
age,
department,
image: { asset: { _ref: result.document._id } },
- isWorking: false,
+ isWorking: true,
workingHours,
+ reasonForAbsence: "",
});
});
}
+
+export async function updateEmployee(id, key, value) {
+ return client
+ .patch(id)
+ .set({ [key]: value })
+ .commit()
+ .catch((error) => console.log(error));
+}
+
+export async function deleteEmployee(id) {
+ return client.delete(id).catch((error) => console.log(error));
+}
diff --git a/src/style/button.js b/src/style/button.js
new file mode 100644
index 0000000..a7691d8
--- /dev/null
+++ b/src/style/button.js
@@ -0,0 +1,5 @@
+export const buttonStyle =
+ "text-center border-[1px] border-slate-400/30 text-slate-300 p-2 rounded-lg hover:border-slate-400/50 hover:bg-white/10 transition";
+
+export const deleteButtonStyle =
+ "text-center border-[2px] border-slate-400/30 text-slate-300 p-2 rounded-lg hover:text-red-500 hover:border-red-500 hover:bg-red-500/30 transition";
diff --git a/toy-project-1/schemas/employee.js b/toy-project-1/schemas/employee.js
index d7343fe..699ef3e 100644
--- a/toy-project-1/schemas/employee.js
+++ b/toy-project-1/schemas/employee.js
@@ -38,6 +38,11 @@ export default {
name: 'workingHours',
type: 'string',
},
+ {
+ title: 'ReasonForAbsence',
+ name: 'reasonForAbsence',
+ type: 'string',
+ },
],
preview: {
select: {title: 'name', media: 'image'},
diff --git a/toy-project-1/schemas/index.js b/toy-project-1/schemas/index.js
index bf5cfc4..66cfdf8 100644
--- a/toy-project-1/schemas/index.js
+++ b/toy-project-1/schemas/index.js
@@ -1,3 +1,4 @@
import employee from './employee'
+import notice from './notice'
-export const schemaTypes = [employee]
+export const schemaTypes = [employee, notice]
diff --git a/toy-project-1/schemas/notice.js b/toy-project-1/schemas/notice.js
new file mode 100644
index 0000000..f6c5be5
--- /dev/null
+++ b/toy-project-1/schemas/notice.js
@@ -0,0 +1,25 @@
+export default {
+ title: 'Notice',
+ name: 'notice',
+ type: 'document',
+ fields: [
+ {
+ title: 'Title',
+ name: 'title',
+ type: 'string',
+ },
+ {
+ title: 'Description',
+ name: 'description',
+ type: 'text',
+ },
+ {
+ title: 'Thumbnail',
+ name: 'thumbnail',
+ type: 'image',
+ },
+ ],
+ preview: {
+ select: {title: 'title', media: 'thumbnail'},
+ },
+}