Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[영양사] 영양사 페이지 구현 #221

Merged
merged 13 commits into from
Apr 5, 2024
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import PageNotFound from 'page/Error/PageNotFound';
import ModifyMenu from 'page/ModifyMenu';
import { Suspense } from 'react';
import Toast from 'component/common/Toast';
import Coop from 'page/Coop';

function App() {
return (
Expand All @@ -28,6 +29,7 @@ function App() {
<Route path="/order-management" element={<PageNotFound />} />
<Route path="/sales-management" element={<PageNotFound />} />
<Route path="/shop-add" element={<PageNotFound />} />
<Route path="/coop" element={<Coop />} />
</Route>
<Route element={<AuthLayout />}>
<Route path="/login" element={<Login />} />
Expand Down
15 changes: 15 additions & 0 deletions src/api/coop/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { accessClient } from 'api';
import { DiningImages, SoldOut } from 'model/Coop';

export const getDining = async () => {
const { data } = await accessClient.get('/dinings');
return data;
};

export const uploadDiningImage = async (data: DiningImages) => {
await accessClient.patch<DiningImages>('/coop/dining/image', data);
};

export const updateSoldOut = async (data: SoldOut) => {
await accessClient.patch<SoldOut>('/coop/dining/soldout', data);
};
8 changes: 8 additions & 0 deletions src/assets/svg/coop/photo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/component/common/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ function Header() {
</button>
)}
<span className={styles.mobileheader__title}>
{pathname === '/' ? (
{pathname === '/' || pathname === '/coop' ? (
<MobileLogoIcon title="코인 로고" />
) : (CATEGORY
.flatMap((categoryValue) => categoryValue.submenu)
Expand Down
33 changes: 33 additions & 0 deletions src/model/Coop/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import z from 'zod';

export type Menus = '아침' | '점심' | '저녁';

export const Dinings = z.object({
date: z.string(),
id: z.number(),
kcal: z.number(),
menu: z.array(z.string()),
place: z.string(),
price_card: z.number(),
price_cash: z.number(),
type: z.string(),
updated_at: z.string(),
sold_out: z.boolean(),
is_changed: z.boolean(),
});

export type Dinings = z.infer<typeof Dinings>;

export const DiningImages = z.object({
menuId: z.number(),
imageUrl: z.string(),
});

export type DiningImages = z.infer<typeof DiningImages>;

export const SoldOut = z.object({
menuId: z.number(),
soldOut: z.boolean(),
});

export type SoldOut = z.infer<typeof SoldOut>;
49 changes: 49 additions & 0 deletions src/page/Coop/Coop.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
.container {
display: flex;
flex-direction: column;
background-color: #f5f5f5;
width: 390px;
min-height: 100vh;
height: 100%;
}

.container-wrapper {
display: flex;
justify-content: center;
align-items: center;
}

.place {
&__container {
display: flex;
align-items: center;
gap: 8px;
margin-top: 24px;
margin-left: 24px;
}

&__button--selected {
width: 60px;
height: 30px;
background-color: #175c8e;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
border-radius: 999px;
cursor: pointer;
}

&__button--unselected {
width: 60px;
height: 30px;
background-color: #fff;
color: #175c8e;
display: flex;
align-items: center;
justify-content: center;
border: solid 1px #175c8e;
border-radius: 999px;
cursor: pointer;
}
}
69 changes: 69 additions & 0 deletions src/page/Coop/components/MenuCard/MenuCard.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
.container {
display: flex;
flex-direction: column;
}

.card {
display: flex;
flex-direction: column;
background-color: #fff;
margin: 10px 20px;
border-radius: 4px;
box-shadow: 0 1px 9px 1px rgb(0 0 0 / 6%);

&__header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #e0e0e0;
padding: 10px;
}

&__title {
font-size: 18px;
font-weight: 500;
}

&__content {
display: flex;
flex-direction: column;
font-size: 12px;
font-weight: 400;
line-height: 15px;
width: 150px;
}

&__image {
width: 150px;
height: 100px;
flex-shrink: 0;
border-radius: 4px;
border: 1px solid #cacaca;
background: #fafafa;
display: flex;
align-items: center;
justify-content: center;
object-fit: scale-down;
}

&__soldout-wrapper {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}

&__wrapper {
display: flex;
align-items: center;
justify-content: center;
padding: 12px;
gap: 40px;
}

&__soldout {
color: #8e8e8e;
font-size: 13px;
font-weight: 400;
}
}
98 changes: 98 additions & 0 deletions src/page/Coop/components/MenuCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useGetDining } from 'query/coop';
import { Dinings, Menus } from 'model/Coop';
import SoldoutToggle from 'page/Coop/components/SoldoutToggle';
import { ReactComponent as Photo } from 'assets/svg/coop/photo.svg';
import { useRef, useState } from 'react';
import styles from './MenuCard.module.scss';

interface MenuCardProps {
selectedMenuType: Menus;
}

export default function MenuCard({ selectedMenuType }: MenuCardProps) {
const { data } = useGetDining();
const [selectedImages, setSelectedImages] = useState<{ [key: number]: string }>({});
const fileInputRefs = useRef<{ [key: number]: HTMLInputElement | null }>({});

const handleImageChange = (menuId: number) => (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files[0]) {
const fileReader = new FileReader();
fileReader.onload = (e) => {
setSelectedImages((prevImages) => ({
...prevImages,
[menuId]: e.target?.result as string,
}));
};
fileReader.readAsDataURL(event.target.files[0]);
}
};

const handleImageClick = (menuId: number) => () => {
fileInputRefs.current[menuId]?.click();
};

const getDiningType = (menuType: Menus) => {
MinGu-Jeong marked this conversation as resolved.
Show resolved Hide resolved
switch (menuType) {
case '아침':
return 'BREAKFAST';
case '점심':
return 'LUNCH';
case '저녁':
return 'DINNER';
default:
return '';
}
};

const filteredData = data?.filter((menu:Dinings) => {
const diningType = getDiningType(selectedMenuType);
return menu.type === diningType && ['A코너', 'B코너', 'C코너'].includes(menu.place);
});

return (
<div className={styles.container}>
{filteredData?.map((menu: Dinings) => (
<div key={menu.id} className={styles.card}>
<div className={styles.card__header}>
<span className={styles.card__title}>{menu.place}</span>
<div className={styles['card__soldout-wrapper']}>
<span className={styles.card__soldout}>품절</span>
<SoldoutToggle />
</div>
</div>
<div className={styles.card__wrapper}>
<div
className={styles.card__image}
onClick={handleImageClick(menu.id)}
onKeyDown={(event) => {
if (event.key === 'Enter') handleImageClick(menu.id)();
}}
role="button"
tabIndex={0}
>
{selectedImages[menu.id] ? (
<img src={selectedImages[menu.id]} alt="" className={styles.card__image} />
) : (
<Photo />
)}
</div>
<div className={styles.card__content}>
{menu.menu.map((item) => (
<div key={item}>{item}</div>
))}
</div>
</div>
<input
type="file"
accept="image/*"
style={{ display: 'none' }}
onChange={handleImageChange(menu.id)}
ref={(el) => {
fileInputRefs.current[menu.id] = el;
}}
/>
</div>
))}
</div>
);
}
34 changes: 34 additions & 0 deletions src/page/Coop/components/MenuType/MenuType.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.place {
&__container {
display: flex;
align-items: center;
gap: 8px;
margin-top: 24px;
margin-left: 24px;
}

&__button--selected {
width: 60px;
height: 33px;
background-color: #175c8e;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
border-radius: 999px;
cursor: pointer;
}

&__button--unselected {
width: 60px;
height: 33px;
background-color: #fff;
color: #175c8e;
display: flex;
align-items: center;
justify-content: center;
border: solid 1px #175c8e;
border-radius: 999px;
MinGu-Jeong marked this conversation as resolved.
Show resolved Hide resolved
cursor: pointer;
}
}
51 changes: 51 additions & 0 deletions src/page/Coop/components/MenuType/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import cn from 'utils/ts/className';
import { Menus } from 'model/Coop';
import styles from './MenuType.module.scss';

interface MenuTypeProps {
selectedMenuType: Menus;
setSelectedMenuType: (menuType: Menus) => void;
}

export default function MenuType({ selectedMenuType, setSelectedMenuType }: MenuTypeProps) {
return (
<div className={styles.place__container}>
<button
className={cn({
[styles['place__button--selected']]: selectedMenuType === '아침',
[styles['place__button--unselected']]: selectedMenuType !== '아침',
})}
MinGu-Jeong marked this conversation as resolved.
Show resolved Hide resolved
onClick={() => setSelectedMenuType('아침')}
onKeyDown={(e) => e.key === 'Enter' && setSelectedMenuType('아침')}
type="button"
tabIndex={0}
>
아침
</button>
<button
className={cn({
[styles['place__button--selected']]: selectedMenuType === '점심',
[styles['place__button--unselected']]: selectedMenuType !== '점심',
})}
onClick={() => setSelectedMenuType('점심')}
onKeyDown={(e) => e.key === 'Enter' && setSelectedMenuType('점심')}
type="button"
tabIndex={0}
>
점심
</button>
<button
className={cn({
[styles['place__button--selected']]: selectedMenuType === '저녁',
[styles['place__button--unselected']]: selectedMenuType !== '저녁',
})}
onClick={() => setSelectedMenuType('저녁')}
onKeyDown={(e) => e.key === 'Enter' && setSelectedMenuType('저녁')}
type="button"
tabIndex={0}
>
저녁
</button>
</div>
);
}
Loading
Loading