Skip to content

Commit

Permalink
Merge pull request #221 from BCSDLab/feature/#197
Browse files Browse the repository at this point in the history
[영양사] 영양사 페이지 구현
  • Loading branch information
MinGu-Jeong authored Apr 5, 2024
2 parents 8e6354a + 2360b6e commit 283c1bf
Show file tree
Hide file tree
Showing 14 changed files with 447 additions and 1 deletion.
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 @@ -86,7 +86,7 @@ function Header() {
</button>
)}
<span className={styles.mobileheader__title}>
{pathname === '/' ? (
{pathname === '/' || pathname === '/coop' ? (
<MobileLogoIcon title="코인 로고" />
) : (CATEGORY
.flatMap((categoryValue) => categoryValue.submenu)
Expand Down
41 changes: 41 additions & 0 deletions src/model/Coop/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import z from 'zod';

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

export type DiningTypes = 'BREAKFAST' | 'LUNCH' | 'DINNER';

export const DINING_TYPES: Record<Menus, DiningTypes> = {
아침: 'BREAKFAST',
점심: 'LUNCH',
저녁: 'DINNER',
};

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;
}
}
87 changes: 87 additions & 0 deletions src/page/Coop/components/MenuCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { useGetDining } from 'query/coop';
import { Dinings, Menus, DINING_TYPES } 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) => DINING_TYPES[menuType];

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 {
width: 60px;
height: 33px;
background-color: #fff;
color: #175c8e;
display: flex;
align-items: center;
justify-content: center;
border: solid 1px #175c8e;
border-radius: 999px;
cursor: pointer;
}

&__button--selected {
width: 60px;
height: 33px;
background-color: #175c8e;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
border-radius: 999px;
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]: 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]: 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]: selectedMenuType !== '저녁',
})}
onClick={() => setSelectedMenuType('저녁')}
onKeyDown={(e) => e.key === 'Enter' && setSelectedMenuType('저녁')}
type="button"
tabIndex={0}
>
저녁
</button>
</div>
);
}
Loading

0 comments on commit 283c1bf

Please sign in to comment.