Skip to content
Open
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
189 changes: 67 additions & 122 deletions app/_components/meal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,8 @@ const CATEGORY_ORDER: Array<{ category: MenuCategory; label: string }> = [
{ category: 'DINNER', label: '저녁' },
];

function Skeleton({ className }: { className: string }) {
return <View className={`rounded-md bg-grey-10 ${className}`} />;
}

export default function MealSection({ all = false }: MealSectionProps) {
const { isLoading, isFetching, isError, error, keys, todayKey, getByDate } = useMenuData();

const [idx, setIdx] = React.useState<number | null>(null);

React.useEffect(() => {
Expand All @@ -51,18 +46,6 @@ export default function MealSection({ all = false }: MealSectionProps) {

React.useEffect(() => {
if (!__DEV__) return;

console.log('[menus/ui] MealSection 렌더 데이터', {
isLoading,
isFetching,
isError,
error,
idx,
keys,
todayKey,
dateKey,
dayItems,
});
}, [dateKey, dayItems, error, idx, isError, isFetching, isLoading, keys, todayKey]);

const mealsByCategory = React.useMemo(() => {
Expand All @@ -75,7 +58,7 @@ export default function MealSection({ all = false }: MealSectionProps) {

dayItems.forEach((item) => {
const sanitizedMeals = (item.meals ?? []).filter(
(meal) => !normalizedMarkers.includes(normalizeMealText(meal)),
(meal) => !normalizedMarkers.includes(normalizeMealText(meal))
);

map.set(item.menuCategory, sanitizedMeals);
Expand All @@ -89,123 +72,85 @@ export default function MealSection({ all = false }: MealSectionProps) {

React.useEffect(() => {
if (!__DEV__) return;

console.log('[menus/ui] 카테고리별 식단', {
breakfast: mealsByCategory.get('BREAKFAST'),
lunch: mealsByCategory.get('LUNCH'),
dinner: mealsByCategory.get('DINNER'),
});
}, [mealsByCategory]);

const onPrev = () => {
const onPrevClick = () => {
if (atStart) return;
setIdx((current) => (current == null ? 0 : Math.max(0, current - 1)));
};

const onNext = () => {
const onNextClick = () => {
if (atEnd) return;
setIdx((current) => (current == null ? 0 : Math.min(keys.length - 1, current + 1)));
};

const renderMealCards = ({
textClassName,
wrapperClassName,
cardClassName,
emptyTextClassName,
listClassName,
bodyClassName,
}: {
textClassName: string;
wrapperClassName?: string;
cardClassName?: string;
emptyTextClassName?: string;
listClassName?: string;
bodyClassName?: string;
}) => (
<View className={`flex flex-col gap-4 ${wrapperClassName ?? ''}`}>
{CATEGORY_ORDER.map(({ category, label }) => {
const meals = mealsByCategory.get(category) ?? [];

return (
<View
key={category}
className={`border-grey-10 rounded-2xl border p-4 ${cardClassName ?? ''}`}
>
<Text className="text-body02 text-blue-20 text-center font-semibold">
{label}
return (
<View className="w-screen flex-1 bg-white">
<View className="flex w-full flex-col gap-3 p-4">
{isError && (
<View className="border-error mb-2 rounded-2xl border p-4">
<Text className="text-body05 text-error text-center">
식단 데이터를 불러오지 못했습니다.
</Text>

<View className="bg-grey-10 mt-2 mb-3 h-px w-full" />

<View className={bodyClassName ?? ''}>
{isLoading ? (
<View className="flex flex-col gap-2">
<Skeleton className="h-5 w-full" />
<Skeleton className="h-5 w-4/5" />
<Skeleton className="h-5 w-3/5" />
</View>
) : meals.length === 0 ? (
<View className="flex h-full min-h-[72px] items-center justify-center">
<Text className={`${textClassName} text-center ${emptyTextClassName ?? ''}`}>
등록된 식단 내용이 없습니다.
</Text>
</View>
) : (
<View className={`flex flex-col gap-1 text-center ${listClassName ?? ''}`}>
{meals.map((meal, index) => (
<Text key={`${category}-${index}`} className={`${textClassName} text-center`}>
{meal}
</Text>
))}
</View>
)}
</View>
</View>
);
})}
</View>
);

return (
<View className="flex w-full flex-col gap-3 p-4">
{isError && (
<View className="border-error mb-2 rounded-2xl border p-4">
<Text className="text-body05 text-error text-center">
식단 데이터를 불러오지 못했습니다.
</Text>
</View>
)}

{!all && (
<View className="flex flex-row items-center justify-center gap-4">
<Pressable onPress={onPrev} disabled={atStart} accessibilityLabel="이전 날짜">
<Icon
as={ChevronLeft}
size={24}
className={atStart ? 'text-grey-10' : 'text-grey-40'}
/>
</Pressable>

<Text className="text-title03 text-black">{dateKey || '-'}</Text>

<Pressable onPress={onNext} disabled={atEnd} accessibilityLabel="다음 날짜">
<Icon
as={ChevronRight}
size={24}
className={atEnd ? 'text-grey-10' : 'text-grey-40'}
/>
</Pressable>
)}

{!all && (
<View className="flex flex-row items-center justify-center gap-4">
<Pressable onPress={onPrevClick} disabled={atStart} accessibilityLabel="이전 날짜">
<Icon
as={ChevronLeft}
size={24}
className={atStart ? 'text-grey-10' : 'text-grey-40'}
/>
</Pressable>

<Text className="text-title03 text-black">{dateKey || '-'}</Text>

<Pressable onPress={onNextClick} disabled={atEnd} accessibilityLabel="다음 날짜">
<Icon
as={ChevronRight}
size={24}
className={atEnd ? 'text-grey-10' : 'text-grey-40'}
/>
</Pressable>
</View>
)}

<View className="mb-5 flex flex-col gap-3">
{CATEGORY_ORDER.map(({ category, label }) => {
const meals = mealsByCategory.get(category) ?? [];

return (
<View key={category} className="border-grey-10 flex flex-col rounded-2xl border p-4">
<Text className="text-body02 text-blue-20 text-center font-semibold">{label}</Text>

<View className="bg-grey-10 mb-3 mt-2 h-px w-full" />

<View className="flex flex-col">
{meals.length === 0 ? (
<View className="flex h-full min-h-[72px] items-center justify-center">
<Text className="text-body05 text-grey-30 text-center">
등록된 식단 내용이 없습니다.
</Text>
</View>
) : (
<View className="flex flex-col gap-1 text-center">
{meals.map((meal, index) => (
<Text
key={`${category}-${index}`}
className="text-body05 text-grey-80 text-center">
{meal}
</Text>
))}
</View>
)}
</View>
</View>
);
})}
</View>
)}

{renderMealCards({
textClassName: 'text-body05 text-grey-80',
wrapperClassName: 'gap-3 mb-5',
cardClassName: 'flex flex-col',
emptyTextClassName: 'text-grey-30',
listClassName: '',
bodyClassName: 'flex flex-col',
})}
</View>
</View>
);
}
23 changes: 10 additions & 13 deletions app/_components/Page5.tsx → app/_components/notice-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import clsx from 'clsx';
import * as React from 'react';
import { Linking, Pressable, ScrollView, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { Footer } from '../../components/footer';

export function Page5() {
export function NoticePage() {
const categories = ['전체', '일반', '학사', '장학', '진로', '학생활동', '학칙개정'];
const [selectedCategory, setSelectedCategory] = React.useState('전체');
const [currentPage, setCurrentPage] = React.useState(1);
Expand Down Expand Up @@ -53,23 +54,19 @@ export function Page5() {
))}
</View>

{/* 페이지네이션 */}
<View className="mt-6 w-full items-center">
<Pagination
currentPage={currentPage}
totalPages={TOTAL_PAGES}
onPageChange={setCurrentPage}
/>
</View>

{/* footer 작성 전 */}
<View className="bg-blue-05 mt-9 h-20 w-full"></View>
<Pagination
currentPage={currentPage}
totalPages={TOTAL_PAGES}
onPageChange={setCurrentPage}
className="mt-6"
/>
<Footer className="mt-9" />
</ScrollView>
);
}

// dummy
const TOTAL_PAGES = 8;

const NOTICES = [
{
id: 1,
Expand Down
14 changes: 6 additions & 8 deletions app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Page2 } from './_components/Page2';
import { Page3 } from './_components/Page3';
import MealSection from './_components/meal';
import { Page4 } from './_components/Page4';
import { Page5 } from './_components/Page5';
import { NoticePage } from './_components/notice-page';
import { Page6 } from './_components/Page6';
import { Page7 } from './_components/Page7';
import { Input } from '@/components/ui/input';
Expand All @@ -17,7 +17,7 @@ import clsx from 'clsx';

const { width } = Dimensions.get('window');
const TABS = ['ALL', '학식', '게시판', '명지도', '공지사항', '학사일정', '명대신문', '명대뉴스'];
const INITIAL_PAGE = 1; // 0-indexed, Page2
const INITIAL_PAGE = 0;

export default function Screen() {
const insets = useSafeAreaInsets();
Expand Down Expand Up @@ -96,16 +96,14 @@ export default function Screen() {
onMomentumScrollEnd={handleScroll}
onScrollBeginDrag={Keyboard.dismiss}
style={{ flex: 1 }}>
<Page1 />
<Page2 />
<Page3 />
<MealSection />
<Page1 />
<Page4 />
<Page5 />
<NoticePage />
<Page6 />
<Page7 />
<View className="w-screen flex-1 bg-white">
<MealSection />
</View>
<Page3 />
</ScrollView>
</>
);
Expand Down
60 changes: 60 additions & 0 deletions components/footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Text } from '@/components/ui/text';
import clsx from 'clsx';
import * as React from 'react';
import { Linking, Pressable, View } from 'react-native';
import Svg, { Path } from 'react-native-svg';

const GITHUB_URL = 'https://github.com/NOVA-MJU';
const INSTAGRAM_URL = 'https://www.instagram.com/thing_go_/';
const TERMS_URL =
'https://verbena-ixia-597.notion.site/Thingo-33e22ef5d21e80b08328edd8519b0b4e?source=copy_link';
const PRIVACY_URL =
'https://verbena-ixia-597.notion.site/Thingo-33e22ef5d21e807d9738dc14def5de24?source=copy_link';
const CONTACT_MAIL = `mailto:mjsearch2025@gmail.com?subject=${encodeURIComponent('문의 내용을 작성해주세요')}&body=${encodeURIComponent('안녕하세요,\n\n문의사항을 아래에 작성해주세요.\n\n- 이름:\n- 연락처:\n- 문의 내용:')}`;

async function openContactMail() {
const supported = await Linking.canOpenURL(CONTACT_MAIL);
if (supported) Linking.openURL(CONTACT_MAIL);
else console.log('mail 앱이 설치되어있지 않음');
}

export function Footer({ className }: { className?: string }) {
return (
<View className={clsx('bg-blue-05 w-full px-10 py-5', className)}>
<View className="flex-col">
<View className="flex-row gap-3">
<Pressable onPress={() => Linking.openURL(GITHUB_URL)}>
<Svg width="28" height="28" viewBox="0 0 28 28" fill="none">
<Path
d="M13.9995 2.99951C11.3213 2.99945 8.73054 3.95241 6.69075 5.68786C4.65096 7.42331 3.29529 9.82799 2.86633 12.4716C2.43738 15.1152 2.96314 17.8251 4.34953 20.1165C5.73592 22.4079 7.89246 24.1312 10.4332 24.9779C10.9972 25.0772 11.2081 24.7388 11.2081 24.4422C11.2081 24.1749 11.1945 23.2862 11.1945 22.3422C8.36023 22.8632 7.62713 21.6508 7.40156 21.0169C7.15118 20.3998 6.75442 19.8529 6.24551 19.4233C5.85077 19.2124 5.28684 18.6902 6.23085 18.6766C6.59166 18.7156 6.93774 18.841 7.23972 19.0423C7.5417 19.2435 7.79067 19.5147 7.96549 19.8327C8.1197 20.1098 8.32707 20.3537 8.57573 20.5505C8.82438 20.7473 9.10943 20.8931 9.41453 20.9795C9.71963 21.066 10.0388 21.0914 10.3537 21.0542C10.6686 21.0171 10.9732 20.9182 11.2498 20.7632C11.2982 20.1894 11.554 19.6529 11.9694 19.2541C9.45989 18.9721 6.83764 17.9999 6.83764 13.6859C6.82257 12.565 7.23608 11.4805 7.99368 10.6542C7.6496 9.67991 7.68993 8.61105 8.10647 7.66543C8.10647 7.66543 9.05048 7.36993 11.2081 8.82148C13.0536 8.31406 15.0018 8.31406 16.8473 8.82148C19.0038 7.35527 19.9489 7.66543 19.9489 7.66543C20.3654 8.61105 20.4058 9.67991 20.0617 10.6542C20.8223 11.4787 21.2363 12.5643 21.2177 13.6859C21.2177 18.0135 18.5808 18.9721 16.0725 19.2541C16.3413 19.5271 16.5483 19.8547 16.6795 20.2147C16.8107 20.5747 16.8629 20.9587 16.8327 21.3406C16.8327 22.8497 16.8191 24.0621 16.8191 24.4422C16.8191 24.7388 17.03 25.0907 17.5939 24.9779C20.13 24.1241 22.2802 22.3967 23.6605 20.1042C25.0408 17.8117 25.5615 15.1033 25.1296 12.4624C24.6977 9.8215 23.3413 7.42004 21.3026 5.68671C19.2638 3.95337 16.6755 3.00097 13.9995 2.99951Z"
fill="#8BC7FF"
/>
</Svg>
</Pressable>
<Pressable onPress={() => Linking.openURL(INSTAGRAM_URL)}>
<Svg width="28" height="28" viewBox="0 0 28 28" fill="none">
<Path
d="M9.38024 2.99951H18.6202C22.1402 2.99951 25.0002 5.85951 25.0002 9.37951V18.6195C25.0002 20.3116 24.3281 21.9344 23.1316 23.1309C21.9351 24.3273 20.3123 24.9995 18.6202 24.9995H9.38024C5.86024 24.9995 3.00024 22.1395 3.00024 18.6195V9.37951C3.00024 7.68743 3.67242 6.06465 4.8689 4.86817C6.06539 3.67169 7.68816 2.99951 9.38024 2.99951ZM9.16024 5.19951C8.10999 5.19951 7.10275 5.61672 6.3601 6.35937C5.61746 7.10201 5.20024 8.10925 5.20024 9.15951V18.8395C5.20024 21.0285 6.97124 22.7995 9.16024 22.7995H18.8402C19.8905 22.7995 20.8977 22.3823 21.6404 21.6397C22.383 20.897 22.8002 19.8898 22.8002 18.8395V9.15951C22.8002 6.97051 21.0292 5.19951 18.8402 5.19951H9.16024ZM19.7752 6.84951C20.1399 6.84951 20.4897 6.99438 20.7475 7.25224C21.0054 7.5101 21.1502 7.85984 21.1502 8.22451C21.1502 8.58918 21.0054 8.93892 20.7475 9.19678C20.4897 9.45465 20.1399 9.59951 19.7752 9.59951C19.4106 9.59951 19.0608 9.45465 18.803 9.19678C18.5451 8.93892 18.4002 8.58918 18.4002 8.22451C18.4002 7.85984 18.5451 7.5101 18.803 7.25224C19.0608 6.99438 19.4106 6.84951 19.7752 6.84951ZM14.0002 8.49951C15.4589 8.49951 16.8579 9.07897 17.8893 10.1104C18.9208 11.1419 19.5002 12.5408 19.5002 13.9995C19.5002 15.4582 18.9208 16.8571 17.8893 17.8886C16.8579 18.92 15.4589 19.4995 14.0002 19.4995C12.5416 19.4995 11.1426 18.92 10.1112 17.8886C9.07971 16.8571 8.50024 15.4582 8.50024 13.9995C8.50024 12.5408 9.07971 11.1419 10.1112 10.1104C11.1426 9.07897 12.5416 8.49951 14.0002 8.49951ZM14.0002 10.6995C13.125 10.6995 12.2857 11.0472 11.6668 11.6661C11.0479 12.2849 10.7002 13.1243 10.7002 13.9995C10.7002 14.8747 11.0479 15.7141 11.6668 16.333C12.2857 16.9518 13.125 17.2995 14.0002 17.2995C14.8755 17.2995 15.7148 16.9518 16.3337 16.333C16.9526 15.7141 17.3002 14.8747 17.3002 13.9995C17.3002 13.1243 16.9526 12.2849 16.3337 11.6661C15.7148 11.0472 14.8755 10.6995 14.0002 10.6995Z"
fill="#8BC7FF"
/>
</Svg>
</Pressable>
</View>
<View className="flex-row gap-4 py-1">
<Pressable onPress={() => Linking.openURL(TERMS_URL)}>
<Text className="text-caption02 text-grey-40">이용약관</Text>
</Pressable>
<Pressable onPress={() => Linking.openURL(PRIVACY_URL)}>
<Text className="text-caption02 text-grey-40">개인정보 처리방침</Text>
</Pressable>
<Pressable onPress={openContactMail}>
<Text className="text-caption02 text-grey-40">문의하기</Text>
</Pressable>
</View>
<View className="py-1">
<Text className="text-caption02 text-grey-20">@ 2025 MJS. All rights reserved</Text>
</View>
</View>
</View>
);
}
5 changes: 0 additions & 5 deletions components/ui/aspect-ratio.tsx

This file was deleted.

Loading