diff --git a/src/api/outList/index.ts b/src/api/outList/index.ts new file mode 100644 index 0000000..3226c16 --- /dev/null +++ b/src/api/outList/index.ts @@ -0,0 +1,38 @@ +import { useMutation, useQuery } from "@tanstack/react-query"; +import { instance } from ".."; +import { applicationOK, earlyReturnHome } from "../type"; + +export const Application = () => { + return useQuery({ + queryKey: ["outList"], + queryFn: async () => { + const response = await instance.get(`application/non-return`); + return response.data; + }, + }); +}; + +export const EarlyReturn = () => { + return useQuery({ + queryKey: ["earlyReturn"], + queryFn: async () => { + const response = await instance.get(`early-return/ok`); + return response.data; + }, + }); +}; + +export const ReturnSchool = () => { + return useMutation({ + mutationFn: async (param) => { + try { + const response = await instance.patch( + `/application/change/${param.id}` + ); + return response.data; + } catch (error) { + throw error; + } + }, + }); +}; diff --git a/src/api/type.ts b/src/api/type.ts new file mode 100644 index 0000000..ec69044 --- /dev/null +++ b/src/api/type.ts @@ -0,0 +1,18 @@ +export interface applicationOK { + id: string; + username: string; + start_time: string; + end_time: string; + grade: number; + class_num: number; + num: number; +} + +export interface earlyReturnHome { + id: string; + username: string; + start_time: string; + grade: number; + class_num: number; + num: number; +} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 74a4229..4374d3e 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -66,7 +66,7 @@ const Login = () => { } else return "primary"; }; return ( -
+
diff --git a/src/app/outList/page.tsx b/src/app/outList/page.tsx index c77f940..922bc15 100644 --- a/src/app/outList/page.tsx +++ b/src/app/outList/page.tsx @@ -1,11 +1,60 @@ "use client"; -import Header from "@/components/header"; +import { Application, EarlyReturn } from "@/api/outList"; +import { applicationOK, earlyReturnHome } from "@/api/type"; +import BackGround from "@/components/background"; +import NonReturn from "@/components/list/application"; +import { getFullToday } from "@/util/date"; +import { getStudentString } from "@/util/util"; +import React, { useEffect, useState } from "react"; const OutList = () => { + const [selectedTab, setSelectedTab] = useState(true); + const [applicationData, setApplicationData] = useState(); + const [earlyData, setEarlyData] = useState(); + + const { data: applicationOKData } = Application(); + const { data: earlyReturnData } = EarlyReturn(); + + useEffect(() => { + setApplicationData(applicationOKData); + }, [applicationOKData]); + + useEffect(() => { + setEarlyData(earlyReturnData); + }, [earlyReturnData]); + + const onClickTab = (tab: boolean) => { + setSelectedTab(tab); + }; + return ( -
-
-
+ +
+ {selectedTab + ? applicationData?.map((item, index) => ( + + )) + : earlyData?.map((item, index) => ( + + ))} +
+
); }; diff --git a/src/assets/Icon/chevron-right.svg b/src/assets/Icon/chevron-right.svg new file mode 100644 index 0000000..0205bd3 --- /dev/null +++ b/src/assets/Icon/chevron-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/Icon/downarrow.svg b/src/assets/Icon/downarrow.svg new file mode 100644 index 0000000..679c364 --- /dev/null +++ b/src/assets/Icon/downarrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/background/index.tsx b/src/components/background/index.tsx new file mode 100644 index 0000000..731416b --- /dev/null +++ b/src/components/background/index.tsx @@ -0,0 +1,49 @@ +"use client"; +import Header from "@/components/header"; +import Tab from "@/components/tab"; +import React from "react"; + +interface Prop { + title: string; + subTitle: string; + Dropdown?: React.ReactNode; + TabOK: boolean; + children: React.ReactNode; + TabOnclick: (tab: boolean) => void; +} + +const BackGround: React.FC = ({ + title, + subTitle, + Dropdown, + TabOK = true, + children, + TabOnclick, +}) => { + return ( +
+
+
+
+
{title}
+
{subTitle}
+
+
{Dropdown}
+
+
+ {TabOK && ( + + )} +
+ {children} +
+
+
+ ); +}; + +export default BackGround; diff --git a/src/components/button/index.tsx b/src/components/button/index.tsx index bee4547..c4002c4 100644 --- a/src/components/button/index.tsx +++ b/src/components/button/index.tsx @@ -34,16 +34,16 @@ const Button: React.FC = ({ const getColorClass = () => { switch (colorType) { case "primary": - return " rounded-lg bg-primary-200 text-primary-1000 hover:bg-primary-500 focus:bg-primary-500 focus:border focus:border-primary-800 active:bg-primary-800"; + return " rounded-lg bg-primary-200 text-primary-1000 focus:bg-primary-500 focus:border focus:border-primary-800 active:bg-primary-800"; case "secondary": - return " rounded-lg bg-secondary-200 text-secondary-1000 hover:bg-secondary-500 focus:bg-secondary-500 focus:border focus:border-secondary-800 active:bg-secondary-800"; + return " rounded-lg bg-secondary-200 text-secondary-1000 focus:bg-secondary-500 focus:border focus:border-secondary-800 active:bg-secondary-800"; case "tertiary": - return " rounded-lg bg-tertiary-200 text-tertiary-900 hover:bg-tertiary-500 focus:bg-tertiary-500 focus:border focus:border-tertiary-800 active:bg-tertiary-800"; + return " rounded-lg bg-tertiary-200 text-tertiary-900 focus:bg-tertiary-500 focus:border focus:border-tertiary-800 active:bg-tertiary-800"; case "ghost": - return " rounded-lg border bg-primary-1000 border-primary-200 hover:border-primary-500 hover:text-primary-500 focus:border-primary-800 focus:text-primary-500 active:border-primary-800 active:text-primary-800"; + return " rounded-lg border bg-primary-1000 border-primary-200 focus:border-primary-800 focus:text-primary-500 active:border-primary-800 active:text-primary-800"; case "solidDisabled": return " rounded-lg bg-neutral-600 text-primary-1000"; @@ -52,7 +52,7 @@ const Button: React.FC = ({ return " rounded-lg border border-neutral-500 text-neutral-500 bg-neutral-1000"; case "red": - return " rounded-lg text-error-1000 bg-error-200 hover:bg-error-400 focus:bg-error-400 focus:border focus:border-error-800 active:bg-error-700"; + return " rounded-lg text-error-1000 bg-error-200 focus:bg-error-400 focus:border focus:border-error-800 active:bg-error-700"; } }; diff --git a/src/components/dropdown/index.tsx b/src/components/dropdown/index.tsx new file mode 100644 index 0000000..7ee844d --- /dev/null +++ b/src/components/dropdown/index.tsx @@ -0,0 +1,184 @@ +"use client"; +import Image from "next/image"; +import React, { useEffect, useRef, useState } from "react"; +import arrow from "@/assets/Icon/chevron-right.svg"; +import downarrow from "@/assets/Icon/downarrow.svg"; + +interface DropdownProp { + type: "grade" | "class" | "floor" | "classTime" | "club" | "all"; + onChange: (selectedOption: any, type: string) => void; +} + +const Dropdown: React.FC = ({ type, onChange }) => { + const [selectedGradeOption, setSelectedGradeOption] = useState(1); + const [selectedClassOption, setSelectedClassOption] = useState(1); + const [selectedFloorOption, setSelectedFloorOption] = useState(2); + const [selectedClubOption, setSelectedClubOption] = useState("PiCK"); + const [selectedAllOption, setSelectedAllOption] = useState(1); + const [selectedClassTime, setSelectedClassTime] = useState(8); + const [isDropdownVisible, setIsDropdownVisible] = useState(false); + const dropdownRef = useRef(null); + + const toggleDropdown = () => { + setIsDropdownVisible(!isDropdownVisible); + }; + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setIsDropdownVisible(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + const handleOptionClick = (option: any) => { + if (onChange) { + onChange(option.value, type); + switch (type) { + case "grade": + setSelectedGradeOption(option.value); + break; + case "all": + setSelectedAllOption(option.value); + break; + case "class": + setSelectedClassOption(option.value); + break; + case "classTime": + setSelectedClassTime(option.value); + break; + case "club": + setSelectedClubOption(option.value); + break; + case "floor": + setSelectedFloorOption(option.value); + break; + default: + break; + } + } + }; + + const generateOptions = (options: any[]) => { + return options.map((option) => ( +
handleOptionClick(option)} + className="py-2 px-3 rounded" + > + {option.label} +
+ )); + }; + + const floorOptions = [ + { value: 2, label: "2층" }, + { value: 3, label: "3층" }, + { value: 4, label: "4층" }, + ]; + + const AllOption = [ + { value: 1, label: "1학년" }, + { value: 2, label: "2학년" }, + { value: 3, label: "3학년" }, + { value: 5, label: "전체" }, + ]; + + const gradeOptions = [ + { value: 1, label: "1학년" }, + { value: 2, label: "2학년" }, + { value: 3, label: "3학년" }, + ]; + + const classOptions = [ + { value: 1, label: "1반" }, + { value: 2, label: "2반" }, + { value: 3, label: "3반" }, + { value: 4, label: "4반" }, + ]; + + const clubOptions = [ + { value: "PiCK", label: "PiCK" }, + { value: "대동여지도", label: "대동여지도" }, + { value: "info", label: "info" }, + { value: "은하", label: "은하" }, + { value: "DMS", label: "DMS" }, + { value: "gram", label: "gram" }, + { value: "Lift", label: "Lift" }, + { value: "Log", label: "Log" }, + { value: "Modeep", label: "Modeep" }, + { value: "NoNamed", label: "NoNamed" }, + { value: "TeamQSS", label: "TeamQSS" }, + { value: "어게인", label: "어게인" }, + { value: "자습", label: "자습" }, + ]; + + const classTimeOption = [ + { value: 6, label: "6교시" }, + { value: 7, label: "7교시" }, + { value: 8, label: "8교시" }, + { value: 9, label: "9교시" }, + { value: 10, label: "10교시" }, + ]; + const options = () => { + switch (type) { + case "all": + return AllOption; + case "class": + return classOptions; + case "classTime": + return classTimeOption; + case "club": + return clubOptions; + case "floor": + return floorOptions; + case "grade": + return gradeOptions; + default: + return []; + } + }; + + return ( +
+
+ {type === "grade" + ? `${selectedGradeOption}학년` + : type === "class" + ? `${selectedClassOption}반` + : type === "floor" + ? `${selectedFloorOption}층` + : type === "all" + ? selectedAllOption === 5 + ? `전체` + : `${selectedAllOption}학년` + : type === "classTime" + ? `${selectedClassTime}교시` + : ""} + arrow +
+ {isDropdownVisible && ( +
+ {generateOptions(options())} +
+ )} +
+ ); +}; + +export default Dropdown; diff --git a/src/components/header.tsx b/src/components/header.tsx index 5061418..ca0b792 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -24,9 +24,9 @@ const Header: NextPage = ({}) => { return (
- + -
+
{teacherName ? `${teacherName}선생님` : "선생님"}
diff --git a/src/components/input/index.tsx b/src/components/input/index.tsx index 1c0f299..9f42e74 100644 --- a/src/components/input/index.tsx +++ b/src/components/input/index.tsx @@ -45,7 +45,7 @@ const Input: React.FC = ({ ? "border-error-500 bg-error-900" : disabled ? "bg-neutral-800 border-neutral-800" - : "bg-neutral-900 hover:border-neutral-500 hover:bg-white active:border-secondary-500 caret-primary-500 focus:border-secondary-500" + : "bg-neutral-900 active:border-secondary-500 caret-primary-500 focus:border-secondary-500" } `; diff --git a/src/components/list/application/index.tsx b/src/components/list/application/index.tsx new file mode 100644 index 0000000..715597e --- /dev/null +++ b/src/components/list/application/index.tsx @@ -0,0 +1,66 @@ +"use client"; +import { ReturnSchool } from "@/api/outList"; +import Button from "@/components/button"; +import React, { useState } from "react"; + +interface NonReturnProp { + name: string; + returnTime?: string; + type: "application" | "early-return"; + id: string; +} + +export const NonReturn: React.FC = ({ + name, + returnTime, + type, + id, +}) => { + const { mutate: ReturnStudent } = ReturnSchool(); + const [selected, setSelected] = useState(false); + + const confirmReturn = async () => { + try { + const result = await ReturnStudent( + { id: id }, + { + onSuccess: () => { + location.reload(); + alert("복귀에 성공하셨습니다"); + }, + onError: () => { + console.log("에러발생"); + }, + } + ); + } catch (error) { + console.error(error); + } + }; + + return ( +
+
{name}
+ {type === "application" && ( + <> +
+ {returnTime} 복귀 예정 +
+
+
+ +
+
+ + )} +
+ ); +}; + +export default NonReturn; diff --git a/src/components/tab/index.tsx b/src/components/tab/index.tsx new file mode 100644 index 0000000..4d819a1 --- /dev/null +++ b/src/components/tab/index.tsx @@ -0,0 +1,40 @@ +import React, { useState } from "react"; + +interface Tab { + firstText: string; + SecondText: string; + onClick: (tab: boolean) => void; +} + +const Tab: React.FC = ({ firstText, SecondText, onClick }) => { + const [selectedTab, setSelectedTab] = useState(true); + + const selectTabClass = (tab: boolean) => + selectedTab === tab + ? "font-sans text-sub-title3-B w-full border-b-1 border-primary-500 py-3 flex items-center justify-center select-none" + : "font-sans text-sub-title3-B0 py-3 w-full flex items-center justify-center select-none"; + + const handleTabClick = (tab: boolean) => { + setSelectedTab(tab); + onClick(tab); + }; + + return ( +
+
handleTabClick(true)} + > + {firstText} +
+
handleTabClick(false)} + > + {SecondText} +
+
+ ); +}; + +export default Tab; diff --git a/src/util/date.ts b/src/util/date.ts new file mode 100644 index 0000000..e6485a3 --- /dev/null +++ b/src/util/date.ts @@ -0,0 +1,41 @@ +const today = new Date(); + +export function getWeekDay() { + switch (today.getDay()) { + case 0: + return "일"; + case 1: + return "월"; + case 2: + return "화"; + case 3: + return "수"; + case 4: + return "목"; + case 5: + return "금"; + case 6: + return "토"; + default: + return ""; + } +} + +export function getMonth() { + return today.getMonth() + 1; +} + +export function getDay() { + return today.getDate(); +} + +const month = getMonth().toString().padStart(2, "0"); +const day = getDay().toString().padStart(2, "0"); + +export function getFullToday() { + return `${today.getFullYear()}-${month}-${day}`; +} + +export function getToday() { + return `${month}-${day}`; +} diff --git a/src/util/util.ts b/src/util/util.ts new file mode 100644 index 0000000..ee543ed --- /dev/null +++ b/src/util/util.ts @@ -0,0 +1,53 @@ +interface Time { + hour: number; + minute: number; +} + +export const getTimeString = ({ hour, minute }: Time): string => + `${hour}:${minute}`; + +interface Student { + grade: number; + class_num: number; + num: number; + username: string; +} + +interface studentNum { + grade: number; + class_num: number; + num: number; +} + +export const getStudentString = ({ + grade, + class_num, + num, + username, +}: Student) => { + const change = num.toString().length === 1 ? `0${num}` : `${num}`; + return `${grade}${class_num}${change} ${username}`; +}; + +export const setStudentNum = ({ grade, class_num, num }: studentNum) => { + const change = num.toString().length === 1 ? `0${num}` : `${num}`; + return `${grade}${class_num}${change}`; +}; + +export type outCheck = "OK" | "NO"; + +export const Grade = (grade: number[]) => { + if (grade.includes(4)) { + return "전"; + } else { + return grade.join(", "); + } +}; + +export const ChangeOut = (type: "APPLICATION" | "EARLY_RETURN") => { + if (type === "APPLICATION") { + return "외출"; + } else if (type === "EARLY_RETURN") { + return "조기귀가"; + } +}; diff --git a/tailwind.config.ts b/tailwind.config.ts index c9926d6..039d9cb 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -299,6 +299,7 @@ const config: Config = { "29": "7.25rem", "25": "6.25rem", "27%": "27%", + "18": "4.375rem", }, height: { "140": "36.625rem",