매장 휴무일 설정 - 시지 라이프 Nextjs 웹앱 매장 등록 기능 구현 [8]
시지 라이프 매장 등록 - 매장 휴무일 설정
휴무일 등록은 참 어렵습니다.
1주일에 한번, 2주일에 한번, 한달에 한번, 한달에 두번 등 다양한 휴무일이 있습니다.
이들을 모두 처리하는 것이 쉽지가 않습니다.
영업시간과 휴무일은 별도로 설정이 되지만
휴무일의 영업 시간은 표기하지 말아야 합니다.
서로 연관이 있다는 뜻입니다.
https://next-tailwind-firebase-order-app.vercel.app/
지역 커뮤니티 - 시지 라이프
시지 라이프 시지 지역 커뮤니티 - 테스트 배포 중입니다. 로딩 중...
next-tailwind-firebase-order-app.vercel.app
StoreRegisterPage - 매장 등록 컴포넌트
'use client';
import { useState, useEffect } from 'react';
import { db } from '@/firebase/firebaseConfig';
import { collection, addDoc, serverTimestamp, query, where, getDocs } from 'firebase/firestore';
import { Store, BusinessHour, DayOfWeek, HolidayRule } from '@/types/store';
import Script from 'next/script';
import { useRouter } from 'next/navigation';
import { useUserStore } from '@/stores/userStore';
import BusinessHoursModal from '@/components/modals/BusinessHoursModal';
import HolidayRuleModal from '@/components/modals/HolidayRuleModal';
const categories = [
'한식', '중식', '일식', '양식', '분식', '치킨', '피자', '패스트푸드',
'고기/구이', '족발/보쌈', '찜/탕/찌개', '도시락', '야식', '해산물',
'디저트', '베이커리', '카페', '커피/음료', '샐러드', '브런치', '기타',
];
const emptyBusinessHours: Record<DayOfWeek, BusinessHour> = {
월: { opening: '', closing: '' },
화: { opening: '', closing: '' },
수: { opening: '', closing: '' },
목: { opening: '', closing: '' },
금: { opening: '', closing: '' },
토: { opening: '', closing: '' },
일: { opening: '', closing: '' },
};
const defaultHolidayRule: HolidayRule = {
frequency: '매주',
days: [],
};
export default function StoreRegisterPage() {
const [form, setForm] = useState<Store>({
category: '',
name: '',
description: '',
zipcode: '',
address: '',
detailAddress: '',
latitude: '',
longitude: '',
businessHours: emptyBusinessHours,
holidayRule: defaultHolidayRule,
admin: ''
});
const [showBusinessHoursModal, setShowBusinessHoursModal] = useState(false);
const [showHolidayRuleModal, setShowHolidayRuleModal] = useState(false);
const router = useRouter();
const { userData } = useUserStore();
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setForm(prev => ({ ...prev, [name]: value }));
};
const handleAddressSearch = () => {
if (typeof window === 'undefined' || !(window as any).daum?.Postcode) return;
new (window as any).daum.Postcode({
oncomplete: (data: any) => {
setForm(prev => ({
...prev,
address: data.address,
zipcode: data.zonecode,
}));
}
}).open();
};
const handleOpenMap = () => {
if (!form.name || !form.address) {
alert('상호명과 주소를 입력해주세요.');
return;
}
const query = new URLSearchParams({
name: form.name,
address: form.address,
latitude: String(form.latitude),
longitude: String(form.longitude),
});
const width = 800;
const height = 600;
const left = window.screenX + (window.outerWidth - width) / 2;
const top = window.screenY + (window.outerHeight - height) / 2;
window.open(
`/naver-map-view?${query.toString()}`,
'mapWindow',
`width=${width},height=${height},left=${left},top=${top}`
);
};
useEffect(() => {
const messageHandler = (event: MessageEvent) => {
if (event.origin !== window.location.origin) return;
if (event.data?.type === 'coords') {
setForm(prev => ({
...prev,
latitude: event.data.lat.toString(),
longitude: event.data.lng.toString(),
}));
}
};
window.addEventListener('message', messageHandler);
return () => window.removeEventListener('message', messageHandler);
}, []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!userData?.userId) {
alert('사용자 정보가 없습니다. 로그인 상태를 확인해주세요.');
return;
}
const requiredFields = [
{ key: 'category', label: '업종' },
{ key: 'name', label: '상호명' },
{ key: 'description', label: '소개말' },
{ key: 'zipcode', label: '우편번호' },
{ key: 'address', label: '주소' },
{ key: 'latitude', label: '위도' },
{ key: 'longitude', label: '경도' },
];
for (const field of requiredFields) {
if (!form[field.key as keyof typeof form]) {
alert(`${field.label}을(를) 입력해주세요.`);
return;
}
}
// 영업시간 하나라도 설정됐는지 체크
const hasValidHours = Object.values(form.businessHours).some(
(day) => day.opening && day.closing
);
if (!hasValidHours) {
alert('영업시간을 설정해주세요.');
return;
}
try {
const storesRef = collection(db, 'stores');
const duplicateQuery = query(
storesRef,
where('name', '==', form.name.trim()),
where('address', '==', form.address.trim())
);
const snapshot = await getDocs(duplicateQuery);
if (!snapshot.empty) {
alert(`이미 등록된 매장입니다.\n\n상호명: ${form.name}\n주소: ${form.address}`);
return;
}
await addDoc(storesRef, {
...form,
latitude: parseFloat(form.latitude),
longitude: parseFloat(form.longitude),
admin: userData.userId,
createdAt: serverTimestamp(),
});
alert('매장이 등록되었습니다!');
router.push('/');
} catch (error) {
console.error(error);
alert('등록 실패');
}
};
return (
<div className="max-w-xl mx-auto p-3">
<h1 className="text-2xl font-bold mb-2 dark:text-white">매장 등록</h1>
<form onSubmit={handleSubmit} className="space-y-4">
{/* 업종 선택 */}
<div>
<label className="block font-semibold mb-2 dark:text-gray-200">업종 선택</label>
<div className="flex flex-wrap gap-2">
{categories.map(c => (
<button
key={c}
type="button"
onClick={() => setForm(prev => ({ ...prev, category: c }))}
className={`px-3 py-1.5 rounded-full border text-xs transition
${form.category === c
? 'bg-blue-600 text-white border-blue-600'
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-100 dark:bg-gray-700 dark:text-gray-200 dark:border-gray-500 dark:hover:bg-gray-600'
}`}
>
{c}
</button>
))}
</div>
</div>
{/* 텍스트 입력 필드 예시 */}
<input
type="text"
name="name"
placeholder="상호명"
value={form.name}
onChange={handleChange}
className="w-full p-2 border text-xs border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-black dark:text-white"
/>
{/* textarea */}
<textarea
name="description"
placeholder="소개말"
value={form.description}
onChange={handleChange}
className="w-full p-2 border text-xs border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-black dark:text-white"
/>
{/* 영업시간 버튼 */}
<button
type="button"
onClick={() => setShowBusinessHoursModal(true)}
className="w-full p-2 border text-xs rounded bg-gray-100 dark:bg-gray-700 dark:text-white"
>
영업시간 설정
</button>
{/* 설정된 영업시간 보기 */}
<div className="text-xs text-gray-600">
{Object.entries(form.businessHours).every(([_, h]) => !h.opening && !h.closing) ? (
<span className="italic">영업시간 미설정</span>
) : (
<ul className="mt-0 space-y-1">
{Object.entries(form.businessHours).map(([day, h]) => (
<li key={day}>
<span className="font-semibold">{day}</span>:
{h.opening && h.closing ? `${h.opening} ~ ${h.closing}` : '휴무'}
</li>
))}
</ul>
)}
</div>
{/* 휴무일 설정 버튼 */}
<button
type="button"
onClick={() => setShowHolidayRuleModal(true)}
className="w-full p-2 border text-xs rounded bg-gray-100 dark:bg-gray-700 dark:text-white"
>
휴무일 설정
</button>
{/* 휴무 규칙 요약 */}
<p className="mt-0 text-xs text-gray-600 dark:text-gray-300">
{form.holidayRule.frequency} {form.holidayRule.days.join(', ')}
{form.holidayRule.weeks?.length
? ` (매월 ${form.holidayRule.weeks.join(', ')}주차)`
: ''} 휴무
</p>
{/* 우편번호 및 주소 */}
<input
type="text"
name="zipcode"
placeholder="우편번호 (클릭하여 검색)"
value={form.zipcode}
readOnly
onClick={handleAddressSearch}
className="w-full p-2 text-xs border border-gray-300 dark:border-gray-600
rounded bg-gray-100 dark:bg-gray-800
text-black dark:text-white
placeholder-gray-400 dark:placeholder-gray-500
cursor-pointer"
/>
<input
type="text"
name="address"
placeholder="주소"
value={form.address}
readOnly
onClick={handleAddressSearch}
className="w-full p-2 text-xs border border-gray-300 dark:border-gray-600
rounded bg-gray-100 dark:bg-gray-800
text-black dark:text-white
placeholder-gray-400 dark:placeholder-gray-500
cursor-pointer"
/>
<input
type="text"
name="detailAddress"
placeholder="상세주소"
value={form.detailAddress}
onChange={handleChange}
className="w-full p-2 text-xs border border-gray-300 dark:border-gray-600
rounded bg-white dark:bg-gray-800
text-black dark:text-white
placeholder-gray-400 dark:placeholder-gray-500"
/>
{/* 위도 / 경도 필드 */}
<div className="flex gap-4">
<input
type="text"
name="latitude"
placeholder="위도"
value={form.latitude}
readOnly
onClick={handleOpenMap}
className="w-full p-2 border text-xs border-gray-300 dark:border-gray-600
rounded bg-gray-100 dark:bg-gray-800 text-black dark:text-white"
/>
<input
type="text"
name="longitude"
placeholder="경도"
value={form.longitude}
readOnly
onClick={handleOpenMap}
className="w-full p-2 text-xs border border-gray-300 dark:border-gray-600 rounded
bg-gray-100 dark:bg-gray-800 text-black dark:text-white"
/>
</div>
{/* 등록 버튼 */}
<button
type="submit"
className="w-full text-xs bg-blue-600 text-white py-2 rounded hover:bg-blue-700
dark:hover:bg-blue-500"
>
등록하기
</button>
</form>
{showBusinessHoursModal && (
<BusinessHoursModal
defaultValue={form.businessHours}
onSave={(updatedHours) => {
setForm(prev => ({ ...prev, businessHours: updatedHours }));
setShowBusinessHoursModal(false);
}}
onCancel={() => setShowBusinessHoursModal(false)}
/>
)}
{showHolidayRuleModal && (
<HolidayRuleModal
isOpen={true}
defaultValue={form.holidayRule ?? defaultHolidayRule}
onSave={(updatedRule) => {
setForm(prev => ({ ...prev, holidayRule: updatedRule }));
setShowHolidayRuleModal(false);
}}
onCancel={() => setShowHolidayRuleModal(false)}
/>
)}
<Script src="https://t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js" strategy="lazyOnload" />
</div>
);
}
매장 휴무일 설정 로직
매장 휴무일 설정 로직은 HolidayRuleModal을 통해 이루어지며,
사용자가 반복 주기(frequency), 요일(days), 주차(weeks)를 선택할 수 있도록 설계되어 있습니다.
✅ 1. 휴무일 관련 데이터 구조
export interface HolidayRule {
frequency: '매주' | '격주' | '한 달 1회' | '한 달 2회'; // 반복 주기
days: DayOfWeek[]; // 휴무 요일들, 예: ['일', '수']
weeks?: number[]; // 몇 주차에 적용할지, 예: [1, 3]
}
✅ 2. 휴무일 기본값 (defaultHolidayRule)
const defaultHolidayRule: HolidayRule = {
frequency: '매주',
days: [],
};
- 모달을 처음 열 때의 기본 상태
- frequency만 있고, days와 weeks는 비어 있음
✅ 3. 휴무일 설정 모달 트리거
<button
type="button"
onClick={() => setShowHolidayRuleModal(true)}
>
휴무일 설정
</button>
- 버튼 클릭 시 HolidayRuleModal이 열림
✅ 4. 휴무일 설정 저장 처리
<HolidayRuleModal
isOpen={true}
defaultValue={form.holidayRule ?? defaultHolidayRule}
onSave={(updatedRule) => {
setForm(prev => ({ ...prev, holidayRule: updatedRule }));
setShowHolidayRuleModal(false);
}}
onCancel={() => setShowHolidayRuleModal(false)}
/>
여기서:
- defaultValue는 현재 form.holidayRule
- 모달 내에서 사용자가 설정한 값을 onSave로 전달하면 상태 업데이트
- onCancel을 누르면 모달 닫기만 수행
✅ 5. 설정된 휴무일 요약 표시
<p className="mt-0 text-xs text-gray-600 dark:text-gray-300">
{form.holidayRule.frequency} {form.holidayRule.days.join(', ')}
{form.holidayRule.weeks?.length
? ` (매월 ${form.holidayRule.weeks.join(', ')}주차)`
: ''} 휴무
</p>
예시 출력:
- 매주 월,화 휴무
- 격주 수,금 (매월 2,4주차) 휴무
✅ 6. 전체 흐름 요약
순서 | 설명 |
1. | 사용자가 "휴무일 설정" 버튼 클릭 |
2. | HolidayRuleModal 열림 (기존 데이터 전달됨) |
3. | 모달 내에서 주기/요일/주차 선택 |
4. | "저장" 시 → 선택된 설정이 form.holidayRule에 저장됨 |
5. | 설정 요약은 본문에 간단히 표시됨 |
6. | 최종적으로 Firestore에 저장될 때 이 holidayRule도 함께 저장됨 |
✅ 7. 저장 예시 (Firestore에 저장될 데이터)
{
"holidayRule": {
"frequency": "한 달 2회",
"days": ["일"],
"weeks": [1, 3]
}
}
HolidayRuleModal 컴포넌트
- 매장 휴무일 설정을 위한 모달 UI입니다.
- 사용자는 주기(frequency), 요일(days), 그리고 몇째 주(weeks)를 선택하여 정기적인 휴무 규칙을 정의할 수 있습니다.
'use client';
import {
Dialog,
DialogPanel,
DialogTitle,
Transition,
TransitionChild,
} from '@headlessui/react';
import { Fragment, useState } from 'react';
import type { HolidayRule, HolidayFrequency } from '@/types/store';
export type DayOfWeek = '월' | '화' | '수' | '목' | '금' | '토' | '일';
interface HolidayRuleModalProps {
isOpen: boolean;
defaultValue: HolidayRule;
onSave: (rule: HolidayRule) => void;
onCancel: () => void;
}
const allDays: DayOfWeek[] = ['월', '화', '수', '목', '금', '토', '일'];
const emptyRule: HolidayRule = {
frequency: '매주',
days: [],
weeks: [],
};
export default function HolidayRuleModal({
isOpen,
defaultValue,
onSave,
onCancel,
}: HolidayRuleModalProps) {
const [rule, setRule] = useState<HolidayRule>(defaultValue);
const toggleDay = (day: DayOfWeek) => {
setRule((prev) => ({
...prev,
days: prev.days.includes(day)
? prev.days.filter((d) => d !== day)
: [...prev.days, day],
}));
};
const toggleWeek = (week: number) => {
setRule((prev) => {
const current = prev.weeks || [];
if (prev.frequency === '매월 1회') {
return { ...prev, weeks: [week] }; // 한 개만 선택 가능
}
// 매월 2회인 경우: 다중 선택 가능
return {
...prev,
weeks: current.includes(week)
? current.filter((w) => w !== week)
: [...current, week],
};
});
};
const handleFrequencyChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value as HolidayFrequency;
setRule({
frequency: value,
days: [],
weeks: value === '매월 1회' || value === '매월 2회' ? [] : undefined,
});
};
const handleClear = () => {
setRule(emptyRule);
};
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-50" onClose={onCancel}>
<TransitionChild
as={Fragment}
enter="ease-out duration-200"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black/30" />
</TransitionChild>
<div className="fixed inset-0 flex items-center justify-center p-4">
<TransitionChild
as={Fragment}
enter="ease-out duration-200"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-150"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<DialogPanel className="w-full max-w-md bg-white rounded-xl p-6 shadow-xl">
<DialogTitle className="text-lg font-semibold mb-4">휴무일 설정</DialogTitle>
<div className="space-y-4">
{/* 주기 선택 */}
<div>
<label className="block text-sm font-medium mb-1">휴무 주기</label>
<select
className="w-full p-2 border rounded"
value={rule.frequency}
onChange={handleFrequencyChange}
>
<option value="매주">매주</option>
<option value="격주">격주</option>
<option value="매월 1회">매월 1회</option>
<option value="매월 2회">매월 2회</option>
</select>
</div>
{/* 요일 선택 */}
<div>
<label className="block text-sm font-medium mb-1">요일 선택</label>
<div className="flex gap-2 flex-wrap">
{allDays.map((day) => (
<button
key={day}
type="button"
onClick={() => toggleDay(day)}
className={`px-3 py-1.5 rounded border text-sm transition ${
rule.days.includes(day)
? 'bg-blue-600 text-white border-blue-600'
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-100'
}`}
>
{day}
</button>
))}
</div>
</div>
{/* 주차 선택 */}
{(rule.frequency === '매월 1회' || rule.frequency === '매월 2회') && (
<div>
<label className="block text-sm font-medium mb-1">몇째 주</label>
<div className="flex gap-2">
{[1, 2, 3, 4, 5].map((week) => (
<button
key={week}
type="button"
onClick={() => toggleWeek(week)}
className={`px-3 py-1.5 rounded border text-sm transition ${
rule.weeks?.includes(week)
? 'bg-green-600 text-white border-green-600'
: 'bg-white text-gray-700 border-gray-300 hover:bg-gray-100'
}`}
>
{week}주차
</button>
))}
</div>
</div>
)}
</div>
<div className="mt-6 flex justify-between items-center">
<button
onClick={handleClear}
className="px-4 py-2 rounded border border-gray-300 hover:bg-gray-100"
>
지우기
</button>
<div className="flex gap-2">
<button
onClick={onCancel}
className="px-4 py-2 rounded border border-gray-300 hover:bg-gray-100"
>
취소
</button>
<button
onClick={() => onSave(rule)}
className="px-4 py-2 rounded bg-blue-600 text-white hover:bg-blue-700"
>
저장
</button>
</div>
</div>
</DialogPanel>
</TransitionChild>
</div>
</Dialog>
</Transition>
);
}
📦 1. 주요 타입 정의
export interface HolidayRule {
frequency: '매주' | '격주' | '매월 1회' | '매월 2회';
days: DayOfWeek[]; // 선택한 요일
weeks?: number[]; // '매월 1회' 또는 '매월 2회'일 때 사용
}
export type DayOfWeek = '월' | '화' | '수' | '목' | '금' | '토' | '일';
🔄 2. 컴포넌트 동작 흐름
1. 초기 상태 세팅
const [rule, setRule] = useState<HolidayRule>(defaultValue);
- 부모 컴포넌트에서 전달된 defaultValue로 상태 초기화
2. 휴무 주기 선택 (frequency)
<select onChange={handleFrequencyChange}>
<option value="매주">매주</option>
<option value="격주">격주</option>
<option value="매월 1회">매월 1회</option>
<option value="매월 2회">매월 2회</option>
</select>
const handleFrequencyChange = (e) => {
setRule({
frequency: value,
days: [],
weeks: value === '매월 1회' || value === '매월 2회' ? [] : undefined,
});
};
- 주기 변경 시 기존 days와 weeks 초기화
- 주차 선택이 필요한 주기 (매월 1회, 매월 2회)일 때만 weeks 배열 유지
3. 요일 선택
const toggleDay = (day: DayOfWeek) => {
// 토글 방식으로 day 추가/제거
};
{allDays.map((day) => (
<button onClick={() => toggleDay(day)}>
{day}
</button>
))}
- 선택한 요일은 파란색으로 강조 (bg-blue-600)
4. 주차 선택 (1~5주차)
{(rule.frequency === '매월 1회' || rule.frequency === '매월 2회') && (
// 1~5주차 선택 UI 표시
)}
const toggleWeek = (week: number) => {
if (rule.frequency === '매월 1회') {
setRule({ ...rule, weeks: [week] }); // 하나만 선택 가능
} else {
// 매월 2회는 토글 방식으로 다중 선택
}
};
- 선택한 주차는 초록색으로 강조 (bg-green-600)
5. 지우기 버튼
<button onClick={handleClear}>지우기</button>
const emptyRule = { frequency: '매주', days: [], weeks: [] };
- 휴무 규칙을 초기 상태로 되돌림
6. 취소 / 저장
<button onClick={onCancel}>취소</button>
<button onClick={() => onSave(rule)}>저장</button>
- 취소: 모달 닫기만 수행
- 저장: 현재 설정된 rule을 부모 컴포넌트로 전달
🎯 사용 예시
매월 2회, 월요일과 수요일, 2주차/4주차에 쉬는 경우
{
frequency: '매월 2회',
days: ['월', '수'],
weeks: [2, 4],
}
🧠 UX 포인트
- frequency에 따라 동적으로 weeks 표시 유무 제어
- 요일, 주차는 토글 UI
- 선택된 항목만 색상 강조 → 사용자 피드백 명확
- onSave와 onCancel로 부모 컴포넌트와의 연결 깔끔하게 분리
✅ 요약
기능 | 설명 |
휴무 주기 선택 | 매주, 격주, 매월 1회, 매월 2회 |
요일 선택 | 복수 선택 가능 |
몇째 주 선택 | 주기가 매월일 때만 표시, 매월 1회는 단일 선택 |
저장/취소/초기화 | 모달 닫기 또는 설정 반영 가능 |
휴무일 판단
이 StoreLandingPage 컴포넌트에서는 오늘이 매장 휴무일인지 판단하고,
그에 따라 영업 상태를 보여주는 기능이 구현되어 있습니다.
🔍 휴무일 판단 및 화면 표출 전체 흐름
✅ 1. 오늘의 요일과 날짜 정보 계산
const today = new Date();
const dayIndex = today.getDay(); // 0(일) ~ 6(토)
const days: DayOfWeek[] = ['일', '월', '화', '수', '목', '금', '토'];
const todayLabel = days[dayIndex]; // 현재 요일 문자열 ('월', '화' 등)
✅ 2. 오늘이 휴무일인지 여부 판별
const rule: HolidayRule | undefined = store.holidayRule;
const isTodayHoliday =
rule?.days?.includes(todayLabel) &&
(
rule.frequency === '매주' || // 매주 해당 요일
rule.frequency === '격주' || // (격주도 같은 방식으로 적용됨)
(
(rule.frequency === '매월 1회' || rule.frequency === '매월 2회') &&
rule.weeks?.includes(getWeekNumberOfMonth(today))
)
);
- rule.days에 오늘 요일이 포함되어 있고
- 주기가 매주, 격주, 또는 매월 N회 + 주차 조건 충족이면 휴무일로 판정합니다.
👉 getWeekNumberOfMonth() 함수 설명
const getWeekNumberOfMonth = (date: Date): number => {
const first = new Date(date.getFullYear(), date.getMonth(), 1); // 그 달 1일
const offset = first.getDay(); // 그 달 1일이 무슨 요일인지
const adjustedDate = date.getDate() + offset;
return Math.ceil(adjustedDate / 7); // 주차 계산
};
✅ 3. 오늘의 영업시간 정보와 비교
const todayHour = store.businessHours[todayLabel]; // 오늘의 영업시간 정보
- todayHour.opening과 closing이 없거나
- isTodayHoliday === true이면 → 휴무 처리
✅ 4. 화면 표출 (renderTodayBusinessHour())
🔹 휴무일인 경우:
<p className="text-sm"><strong>{todayLabel}요일</strong>: 오늘은 휴무입니다.</p>
<p className="text-sm font-semibold mt-1 text-red-500">영업 중이 아닙니다.</p>
🔹 영업일인 경우:
opening, closing과 현재 시간을 비교하여 영업 중인지 계산:
const isOpenNow = now >= openTime && now < closeTime;
- 결과에 따라 메시지 표시:
<p>{todayLabel}요일: {todayHour.opening} ~ {todayHour.closing}</p>
<p className={isOpenNow ? 'text-green-600' : 'text-red-500'}>
{isOpenNow ? '영업 중입니다' : '영업 중이 아닙니다.'}
</p>
✅ 5. 휴무일 요약 표출 (renderHolidayRule())
<p className="text-sm">
{rule.frequency} 휴무: {rule.days.join(', ')}
{rule.weeks && rule.weeks.length > 0 && <> (매월 {rule.weeks.join(', ')}주차)</>}
</p>
예시 출력:
매월 2회 휴무: 월, 수 (매월 1, 3주차)
✅ 요약
항목 | 설명 |
요일 판별 | Date.getDay()를 활용해 오늘의 요일 (월, 화 등)을 얻음 |
주차 판별 | getWeekNumberOfMonth()로 현재 달의 몇째 주인지 계산 |
휴무 판단 | holidayRule.frequency + days + weeks를 조합하여 휴무 여부 판단 |
영업 시간 판별 | 오늘의 opening/closing과 현재 시간 비교 |
결과 출력 | 영업 중 / 휴무 여부를 화면에 문구와 색상으로 구분하여 출력 |
https://github.com/inetsos/next-tailwind-firebase-order-app
GitHub - inetsos/next-tailwind-firebase-order-app
Contribute to inetsos/next-tailwind-firebase-order-app development by creating an account on GitHub.
github.com