feat:내가게관리 UI 구현#37
Conversation
📝 WalkthroughWalkthrough내 가게 관리(Owner) 페이지 및 다수의 오너 전용 컴포넌트(테이블 대시보드·상세·생성, 브레이크타임, 메뉴 관리, 가게 설정, 모달 등)가 신규 추가되었고, 라우트에 Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant App as App/Router
participant OwnerPage as OwnerPage
participant OwnerHeader as OwnerHeader
participant TabContent as TabContent (Dashboard/Settings/Menu)
User->>App: GET /mypage/store/:storeId
App->>OwnerPage: render OwnerPage(storeId)
OwnerPage->>OwnerHeader: props(activeTab, onChangeTab)
OwnerHeader->>OwnerPage: onChangeTab(tabKey)
OwnerPage->>TabContent: render selected tab component
TabContent->>User: UI (대시보드 / 설정 / 메뉴)
sequenceDiagram
participant User as User
participant TableDashboard as TableDashboard
participant TableCreateModal as TableCreateModal
participant TableDetailModal as TableDetailModal
participant BreakTimeModal as BreakTimeModal
User->>TableDashboard: 클릭 - 테이블 생성
TableDashboard->>TableCreateModal: open()
User->>TableCreateModal: 입력(cols, rows) -> 생성
TableCreateModal->>TableDashboard: onConfirm(cols, rows)
TableDashboard->>TableDashboard: 테이블 그리드 상태 업데이트
User->>TableDashboard: 특정 테이블 클릭
TableDashboard->>TableDetailModal: open(tableNumber, tableInfo, breakTimes)
User->>TableDetailModal: 날짜 선택 -> 슬롯 토글
TableDetailModal->>TableDashboard: 예약/용량 업데이트 (선택적)
User->>TableDashboard: 브레이크타임 추가
TableDashboard->>BreakTimeModal: open()
User->>BreakTimeModal: 입력(start,end) -> confirm
BreakTimeModal->>TableDashboard: onConfirm(breakTime)
TableDashboard->>TableDashboard: breakTimes 업데이트
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/pages/myPage/storePage.tsx (1)
83-182:⚠️ Potential issue | 🟠 Major로딩/에러/빈 목록 상태 UI가 빠져 있어요.
현재 stats/shops를 바로 렌더링해서 데이터가 없거나 실패 시 빈 화면이 됩니다. 요구사항에도 예외 처리(로딩/에러/빈 화면)가 포함돼 있어, 최소한의 분기 UI를 넣어두는 게 좋아요.✅ 예시(간단한 상태 분기)
+ // 예시 상태 (실데이터 연동 시 state/props로 대체) + const isLoading = false; + const error: string | null = null; + + if (isLoading) { + return ( + <section className="rounded-xl bg-white p-8 shadow-sm border border-gray-100"> + <p className="text-sm text-gray-500">로딩 중...</p> + </section> + ); + } + + if (error) { + return ( + <section className="rounded-xl bg-white p-8 shadow-sm border border-gray-100"> + <p className="text-sm text-red-500">데이터를 불러오지 못했어요.</p> + </section> + ); + }- <div className="space-y-4 mb-8"> - {shops.map((shop) => ( + <div className="space-y-4 mb-8"> + {shops.length === 0 ? ( + <div className="rounded-xl border border-gray-100 p-6 text-sm text-gray-500"> + 등록된 가게가 없습니다. + </div> + ) : ( + shops.map((shop) => ( ... - ))} + )) + )} </div>As per coding guidelines:
src/pages/**: 라우팅/레이아웃 영향, 로딩/에러/empty 상태 UX 확인.
🤖 Fix all issues with AI agents
In `@src/pages/ownerPage/BreakTimeModal.tsx`:
- Around line 28-30: The close button in BreakTimeModal lacks an accessible
name—update the button element (the <button onClick={onClose}
className="absolute right-4 top-4"> that renders the <X /> icon) to include an
aria-label (e.g., aria-label="Close" or a localized equivalent) so screen
readers announce its purpose; also consider adding type="button" if absent to
avoid form-submit behavior.
- Around line 75-84: The onClick handler in BreakTimeModal currently calls
onConfirm({ start, end }) without validation; update BreakTimeModal to validate
that start < end and that the chosen start/end fall within the component's
business hours (e.g., props like operatingHours, businessStart/businessEnd or
similar) before calling onConfirm/onClose. If validation fails, prevent calling
onConfirm/onClose and surface an inline error state (e.g., setValidationError)
or disable the confirm button; ensure the checks reference the existing symbols
start, end, onConfirm, onClose and the component's business hours prop/constant
so invalid break times are rejected.
In `@src/pages/ownerPage/menuManagement.tsx`:
- Around line 89-94: The toggle button lacks accessibility attributes so screen
readers can't announce state; update the button in menuManagement.tsx (the
element using onClick={() => toggleActive(menu.id)} and reading menu.isActive)
to include role="switch", aria-checked={menu.isActive}, and an accessible name
(either aria-label or aria-labelledby referencing the menu title) so assistive
tech can announce both purpose and current state; ensure the inner div visual
state still uses menu.isActive to keep visuals in sync with the aria state.
- Around line 60-98: When filteredMenus is empty the UI shows nothing; update
the JSX around the menu card grid where filteredMenus.map(...) is used to
conditionally render an empty state when filteredMenus.length === 0. Replace or
wrap the current mapping with a conditional branch that, when empty, renders a
centered, accessible empty state element (e.g., icon, short message like "메뉴가
없습니다." and an optional action button to reset filters) instead of the grid
items; keep existing classNames/styling consistency and ensure toggleActive,
categories lookups, and other menu card code remain unchanged for the non-empty
branch.
In `@src/pages/ownerPage/ownerPage.tsx`:
- Around line 69-80: TableDashboard, MenuManagement, and StoreSettings lack
proper loading/error/empty-state rendering and ownerPage contains redundant
inner activeTab checks; update each component to track and render loading (e.g.,
isLoading), error (e.g., error / errorMessage), and empty states (e.g.,
items.length === 0) with appropriate fallback UIs (spinner/message, error
banner, and centered "no data" text) and ensure TableDashboard handles empty
menus and breakTimes, MenuManagement renders an empty-state when
filteredMenus.length === 0, and StoreSettings renders loading/error/empty states
for its data; finally, remove the duplicate inner activeTab boolean checks
inside the conditional divs in ownerPage (keep only the outer {activeTab ===
'...'} checks that render <StoreSettings /> and <MenuManagement />).
In `@src/pages/ownerPage/storeSettings.tsx`:
- Around line 222-227: The "설정 저장" button currently has no onClick handler so it
does nothing; add a handler (e.g., handleSaveSettings) in the same component
that performs the mock save flow and attach it to the button's onClick prop;
inside the handler either call a provided save function or emit a visible
confirmation (toast/alert) and/or console.log with the settings payload to aid
testing, and ensure any async mock save uses try/catch to log errors and update
local state (e.g., saving flag) so the UI can show feedback.
- Around line 199-217: The two custom toggle buttons for sameDayBooking and
noShowPolicy (handlers setSameDayBooking and setNoShowPolicy; state
sameDayBooking and noShowPolicy) lack accessibility attributes; update each
button element to include role="switch" and aria-checked={sameDayBooking} /
aria-checked={noShowPolicy} respectively, add an accessible label via aria-label
or aria-labelledby that describes the control (e.g., "Same day booking" and
"No-show policy"), and ensure the clickable element remains keyboard focusable
(keep it a button or add tabIndex if changing element type) so screen readers
correctly announce the switch and its current state.
In `@src/pages/ownerPage/tableCreateModal.tsx`:
- Line 16: The close button lacks an accessible name and the modal doesn't
handle Escape; add an explicit aria-label (e.g., aria-label="Close modal") to
the button element that uses onClick={onClose} (the button rendering <X />) and
add a useEffect in the modal component to register a keydown listener that calls
onClose when e.key === 'Escape', ensuring you remove the listener on cleanup and
include onClose in the effect dependencies.
- Around line 22-36: The cols and rows number inputs in tableCreateModal.tsx
currently allow 0 or negative values; update the input handling for the inputs
bound to cols/rows (and their setters setCols/setRows) to enforce a valid range
(e.g., add min="1" and a sensible max attribute) and clamp/parsing logic in the
onChange handlers (convert to Number, fallback to 1 if NaN or <1, and cap at
max) and ensure any submit handler or grid-creation function validates the final
cols and rows values before use.
In `@src/pages/ownerPage/tableDashboard.tsx`:
- Line 95: The JSX div with className "bg-white border border rounded-lg p-4"
has a duplicated "border" token; update that className in the relevant JSX
element (the div in tableDashboard.tsx) to remove the duplicate — e.g., use a
single "border" (or replace the duplicate with a specific Tailwind border color
like "border-gray-200" if you intended a color) so the class string is not
repeating tokens.
- Around line 218-221: The cancel button currently calls updateTable(id, {
isEditingCapacity: false }) like the confirm button, so edits are not reverted;
modify the cancel flow to restore the previous capacity value when closing edit
mode. Preserve the original capacity when entering edit mode (e.g., add a field
like originalCapacity or store it in component state when toggling
isEditingCapacity on the table record) and on the cancel button call
updateTable(id, { isEditingCapacity: false, capacity: originalCapacity }) (or
reset the local state to originalCapacity before updating) so the change is
discarded; locate the JSX with the two buttons and the updateTable usage in
tableDashboard.tsx and update the enter-edit and cancel logic accordingly,
keeping confirm using the edited capacity and cancel restoring originalCapacity.
In `@src/pages/ownerPage/tableDetailModal.tsx`:
- Line 102: The close button in tableDetailModal.tsx (the button with
onClick={onClose} that renders the <X size={24} /> icon) lacks an accessible
name; add an aria-label (e.g., aria-label="닫기" or aria-label="Close" depending
on localization) to the button so screen readers can announce its purpose,
ensuring the existing onClick={onClose} and icon remain unchanged.
🧹 Nitpick comments (8)
src/pages/ownerPage/storeSettings.tsx (1)
170-190: 최소/최대 인원 유효성 검사 누락최소 인원이 최대 인원보다 클 수 있는 상황이 방지되지 않았어요. 사용자가 잘못된 값을 입력하면 예약 시스템에서 문제가 생길 수 있습니다.
🛡️ 유효성 검사 추가
<div> <label className={labelStyle}>최소 예약 인원</label> <input type="number" value={minGuests} - onChange={(e) => setMinGuests(Number(e.target.value))} + min={1} + onChange={(e) => { + const val = Math.max(1, Number(e.target.value)); + setMinGuests(Math.min(val, maxGuests)); + }} placeholder="최소 인원을 입력하세요" className={inputStyle} /> </div> <div> <label className={labelStyle}>최대 예약 인원</label> <input type="number" value={maxGuests} - onChange={(e) => setMaxGuests(Number(e.target.value))} + min={minGuests} + onChange={(e) => setMaxGuests(Math.max(minGuests, Number(e.target.value)))} placeholder="최대 인원을 입력하세요" className={inputStyle} /> </div>src/pages/ownerPage/menuManagement.tsx (1)
38-40: 여러 버튼에 기능이 연결되지 않았어요"메뉴 추가", "수정", "삭제", "카테고리 추가/수정/삭제" 버튼들이 아직 기능이 없는 상태예요. 목데이터 단계라면 괜찮지만, TODO 주석이나 콘솔 로그라도 추가해두면 나중에 구현할 때 놓치지 않을 것 같아요.
기능 구현 트래킹을 위해 별도 이슈를 생성할까요?
Also applies to: 80-81, 108-109, 114-116
src/pages/ownerPage/tableDetailModal.tsx (3)
5-10: 사용되지 않는 prop:onManageReservation
onManageReservationprop이 선언되어 있지만 컴포넌트 내부에서 사용되지 않고 있어요. 향후 사용 예정이라면 TODO 주석을 남기거나, 현재 불필요하다면 제거하는 게 좋겠습니다.🧹 미사용 prop 제거
interface Props { tableNumber: number; onClose: () => void; breakTimes: BreakTime[]; - onManageReservation?: () => void; }
53-58:getTableType함수의 견고성 개선 필요
capacity문자열 파싱이 "2~4인" 형식을 가정하고 있어요. 예상치 못한 형식이 들어오면NaN이 반환되어 예기치 않은 동작이 발생할 수 있습니다.🛡️ 안전한 파싱 로직
const getTableType = (capacity: string): TableType => { - const max = Number(capacity.split('~')[1]?.replace('인', '')); + const match = capacity.match(/(\d+)~(\d+)/); + const max = match ? Number(match[2]) : 4; // 기본값 4 if (max <= 4) return '소형'; if (max <= 8) return '중형'; return '단체석'; };
181-192: 캘린더 날짜 버튼 접근성 개선날짜 버튼에 스크린 리더를 위한 상세 레이블이 없어요. 날짜만 표시되면 컨텍스트가 부족할 수 있습니다.
♿ aria-label 추가
<button key={day} disabled={isPast} onClick={() => { setSelectedFullDate(dateObj); setStep('SLOTS'); }} + aria-label={`${year}년 ${month + 1}월 ${day}일${isTodayFlag ? ' (오늘)' : ''}${isPast ? ' (선택 불가)' : ''}`} className={`h-14 rounded-xl border-2 ...`} >src/pages/ownerPage/tableDashboard.tsx (2)
255-264: 영업시간 하드코딩
BreakTimeModal에 전달되는openTime과closeTime이 하드코딩되어 있어요.StoreSettings에서 설정한 영업시간과 연동되어야 일관성이 유지됩니다.현재는 목데이터 단계이므로 괜찮지만, API 연동 시에는 가게 설정에서 영업시간을 가져오거나 props로 전달받도록 수정이 필요합니다. TODO 주석을 남겨두면 좋겠어요.
{isBreakModalOpen && ( <BreakTimeModal - openTime="11:00" - closeTime="22:00" + openTime="11:00" // TODO: 가게 설정에서 가져오기 + closeTime="22:00" // TODO: 가게 설정에서 가져오기 onClose={() => setIsBreakModalOpen(false)}
40-63: 코드 포맷팅 일관성
getTableStyle함수의 들여쓰기가 일관되지 않아요. 첫 번째 조건과 나머지 조건의 들여쓰기가 다릅니다.🧹 포맷팅 정리
const getTableStyle = (capacity: number) => { - if (capacity <= 4) - return { - border: 'border-yellow-300', - ... - }; - - if (capacity <= 8) - return { + if (capacity <= 4) { + return { + border: 'border-yellow-300', + bg: 'bg-yellow-100', + badge: 'bg-yellow-500', + hover: 'hover:bg-yellow-200', + }; + } + + if (capacity <= 8) { + return { + border: 'border-blue-300', + bg: 'bg-blue-100', + badge: 'bg-blue-500', + hover: 'hover:bg-blue-200', + }; + } + + return { + border: 'border-purple-300', + bg: 'bg-purple-100', + badge: 'bg-purple-500', + hover: 'hover:bg-purple-200', + }; + };src/pages/ownerPage/ownerPage.tsx (1)
22-80: 탭 내비게이션 접근성(role/aria) 보강 제안.현재도 버튼으로 클릭은 가능하지만, 스크린리더/키보드 사용자를 위해
role="tablist",role="tab",aria-selected,aria-controls,role="tabpanel"를 붙이면 접근성이 크게 좋아집니다.✅ 제안 코드 (접근성 보강)
- <nav className="flex gap-5 pt-4 px-5"> + <nav + className="flex gap-5 pt-4 px-5" + role="tablist" + aria-label="내 가게 관리 탭" + > <button + type="button" + role="tab" + id="owner-tab-dashboard" + aria-selected={activeTab === 'dashboard'} + aria-controls="owner-tabpanel-dashboard" onClick={() => setActiveTab('dashboard')} className={`pb-4 px-2 text-md transition-all relative ${ activeTab === 'dashboard' ? 'text-blue-600' : 'text-gray-900 hover:text-gray-900' }`} > ... <button + type="button" + role="tab" + id="owner-tab-settings" + aria-selected={activeTab === 'settings'} + aria-controls="owner-tabpanel-settings" onClick={() => setActiveTab('settings')} className={`pb-4 px-2 text-md transition-all relative ${ activeTab === 'settings' ? 'text-blue-600' : 'text-gray-900 hover:text-gray-900' }`} > ... <button + type="button" + role="tab" + id="owner-tab-menu" + aria-selected={activeTab === 'menu'} + aria-controls="owner-tabpanel-menu" onClick={() => setActiveTab('menu')} className={`pb-4 px-2 text-md transition-all relative ${ activeTab === 'menu' ? 'text-blue-600' : 'text-gray-900 hover:text-gray-900' }`} > ... - {activeTab === 'dashboard' && <TableDashboard />} + {activeTab === 'dashboard' && ( + <section + role="tabpanel" + id="owner-tabpanel-dashboard" + aria-labelledby="owner-tab-dashboard" + > + <TableDashboard /> + </section> + )} {activeTab === 'settings' && ( - <div className="max-w-7xl mx-auto text-gray-500"> + <div + className="max-w-7xl mx-auto text-gray-500" + role="tabpanel" + id="owner-tabpanel-settings" + aria-labelledby="owner-tab-settings" + > {activeTab === 'settings' && <StoreSettings />} </div> )} {activeTab === 'menu' && ( - <div className="max-w-7xl mx-auto text-gray-500"> + <div + className="max-w-7xl mx-auto text-gray-500" + role="tabpanel" + id="owner-tabpanel-menu" + aria-labelledby="owner-tab-menu" + > {activeTab === 'menu' && <MenuManagement />} </div> )}
dew102938
left a comment
There was a problem hiding this comment.
사용자가 클릭 가능함을 인지할 수 있도록
리뷰를 남긴 부분 외 모든 버튼들에도
cursor-pointer 적용하면 좋을 것 같습니다
수고많으셨습니다!!
jjjsun
left a comment
There was a problem hiding this comment.
고생하셨어요! 코드리뷰달았습니다! 전체적으로 색깔은 indigo대신 blue로 변경부탁드려요!
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/pages/ownerPage/tableDashboard.tsx`:
- Around line 23-52: getTableData currently reads the external tableData causing
stale reads during consecutive updates; change updateTable to derive the current
state from the updater argument (prev) instead of calling getTableData() that
reads the outer tableData. Specifically, inside updateTable's setTableData(prev
=> { ... }) compute current as prev[id] || { numValue: id, minCapacity: 2,
maxCapacity: 4, isEditingCapacity: false, isEditingNum: false } (or refactor
getTableData to accept prev/state param) then apply updates and the min/max
guards before returning { ...prev, [id]: next } so all calculations use prev and
avoid race conditions.
- Around line 193-241: The capacity inputs allow NaN or negative values when
empty/invalid; add validation in the handlers around updateTable to parse and
clamp values: in the maxCapacity onChange/onBlur and minCapacity buttons/changes
(related to table.minCapacity, table.maxCapacity, updateTable,
isEditingCapacity) ensure you convert input with Number/parseInt and if result
is NaN default to a safe value (e.g., Math.max(1, current min or 1)), forbid
negatives by using Math.max(1, value), and guarantee maxCapacity > minCapacity
(set max = min+1 if needed) before calling updateTable so invalid or empty
inputs never persist.
In `@src/pages/ownerPage/tableDetailModal.tsx`:
- Around line 273-285: The status label currently shows "미운영" for both break
time and unavailable states; update the conditional label logic in the span
inside tableDetailModal.tsx (the code using isBreak and isAvailable) so it
differentiates: when isBreak show '미운영', when isAvailable show '예약 가능',
otherwise show '예약 불가' (leave the existing className color branches as-is so the
red styling continues to indicate unavailability).
jjjsun
left a comment
There was a problem hiding this comment.
충돌 해결해주세요!
그리고 해결하신 코멘트들은 Resolved conversation 버튼 눌러서 닫아주세요!
dew102938
left a comment
There was a problem hiding this comment.
수정 내용 확인했습니다! 구조 관련해서 의견 하나 남깁니다
현재 ownerPage 폴더에 페이지 엔트리와 UI 컴포넌트가 함께 있어 역할이 조금 섞여 보입니다.
ownerPage.tsx만 페이지 폴더에 두고
섹션, 모달 컴포넌트는 components/owner처럼 분리해도 좋을 것 같습니다
한 번 검토해주시면 감사하겠습니다!
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Fix all issues with AI agents
In `@src/components/owner/BreakTimeModal.tsx`:
- Around line 25-38: The modal markup in BreakTimeModal lacks ARIA dialog
attributes; update the root modal container (the outermost div returned by the
BreakTimeModal component) to include role="dialog", aria-modal="true", and
aria-labelledby pointing to the modal title element (e.g., give the title
element a unique id and reference it from aria-labelledby) so assistive tech
recognizes it as a dialog; ensure focus management remains handled by existing
onClose and stopPropagation logic and that the title element (the div containing
the Clock and "브레이크 타임 설정") gets the matching id.
In `@src/components/owner/menuFormModal.tsx`:
- Around line 54-63: The modal wrapper JSX (the outer div in the menuFormModal
component) is missing dialog ARIA attributes; add role="dialog" and
aria-modal="true" to that container and set aria-labelledby to the id of the
title element so screenreaders can announce it. Give the <h3> that renders
{editingMenu ? '메뉴 수정' : '새 메뉴 등록'} a stable id (e.g. menuFormModalTitle) and
reference that id from aria-labelledby on the container; keep the existing
onClose button and X icon unchanged.
In `@src/components/owner/menuManagement.tsx`:
- Around line 34-41: When updating an existing menu in handleFormSubmit, don't
overwrite the whole object with menuData; merge the existing menu object with
the incoming menuData so fields like restaurantId, imageUrl, createdAt are
preserved. Locate the branch that runs when editingMenu is true (the setMenus
call that maps over menus and replaces where m.id === menuData.id) and change it
to return a merged object (e.g., {...m, ...menuData}) instead of menuData; keep
the add branch (setMenus(prev => [menuData, ...prev])) as-is.
In `@src/components/owner/ownerHeader.tsx`:
- Around line 25-40: The nav tab markup in ownerHeader.tsx lacks ARIA roles and
selection state; update the tabs rendering (the nav element that maps over tabs,
the button elements, and the onChangeTab handler) to implement proper ARIA: give
the nav role="tablist", each button role="tab" plus a stable id (e.g.
`${tab.key}-tab`), set aria-selected={activeTab === tab.key} (and
tabindex={activeTab===tab.key?0:-1}) and include aria-controls pointing to the
corresponding panel id (e.g. `${tab.key}-panel`); ensure the tab panels use
matching ids and role="tabpanel" so screen readers can associate selection and
content.
In `@src/components/owner/storeSettings.tsx`:
- Around line 214-218: The switch's aria-checked is incorrectly bound to
sameDayBooking; update the accessibility state to reflect the actual toggle by
using noShowPolicy instead. Locate the button with role='switch' that calls
setNoShowPolicy and change its aria-checked prop from sameDayBooking to
noShowPolicy so screen readers report the correct no-show policy state (ensure
setNoShowPolicy and noShowPolicy remain the controlling handlers/values).
In `@src/components/owner/tableDashboard.tsx`:
- Around line 271-276: The cancel button has an invalid Tailwind class
`bg-[\`#FF6B6B\`]` which breaks styling and the icon-only buttons lack
accessibility labels; update the class to a valid Tailwind arbitrary color
syntax (e.g., `bg-[`#FF6B6B`]`) on the button that calls updateTable(id, {
isEditingCapacity: false, minCapacity: ..., maxCapacity: ... }) and add
descriptive aria-label attributes to both icon buttons (the one calling
updateTable(id, { isEditingCapacity: false }) with the Check icon and the
cancel/X button) so screen readers can identify their purpose; keep existing
props (id, tableData, table, updateTable) unchanged.
In `@src/components/owner/tableDetailModal.tsx`:
- Around line 137-139: In the TableDetailModal component replace the invalid
Tailwind class "grid-col-1" on the outer container with the correct
"grid-cols-1" so the grid layout works as intended (look for the div containing
className="grid grid-col-1" inside tableDetailModal.tsx and update it to use
"grid-cols-1").
🧹 Nitpick comments (5)
src/components/owner/menuFormModal.tsx (1)
4-10: props의 any를 구체 타입으로 명확히 해주세요.
onSubmit와editingMenu가any라 사용처가 불명확합니다. 폼 데이터 타입을 정의해 타입 안정성을 높이는 게 좋아요.✅ 개선 예시
+interface MenuFormData { + id: string; + name: string; + category: string; + price: number; + description?: string; + isActive: boolean; + isSoldOut: boolean; +} + interface MenuFormModalProps { isOpen: boolean; onClose: () => void; - onSubmit: (menuData: any) => void; + onSubmit: (menuData: MenuFormData) => void; categories: { id: string; label: string }[]; - editingMenu?: any; // 수정 시 전달받을 데이터 + editingMenu?: MenuFormData; // 수정 시 전달받을 데이터 }src/components/owner/menuManagement.tsx (1)
6-20: 카테고리 ID 타입을 동적 추가까지 커버하도록 확장해주세요.
CategoryType이 고정 union이라as CategoryType캐스팅이 필요해 타입 안정성이 떨어집니다.✅ 개선 예시
-type CategoryType = 'ALL' | 'MAIN' | 'SIDE' | 'DRINK'; +type CategoryId = 'ALL' | 'MAIN' | 'SIDE' | 'DRINK' | `CAT_${string}`; -const [activeCategory, setActiveCategory] = useState<CategoryType>('ALL'); +const [activeCategory, setActiveCategory] = useState<CategoryId>('ALL'); -const [categories, setCategories] = useState([ +const [categories, setCategories] = useState<{ id: CategoryId; label: string }[]>([ { id: 'ALL', label: '전체' }, { id: 'MAIN', label: '메인 메뉴' }, { id: 'SIDE', label: '사이드 메뉴' }, { id: 'DRINK', label: '음료' }, ]); - onClick={() => setActiveCategory(cat.id as CategoryType)} + onClick={() => setActiveCategory(cat.id)}Also applies to: 105-111
src/components/owner/tableDetailModal.tsx (1)
243-249: slot 토글은 함수형 업데이트로 바꾸는 게 안전합니다.
이전 상태 기반 업데이트라 배치 업데이트 상황에서 stale 가능성이 있습니다.✅ 개선 예시
- setSlots(slots.map(s => - s.id === slot.id - ? { ...s, isAvailable: !s.isAvailable } - : s - )); + setSlots(prev => + prev.map(s => + s.id === slot.id + ? { ...s, isAvailable: !s.isAvailable } + : s + ) + );src/pages/ownerPage/ownerPage.tsx (1)
15-67: OwnerHeader로 헤더/탭 UI를 재사용하면 중복을 줄일 수 있어요.
이미 동일 UI가 컴포넌트로 분리돼 있어서 단일 책임/유지보수 측면에서 재사용이 좋습니다.✅ 개선 예시
-import {Store} from 'lucide-react'; +import OwnerHeader from '../../components/owner/ownerHeader'; ... - <header className="bg-white border-b border-gray-200 pt-3"> - <div className="max-w-7xl mx-auto"> - <div className="text-lg text-gray-900 flex items-center gap-2 pb-4 border-b px-5"> - <Store className="text-blue-600" size={24} /> - 내 가게 관리 - </div> - ... - </div> - </header> + <OwnerHeader activeTab={activeTab} onChangeTab={setActiveTab} />src/components/owner/tableCreateModal.tsx (1)
13-19: 모달에role="dialog"/aria-modal/aria-labelledby를 추가해 접근성 보강 필요현재는 시맨틱 정보가 없어 스크린 리더가 모달을 인식하기 어려워요. 모달 컨테이너에 역할과 라벨을 부여해 주세요.
As per coding guidelines, 접근성(aria) 체크가 필요합니다.✅ 예시 코드
- <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50" - onClick={onClose}> - <div className="bg-white w-[400px] rounded-3xl p-8 relative" - onClick={(e)=> e.stopPropagation()}> + <div + className="fixed inset-0 bg-black/50 flex items-center justify-center z-50" + onClick={onClose} + > + <div + role="dialog" + aria-modal="true" + aria-labelledby="table-create-title" + className="bg-white w-[400px] rounded-3xl p-8 relative" + onClick={(e) => e.stopPropagation()} + > - <h3 className="text-xl font-bold mb-6">테이블 생성하기</h3> + <h3 id="table-create-title" className="text-xl font-bold mb-6">테이블 생성하기</h3>
There was a problem hiding this comment.
Actionable comments posted: 11
🤖 Fix all issues with AI agents
In `@src/components/owner/menuManagement.tsx`:
- Around line 10-12: Add explicit loading and error state flags and conditional
rendering around the menus state: introduce useState<boolean> loading and
useState<string|null> error in the MenuManagement component and set loading=true
before any fetch/initialization for restaurantId, then setMenus on success and
setLoading=false; on failure setError(error.message) and setLoading=false.
Update the JSX to render a loading indicator when loading is true, an error
message when error is non-null, and only render menus (using menus and
activeCategory/setActiveCategory) when not loading and no error; ensure any mock
initialization or future API call in useEffect references restaurantId and
updates loading/error appropriately.
- Around line 149-154: The icon-only edit/delete buttons in menuManagement.tsx
lack accessible labels; update the two buttons that call handleEditClick(menu)
and deleteMenu(menu.id) to include descriptive aria-label attributes (e.g.,
aria-label="Edit menu: {menu.name}" and aria-label="Delete menu: {menu.name}"),
and consider also adding title attributes for hover tooltips; ensure the labels
use a stable menu property (like menu.name or menu.id) so screen readers convey
intent while keeping the existing click handlers and styles unchanged.
In `@src/components/owner/storeSettings.tsx`:
- Around line 133-145: The day-toggle buttons in the JSX map (inside the
component in storeSettings.tsx) lack accessibility state; update the button
element generated in the days.map to include
aria-pressed={closedDays.includes(day)} (use the existing closedDays and
toggleDay logic) so screen readers will announce the toggle state; ensure the
attribute value reflects the same boolean used to decide the visual state.
- Around line 39-47: The label for the store name lacks an htmlFor/id pairing so
clicking the label doesn't focus the input and screen readers can't associate
them; add a unique id (e.g., "storeName") to the input used by the storeName
state and set the label's htmlFor to that id (preserve labelStyle and inputStyle
and retain value={storeName} and onChange={setStoreName}); apply the same
pattern (unique id on inputs and matching htmlFor on labels) to the other fields
(description, phone, email, address, hours, capacity) to fix accessibility.
- Around line 170-190: The min/max guest inputs allow negative values and
minGuests can be greater than maxGuests; update the two inputs and their
handlers (value bindings minGuests/maxGuests and setters
setMinGuests/setMaxGuests) to enforce validation: add input attributes (e.g.,
min="0") to both <input type="number"> elements, and in the onChange handlers
clamp the parsed Number(e.target.value) to >=0 and ensure when changing one
value it respects the other (e.g., if setting minGuests and newMin > maxGuests,
also adjust maxGuests to newMin, and vice versa), and surface a simple
validation state/message if needed so the UI prevents invalid ranges.
- Around line 231-234: The click handler currently logs only { storeName, phone,
email, openTime, closeTime, closedDays }; update the onClick in the save button
handler to include the missing settings (description, address,
reservationPeriod, minGuests, maxGuests, sameDayBooking, noShowPolicy) so the
full state is output for debugging/API integration; locate the onClick closure
in src/components/owner/storeSettings.tsx and expand the console.log payload to
include those variable names and ensure they are the same identifiers used in
the component state.
In `@src/components/owner/tableDashboard.tsx`:
- Around line 280-281: The onClick currently sets only isEditingCapacity via
updateTable(id, { isEditingCapacity: true }) so
originalMinCapacity/originalMaxCapacity remain undefined and cancel can't
restore them; replace this inline handler to call the existing
startEditingCapacity function (startEditingCapacity(id)) which captures and
stores originalMinCapacity and originalMaxCapacity before toggling editing,
ensuring cancel restores original values; locate the onClick on the capacity
edit button and swap updateTable(...) for startEditingCapacity(id).
- Around line 232-236: The Pencil icon currently lacks an accessibility label
and keyboard support; add an aria-label prop to the Pencil element (e.g.,
aria-label="Edit table number" or localized text) and ensure it is
keyboard-focusable and activatable by keyboard—e.g., add tabIndex={0} and an
onKeyDown handler that calls updateTable(id, { isEditingNum: true }) on
Enter/Space so screen readers and keyboard users can operate the control;
reference the Pencil component, updateTable function and id to locate where to
apply these changes.
- Around line 249-250: The ▲/▼ buttons lack aria-labels causing poor
screen-reader accessibility; update the two button elements that call
updateTable (the onClick handlers referencing updateTable(id, { minCapacity: ...
}) and using table.minCapacity) to include descriptive aria-label
attributes—e.g., aria-label="Increase minimum capacity" and aria-label="Decrease
minimum capacity" (or include the table id/name like "Increase minimum capacity
for table {id}")—so screen readers convey each button's purpose while keeping
the existing onClick behavior.
- Around line 84-91: 현재 편집 시작 시 직접 updateTable(id, { isEditingCapacity: true })를
호출해 originalMinCapacity/originalMaxCapacity가 저장되지 않으니, 편집 시작 지점에서
startEditingCapacity(id)를 호출하도록 변경해 getTableData(id)로 원래 값을 캡처해 updateTable에
originalMinCapacity/originalMaxCapacity를 함께 설정하거나(대안) 만약 startEditingCapacity를 더
이상 사용하지 않기로 했다면 해당 함수를 완전히 제거하고 취소 로직이 원래 값을 복원할 수 있도록 별도 방식으로
originalMinCapacity/originalMaxCapacity를 저장하는 로직을 추가하세요.
- Line 177: The JSX uses non-existent Tailwind classes "text-2md" and "text-md"
(in the owner table dashboard JSX where className="text-2md" and
className="text-md" are set); replace them with standard Tailwind font-size
classes — e.g., change "text-2md" to "text-2xl" (or "text-lg" if you prefer
smaller) and change "text-md" to "text-base" (or "text-sm") so the styles
actually apply in TableDashboard's JSX.
🧹 Nitpick comments (4)
src/components/owner/storeSettings.tsx (1)
5-19: 상태 관리 구조 개선 제안현재 15개의 개별
useState를 사용하고 있는데, 나중에 API 연동 시 상태를 한 번에 전송하거나 초기화하기 번거로울 수 있어요. 하나의 객체로 묶거나useReducer를 사용하면 관리가 더 수월해질 거예요.♻️ 상태 객체로 통합 예시
interface StoreSettingsState { storeName: string; description: string; phone: string; email: string; address: string; openTime: string; closeTime: string; closedDays: string[]; reservationPeriod: string; minGuests: number; maxGuests: number; sameDayBooking: boolean; noShowPolicy: boolean; } const [settings, setSettings] = useState<StoreSettingsState>({ storeName: '맛있는 레스토랑', description: '신선한 재료로 만드는 정성 가득한 음식을 제공합니다.', // ... 나머지 초기값 }); // 업데이트 시 const handleChange = (key: keyof StoreSettingsState, value: StoreSettingsState[typeof key]) => { setSettings(prev => ({ ...prev, [key]: value })); };src/components/owner/menuManagement.tsx (1)
175-221: 카테고리 관리 섹션 분리 리팩터를 추천합니다.
메뉴 목록, 모달 제어, 카테고리 CRUD가 한 컴포넌트에 모여 있어 책임이 큽니다. 유지보수성을 위해 섹션을 분리하는 편이 좋겠습니다.🧩 분리 예시
- {/* 카테고리 관리 섹션*/} - <section className="bg-white border border-gray-100 rounded-lg p-8 shadow-sm"> - ... - </section> + <CategoryManagementSection + categories={categories} + activeCategory={activeCategory} + onAddCategory={handleAddCategory} + onStartEditCategory={startEditCategory} + onSaveCategory={saveCategory} + onDeleteCategory={deleteCategory} + editingCatId={editingCatId} + tempCatLabel={tempCatLabel} + setTempCatLabel={setTempCatLabel} + setEditingCatId={setEditingCatId} + />As per coding guidelines 컴포넌트는 단일 책임, props 타입/네이밍 명확히, 접근성(aria) 체크.
src/components/owner/tableDashboard.tsx (2)
308-308:py-15클래스가 유효한지 확인이 필요해요Tailwind CSS 기본 설정에서 spacing은 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 24... 순서예요.
py-15는 기본 클래스에 없어서py-14또는py-16을 의도하신 것 같아요.✏️ 수정 예시
- <div className="flex flex-col items-center justify-center py-15 px-6 text-center"> + <div className="flex flex-col items-center justify-center py-16 px-6 text-center">
334-343: 영업 시간이 하드코딩되어 있어요
openTime="11:00"과closeTime="22:00"이 고정값으로 되어 있는데, 실제 가게별로 영업 시간이 다를 수 있어요. 향후 API 연동 시 가게 설정에서 가져오도록 수정이 필요할 것 같습니다.목데이터 단계에서는 괜찮지만, TODO 주석을 남겨두면 좋을 것 같아요:
{isBreakModalOpen && ( <BreakTimeModal + // TODO: 가게 설정에서 영업시간 가져오기 openTime="11:00" closeTime="22:00"
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/components/owner/menuManagement.tsx`:
- Line 6: CategoryType is a fixed union ('ALL'|'MAIN'|'SIDE'|'DRINK') but
handleAddCategory generates dynamic IDs like CAT_${Date.now()} and casts cat.id
as CategoryType; change the types so dynamic category IDs are allowed and avoid
unsafe casts: update CategoryType to separate a fixed CategoryKind =
'ALL'|'MAIN'|'SIDE'|'DRINK' and introduce a CategoryId = string (or a branded
type) used for cat.id and activeCategory state, then adjust usages in
handleAddCategory, the cat.id references, and the menu filtering logic (which
currently relies on CategoryType) to compare by CategoryKind where appropriate
and by CategoryId when selecting/activating categories (ensure functions
handleAddCategory, activeCategory state, and the filtering code use the new
types).
- Around line 196-204: The save/cancel buttons next to the editable input lack
accessible labels; update the button elements that call saveCategory(cat.id) and
setEditingCatId(null) (the buttons rendering <Check/> and <X/>) to include
descriptive aria-label attributes (e.g., "Save category" and "Cancel edit") so
screen readers can announce their purpose, and ensure the icons remain
decorative (aria-hidden="true") if you keep them; also consider adding
type="button" to those buttons to avoid accidental form submits.
🧹 Nitpick comments (2)
src/components/owner/menuManagement.tsx (2)
34-36:any타입 사용을 줄이면 타입 안전성이 높아져요.
editingMenu,menuData,menu파라미터에any타입을 사용하고 있는데,MenuItem타입을 import해서 사용하면 자동완성과 컴파일 타임 검증의 이점을 얻을 수 있어요.♻️ 타입 적용 예시
+import { mockMenusByRestaurantId, MenuItem } from '../../mock/menus'; -import { mockMenusByRestaurantId } from '../../mock/menus'; -const [editingMenu, setEditingMenu] = useState<any>(null); +const [editingMenu, setEditingMenu] = useState<MenuItem | null>(null); -const handleFormSubmit = (menuData: any) => { +const handleFormSubmit = (menuData: Partial<MenuItem> & { id: string }) => { -const handleEditClick = (menu: any) => { +const handleEditClick = (menu: MenuItem) => {Also applies to: 53-53
152-152: 키보드/터치 사용자를 위한 버튼 가시성 개선을 고려해주세요.수정/삭제 버튼이 hover 시에만 표시되는데(
opacity-0 group-hover:opacity-100), 키보드로 탐색하는 사용자는 버튼에 포커스가 있어도 보이지 않을 수 있어요.focus-within상태도 추가하면 접근성이 향상됩니다.♿ focus 상태 추가 예시
-<div className="flex gap-2 opacity-0 group-hover:opacity-100 transition-opacity"> +<div className="flex gap-2 opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 transition-opacity">As per coding guidelines 컴포넌트는 단일 책임, props 타입/네이밍 명확히, 접근성(aria) 체크.
dew102938
left a comment
There was a problem hiding this comment.
menuFormModal의 모든 버튼들에도 cursor-pointer 추가해주세요
수고하셨습니다!
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/App.tsx (2)
14-52:⚠️ Potential issue | 🟠 Major헤더 네비게이션 경로와 라우터 정의가 일치하지 않습니다.
src/components/main/Header.tsx에서 "내 가게 관리" 링크가/ownerPage로 설정되어 있는데,src/App.tsx의 라우터에는 해당 경로가 정의되지 않아 404가 발생할 거예요. 현재 OwnerPage로 가는 라우트는/mypage/store/:storeId로 동적 경로입니다.다음 중 하나로 수정이 필요합니다:
- 헤더 네비게이션을 실제 라우트 경로로 통일 (단, 동적 storeId 처리 필요)
- 또는
/ownerPage라우트를 추가하고, 내부에서 실제 가게 정보로 리다이렉트참고: Header.tsx의 주석 "//임시 위치."로 보아 아직 최종 결정이 필요한 부분 같으니, 전체 네비게이션 구조를 함께 확인해주세요.
39-52:⚠️ Potential issue | 🟠 Major
/mypage하위 라우트로 묶여야 하는지 확인 부탁해요.현재
/mypage/*(MyPage)와/mypage/store/...가 형제라서, MyPage가 제공하는 공통 레이아웃/가드/컨텍스트가 의도대로 적용되지 않을 수 있어요. 의도한 UX가 “마이페이지 내부 화면”이라면 하위 라우트로 중첩하는 쪽이 안전합니다.✅ 중첩 라우트 제안 (예시)
- { - path: "/mypage/*", - element: <MyPage />, - errorElement: <NotFound />, - }, - { - path: "/mypage/store/register", //가게 등록 경로 - element:<StoreRegistrationPage />, - errorElement: <NotFound />, - }, - { - path: "/mypage/store/:storeId", - element: <OwnerPage />, - errorElement: <NotFound />, - } + { + path: "/mypage", + element: <MyPage />, + errorElement: <NotFound />, + children: [ + { path: "store/register", element: <StoreRegistrationPage /> }, + { path: "store/:storeId", element: <OwnerPage /> }, + ], + }
🤖 Fix all issues with AI agents
In `@src/components/owner/menuManagement.tsx`:
- Around line 149-168: deleteCategory currently force-casts fallbackCategory to
MenuCategory which can mismatch dynamic IDs; change fallbackCategory to a string
| undefined (e.g., const fallbackCategory = categories.find(...)?.id) and update
any uses to treat menu.category as a string (or convert/validate against
MenuCategory enum) so you don't brute-force-cast invalid IDs; add a guard to
early-return if !fallbackCategory before performing setMenus/setCategories; also
add the missing semicolon after the alert(...) statement so the subsequent
return is properly terminated; update references: deleteCategory,
fallbackCategory, categories, setMenus, setCategories, activeCategory.
In `@src/components/owner/tableDashboard.tsx`:
- Around line 45-50: initialData is captured once via useMemo without reacting
to storeId changes and closedDays is always initialized empty, so switch to
deriving/restoring saved state when the store changes and include saved
closedDays: make getSavedData accept or read the current storeId (or include
storeId in the useMemo dependency) and recompute initialData when storeId
changes, then initialize setConfig, setTableData, setBreakTimes and
setClosedDays from that recomputed initialData (or run an effect that calls
setConfig/setTableData/setBreakTimes/setClosedDays when initialData or storeId
changes); reference initialData, getSavedData, useMemo, config/setConfig,
tableData/setTableData, breakTimes/setBreakTimes and closedDays/setClosedDays.
In `@src/components/owner/tableDetailModal.tsx`:
- Around line 117-126: Add accessible labels to the icon-only buttons in the
tableDetailModal component: update the back button that uses handleBack and
ArrowLeft to include an aria-label (e.g., "Back" or localized "뒤로") and update
the close button that uses onClose and X to include an aria-label (e.g., "Close"
or localized "닫기"); ensure the aria-label strings are descriptive for screen
readers and keep existing className and onClick handlers intact.
- Around line 33-41: The component currently initializes local state closedDays
to a hardcoded ['일'], ignoring the closedDays prop; update the TableDetailModal
component to initialize closedDays state from the incoming closedDays prop
(e.g., useState<string[]>(closedDays)) and/or synchronize when the prop changes
by adding a useEffect that calls setClosedDays(closedDays) when the closedDays
prop updates; reference the closedDays state and setClosedDays setter in
src/components/owner/tableDetailModal.tsx so the calendar reflects the prop
value instead of the hardcoded default.
In `@src/pages/myPage/storePage.tsx`:
- Around line 93-94: 현재 JSX에서 shops.map(...)만 렌더링하고 있어 데이터가 없을 경우(empty) 처리가 빠져
있습니다; shops(또는 MOCK_RESTAURANTS)가 비어 있거나 undefined일 때는 map 호출을 피하고 사용자에게 빈 상태
UI(예: 아이콘, "가게가 없습니다" 메시지, 새로고침/홈으로 이동 버튼)를 보여주도록 조건부 렌더링을 추가하세요 — 예를 들어 확인용
조건으로 shops && shops.length > 0 ? shops.map(...) : <EmptyState /> 형태로 처리하고,
storePage.tsx 내 shops 변수와 MOCK_RESTAURANTS 사용처를 찾아 해당 분기 로직을 넣어 빈 화면 요구사항 및
라우팅/로딩/에러 UX와 충돌하지 않도록 구현하세요.
🧹 Nitpick comments (4)
src/components/owner/menuManagement.tsx (1)
27-27:any[]타입 대신MenuItem[]타입을 사용하세요타입 안전성을 위해
any[]대신 명확한 타입을 사용하는 게 좋아요. mock 데이터에서 이미MenuItem타입을 사용하고 있으니 활용해주세요.♻️ 타입 개선 제안
+import type { MenuItem } from '@/types/menus'; import type { MenuCategory } from '@/types/menus'; - const [menus, setMenus] = useState<any[]>([]); + const [menus, setMenus] = useState<MenuItem[]>([]);그리고 Lines 98, 100, 117의
any타입도 함께 수정해주세요:-const [editingMenu, setEditingMenu] = useState<any>(null); +const [editingMenu, setEditingMenu] = useState<MenuItem | null>(null); -const handleFormSubmit = (menuData: any) => { +const handleFormSubmit = (menuData: MenuItem) => { -const handleEditClick = (menu: any) => { +const handleEditClick = (menu: MenuItem) => {src/mock/restaurants.ts (1)
11-11: 일관된 포맷팅을 유지해주세요
isApproved필드의 포맷팅이 각 항목마다 다르게 되어있어요:
- Line 11:
isApproved : true(콜론 앞 공백)- Line 54:
isApproved: true(정상)- Line 95:
isApproved:false(공백 없음)✨ 일관된 포맷팅
- isApproved : true, + isApproved: true,- isApproved:false, + isApproved: false,Also applies to: 54-54, 95-95
src/pages/myPage/storePage.tsx (2)
15-37: 통계 데이터가 하드코딩되어 있어요통계 값들이 실제
MOCK_RESTAURANTS데이터와 연동되지 않고 하드코딩되어 있어요. 데이터가 변경되면 통계가 불일치하게 됩니다.♻️ 동적 계산 제안
+ const totalShops = MOCK_RESTAURANTS.length; + const avgRating = ( + MOCK_RESTAURANTS.reduce((sum, r) => sum + r.rating, 0) / MOCK_RESTAURANTS.length + ).toFixed(1); const stats = [ { label: "총 가게 수", - value: "3개", + value: `${totalShops}개`, icon: <Store size={20} />, bgColor: "bg-blue-50", iconColor: "text-blue-500", }, { label: "총 예약 수", - value: "2126건", + value: "-", // API 연동 후 실제 데이터로 대체 icon: <Calendar size={20} />, bgColor: "bg-indigo-50", iconColor: "text-indigo-500", }, { label: "평균 평점", - value: "4.8", + value: avgRating, icon: <Star size={20} />, bgColor: "bg-green-50", iconColor: "text-yellow-500", }, ];
135-135: 연산자 주변 공백이 누락되었어요
shop.isApproved&&에서&&연산자 앞에 공백이 없어요. 가독성을 위해 공백을 추가해주세요.✨ 포맷팅 수정
- {shop.isApproved&& ( + {shop.isApproved && (
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/components/owner/tableDetailModal.tsx`:
- Around line 84-87: confirmCapacity currently calls
onUpdateCapacity(Number(tempMin), Number(tempMax)) without validating
tempMin/tempMax, allowing negative values or min>max; add input validation to
ensure both values are numbers, non-negative and min <= max before calling
onUpdateCapacity (and either disable the confirm button when invalid or
short-circuit in confirmCapacity to avoid calling onUpdateCapacity), update UI
state via setIsEditing only on success, and apply the same validation/guard
logic for the similar block around lines 150-159; reference tempMin, tempMax,
confirmCapacity, onUpdateCapacity and setIsEditing when making the changes.
- Around line 205-207: The icon-only month navigation buttons lack accessible
names; update the two buttons that call changeMonth(-1) and changeMonth(1) (the
elements rendering ChevronLeft and ChevronRight) to include appropriate
aria-label attributes (e.g., "Previous month" and "Next month" or Korean
equivalents) so screen readers can convey their purpose; keep existing onClick
and className props unchanged and ensure labels are concise and localized if the
component uses Korean UI.
- Around line 267-276: The slot item currently uses a clickable div which blocks
keyboard/screenreader interaction; replace it with a semantic interactive
element by converting the div into a <button> (or ensure role="button" +
tabIndex=0) and add keyboard handlers: use onClick and onKeyDown that toggle via
setSlots when Enter/Space are pressed, bail out early when isBreak is true, and
ensure the element has type="button" and aria-pressed={isAvailable} (or
aria-disabled when isBreak) so assistive tech sees state; also preserve the
existing className logic (use disabled styling when isBreak) and keep the same
setSlots mapping that checks s.id === slot.id to flip isAvailable.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/pages/myPage/storePage.tsx`:
- Around line 38-50: The mapping that builds shops uses
restaurant.rating.toFixed(1) which can throw if rating is null/undefined; update
the shops creation (the map over MOCK_RESTAURANTS that produces shops) to
defensively handle missing ratings by checking restaurant.rating for
null/undefined and using a safe fallback (e.g., 0 or "-" or undefined) before
calling toFixed, or use Number.isFinite/typeof checks and only call toFixed when
it's a valid number; ensure the property name rating on the resulting object
always receives a string (e.g., fallbackValue.toString() or "-") so consumers
won’t get runtime errors.
🧹 Nitpick comments (3)
src/pages/myPage/storePage.tsx (3)
14-36: 통계 값이 하드코딩되어 있어 실제 데이터와 불일치할 수 있어요
shops데이터는MOCK_RESTAURANTS에서 동적으로 가져오는데,stats의 "총 가게 수", "총 예약 수", "평균 평점"은 하드코딩되어 있어요. 데이터가 변경되면 UI와 실제 값이 달라질 수 있어요.♻️ 동적 계산으로 개선하는 예시
+ const totalShops = MOCK_RESTAURANTS.length; + const avgRating = MOCK_RESTAURANTS.length > 0 + ? (MOCK_RESTAURANTS.reduce((sum, r) => sum + (r.rating ?? 0), 0) / MOCK_RESTAURANTS.length).toFixed(1) + : "0.0"; const stats = [ { label: "총 가게 수", - value: "3개", + value: `${totalShops}개`, icon: <Store size={20} />, bgColor: "bg-blue-50", iconColor: "text-blue-500", }, { label: "총 예약 수", value: "2126건", // TODO: API 연동 시 실제 예약 수로 교체 icon: <Calendar size={20} />, bgColor: "bg-indigo-50", iconColor: "text-indigo-500", }, { label: "평균 평점", - value: "4.8", + value: avgRating, icon: <Star size={20} />, bgColor: "bg-green-50", iconColor: "text-yellow-500", }, ];
137-137: 연산자 앞뒤 공백이 빠져 있어요가독성을 위해
&&연산자 앞에 공백을 추가해 주세요.✨ 포매팅 수정
- {shop.isApproved&& ( + {shop.isApproved && (
189-191: 버튼에 접근성 속성이 누락되었어요프리미엄 플랜 버튼이 실제 동작 없이 렌더링되고 있어요. 현재 기능이 구현되지 않았다면
disabled상태로 만들거나, 향후 구현 예정임을 사용자에게 알려주는 게 좋겠어요. 또한type속성을 명시하면 폼 내부에서의 의도치 않은 동작을 방지할 수 있어요.♿ 접근성 개선 제안
- <button className="cursor-pointer mt-6 w-full sm:w-auto px-8 py-4 rounded-lg bg-blue-500 font-bold text-white hover:bg-blue-600 shadow-sm transition"> + <button + type="button" + className="cursor-pointer mt-6 w-full sm:w-auto px-8 py-4 rounded-lg bg-blue-500 font-bold text-white hover:bg-blue-600 shadow-sm transition" + aria-label="프리미엄 플랜 상세 정보 보기" + > 프리미엄 플랜 알아보기 </button>
…feat/ownerPage feat/ownerPage
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/components/owner/tableDashboard.tsx`:
- Around line 34-47: getSavedData currently returns null immediately when
storeId is falsy, but temp state is being saved to STORAGE_KEY even without a
storeId so refreshes don't restore it; update getSavedData (and ensure it uses
STORAGE_KEY) to not bail out on !storeId — read
localStorage.getItem(STORAGE_KEY), JSON.parse it inside the try/catch and return
the parsed value (or null on parse error) so temporary dashboard state is
restored after reload; if you intended per-store persistence instead, instead
compose the storage key with storeId before reading/writing (e.g.,
`${STORAGE_KEY}-${storeId}`) and update both save and get logic accordingly.
🧹 Nitpick comments (2)
src/components/owner/tableDashboard.tsx (2)
84-85: 디버그 로그는 배포 전 제거를 권장해요저장 로직마다 콘솔 로그가 발생하면 노이즈가 커집니다.
🧹 수정 예시
- console.log("저장됨:", data); // 디버깅용
23-405: TableDashboard가 너무 많은 책임을 한 컴포넌트에 담고 있어요로컬스토리지 영속화, 모달 상태, 그리드 렌더링, 요약 카드/헤더까지 한 파일에 묶여 있어 유지보수가 어려워질 수 있습니다.
Header,BreakTimeList,TableGrid,Modals등으로 분리하고, 저장 로직은 커스텀 훅으로 추출하는 방향을 추천합니다.
As per coding guidelines, 컴포넌트는 단일 책임, props 타입/네이밍 명확히, 접근성(aria) 체크.
|
메인에서 header, hero의 |
💡 개요
내 가게 관리 UI 구현
🔢 관련 이슈 링크
💻 작업내용
📌 변경사항PR
🤔 추가 논의하고 싶은 내용
✅ 체크리스트
Summary by CodeRabbit
New Features
Chores
Style/UX