fix: add shift management (#41)
Release package / release (push) Successful in 10m13s

Co-authored-by: TakahashiNg <83152264+TakahashiNguyen@users.noreply.github.com>
Reviewed-on: #41
This commit was merged in pull request #41.
This commit is contained in:
2026-05-14 03:13:06 +00:00
parent dfcb1b09c0
commit 9ed4889310
18 changed files with 305 additions and 422 deletions
+19 -44
View File
@@ -1,35 +1,9 @@
"use client";
import type { ShiftSlot } from "@/lib/types";
import type { ShiftEntity } from "@/lib/types";
import type { ShiftCardProps } from "./ShiftCard.types";
const STATUS_STYLES: Record<
ShiftSlot["status"],
{ bg: string; text: string; label: string }
> = {
available: {
bg: "bg-blue-50 border-blue-200",
text: "text-blue-700",
label: "Còn trống",
},
registered: {
bg: "bg-blue-100 border-blue-400",
text: "text-blue-900",
label: "Đã đăng ký",
},
approved_leave: {
bg: "bg-purple-50 border-purple-300",
text: "text-purple-700",
label: "Nghỉ phép",
},
absent: {
bg: "bg-red-50 border-red-300",
text: "text-red-700",
label: "Vắng mặt",
},
};
function formatWage(wage: number): string {
if (wage >= 1000) {
return `${(wage / 1000).toFixed(0)}k`;
@@ -42,20 +16,20 @@ export default function ShiftCard({
compact = false,
onClick,
}: ShiftCardProps) {
const style = STATUS_STYLES[shift.status];
console.log(shift);
if (compact) {
return (
<button
type="button"
onClick={() => onClick?.(shift)}
className={`w-full cursor-pointer rounded-lg border px-2 py-1.5 text-left text-xs transition-shadow hover:shadow-sm ${style.bg} ${style.text}`}
className={`w-full cursor-pointer rounded-lg border px-2 py-1.5 text-left text-xs transition-shadow hover:shadow-sm `}
>
<p className="font-semibold">
{shift.startTime} {shift.endTime}
</p>
<p className="mt-0.5 opacity-75">
{shift.durationHours}h · {formatWage(shift.wage)}
{}h · {formatWage(shift.wage)}
</p>
</button>
);
@@ -65,7 +39,7 @@ export default function ShiftCard({
<button
type="button"
onClick={() => onClick?.(shift)}
className={`w-full cursor-pointer rounded-xl border p-3 text-left transition-shadow hover:shadow-md ${style.bg} ${style.text}`}
className={`w-full cursor-pointer rounded-xl border p-3 text-left transition-shadow hover:shadow-md `}
>
<div className="flex items-start justify-between">
<div>
@@ -73,25 +47,26 @@ export default function ShiftCard({
{shift.startTime} {shift.endTime}
</p>
<p className="mt-1 text-xs opacity-75">
{shift.durationHours}h · {formatWage(shift.wage)} VND
{}h · {formatWage(shift.wage)} VND
</p>
</div>
<span
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold ${
shift.status === "available"
? "bg-blue-200 text-blue-800"
: shift.status === "registered"
? "bg-blue-300 text-blue-900"
: shift.status === "approved_leave"
? "bg-purple-200 text-purple-800"
: "bg-red-200 text-red-800"
// shift.status === "available"
// ? "bg-blue-200 text-blue-800"
// : shift.status === "registered"
// ? "bg-blue-300 text-blue-900"
// : shift.status === "approved_leave"
// ? "bg-purple-200 text-purple-800"
// : "bg-red-200 text-red-800"
""
}`}
>
{style.label}
{/* {style.label} */}
</span>
</div>
{shift.registeredStaff.length > 0 && (
{(shift.registeredStaff && shift.registeredStaff.length > 0) && (
<div className="mt-2 border-t border-current/10 pt-2">
<p className="text-[10px] font-medium tracking-wide uppercase opacity-60">
Nhân viên ({shift.registeredStaff.length}/{shift.maxStaff})
@@ -102,18 +77,18 @@ export default function ShiftCard({
key={s.id}
className="rounded-full bg-white/60 px-2 py-0.5 text-[10px] font-medium"
>
{s.name}
{s.staffId}
</span>
))}
</div>
</div>
)}
{shift.status === "available" && shift.registeredStaff.length === 0 && (
{/* {shift.status === "available" && shift.registeredStaff.length === 0 && (
<p className="mt-2 text-[10px] italic opacity-50">
{shift.maxStaff} vị trí còn trống
</p>
)}
)} */}
</button>
);
}
@@ -1,7 +1,7 @@
import type { ShiftSlot } from "@/lib/types";
import type { ShiftEntity } from "@/lib/types";
export interface ShiftCardProps {
shift: ShiftSlot;
shift: ShiftEntity;
compact?: boolean;
onClick?: (shift: ShiftSlot) => void;
onClick?: (shift: ShiftEntity) => void;
}
+1 -1
View File
@@ -15,7 +15,7 @@ export { ReviewModal } from "./modals";
export type { ReviewModalProps, ConfirmModalProps } from "./modals";
// Shop Grid
export { ShopGrid } from "./shop-grid";
// export { ShopGrid } from "./shop-grid";
export type { ShopGridProps } from "./shop-grid";
// Manager
@@ -44,9 +44,7 @@ export default function MobileShiftView({
onShiftClick,
}: MobileShiftViewProps) {
const { currentDate, shifts, goToNextMonth, goToPrevMonth } = useShift();
const [selectedDate, setSelectedDate] = useState<string>(
formatDateISO(new Date(2026, 3, 10)),
);
const [selectedDate, setSelectedDate] = useState<Date>(new Date(2026, 3, 10));
const calendarDays = useMemo(() => {
const year = currentDate.getFullYear();
@@ -67,21 +65,20 @@ export default function MobileShiftView({
}, [currentDate]);
const getDotColors = (date: Date): string[] => {
const dateStr = formatDateISO(date);
const dayShifts = shifts.filter((s) => s.date === dateStr);
const dayShifts = shifts.filter((s) => s.date.getDate() === date.getDate());
const dots: string[] = [];
if (dayShifts.some((s) => s.status === "available"))
dots.push("bg-amber-400");
if (dayShifts.some((s) => s.status === "registered"))
dots.push("bg-green-500");
if (dayShifts.some((s) => s.status === "approved_leave"))
dots.push("bg-purple-400");
if (dayShifts.some((s) => s.status === "absent")) dots.push("bg-red-400");
// if (dayShifts.some((s) => s.status === "available"))
// dots.push("bg-amber-400");
// if (dayShifts.some((s) => s.status === "registered"))
// dots.push("bg-green-500");
// if (dayShifts.some((s) => s.status === "approved_leave"))
// dots.push("bg-purple-400");
// if (dayShifts.some((s) => s.status === "absent")) dots.push("bg-red-400");
return dots;
};
const selectedShifts = useMemo(() => {
return shifts.filter((s) => s.date === selectedDate);
return shifts.filter((s) => s.date.getDate() === selectedDate.getDate());
}, [shifts, selectedDate]);
const selectedDateObj = new Date(selectedDate + "T00:00:00");
@@ -133,16 +130,15 @@ export default function MobileShiftView({
return <div key={`empty-${i}`} className="p-1" />;
}
const dateStr = formatDateISO(date);
const today = isToday(date);
const selected = dateStr === selectedDate;
const selected = date === selectedDate;
const dots = getDotColors(date);
return (
<button
type="button"
key={i}
onClick={() => setSelectedDate(dateStr)}
onClick={() => setSelectedDate(date)}
className={`flex cursor-pointer flex-col items-center border-none bg-transparent p-1 transition ${
selected ? "rounded-lg bg-(--color-primary)/10" : ""
}`}
@@ -218,9 +214,7 @@ export default function MobileShiftView({
) : (
<div className="space-y-3">
{DEPARTMENTS.map((dept) => {
const deptShifts = selectedShifts.filter(
(s) => s.department === dept.id,
);
const deptShifts = selectedShifts;
if (deptShifts.length === 0) return null;
return (
<div key={dept.id}>
@@ -7,7 +7,7 @@ import type { MonthlyCalendarProps } from "./ShiftSchedule.types";
const DAY_HEADERS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
function formatDateISO(d: Date): string {
export function formatDateISO(d: Date): string {
const y = d.getFullYear();
const m = (d.getMonth() + 1).toString().padStart(2, "0");
const day = d.getDate().toString().padStart(2, "0");
@@ -60,15 +60,14 @@ export default function MonthlyCalendar({
}, [currentDate]);
const getShiftSummary = (date: Date) => {
const dateStr = formatDateISO(date);
const dayShifts = shifts.filter((s) => s.date === dateStr);
const available = dayShifts.filter((s) => s.status === "available").length;
const registered = dayShifts.filter(
(s) => s.status === "registered",
).length;
const leave = dayShifts.filter((s) => s.status === "approved_leave").length;
const absent = dayShifts.filter((s) => s.status === "absent").length;
return { total: dayShifts.length, available, registered, leave, absent };
const dayShifts = shifts.filter((s) => s.date.getDate() === date.getDate());
// const available = dayShifts.filter((s) => s.status === "available").length;
// const registered = dayShifts.filter(
// (s) => s.status === "registered",
// ).length;
// const leave = dayShifts.filter((s) => s.status === "approved_leave").length;
// const absent = dayShifts.filter((s) => s.status === "absent").length;
return { total: dayShifts.length };
};
return (
@@ -104,7 +103,7 @@ export default function MonthlyCalendar({
<button
type="button"
key={i}
onClick={() => onDateSelect?.(formatDateISO(date))}
onClick={() => onDateSelect?.(date)}
className={`min-h-25 cursor-pointer border-r border-b border-(--color-border-light) bg-transparent p-2 text-left transition hover:bg-gray-50 ${
today ? "bg-(--color-primary)/5" : ""
}`}
@@ -119,7 +118,7 @@ export default function MonthlyCalendar({
{date.getDate()}
</span>
{summary.total > 0 && (
{/* {summary.total > 0 && (
<div className="mt-2 space-y-1">
{summary.available > 0 && (
<div className="flex items-center gap-1.5">
@@ -154,7 +153,7 @@ export default function MonthlyCalendar({
</div>
)}
</div>
)}
)} */}
</button>
);
})}
@@ -4,6 +4,7 @@ import { DEPARTMENTS } from "@/lib/constants";
import { useShift } from "@/lib/shift-context";
import { useEffect, useState } from "react";
import { formatDateISO } from "./MonthlyCalendar";
import type { ShiftCreateModalProps } from "./ShiftSchedule.types";
export default function ShiftCreateModal({
@@ -13,7 +14,7 @@ export default function ShiftCreateModal({
}: ShiftCreateModalProps) {
const { createShift } = useShift();
const [date, setDate] = useState(defaultDate ?? "2026-04-10");
const [date, setDate] = useState<Date>(new Date(defaultDate ?? "2026-04-10"));
const [startTime, setStartTime] = useState("08:00");
const [endTime, setEndTime] = useState("12:00");
const [department, setDepartment] = useState("bar");
@@ -23,7 +24,7 @@ export default function ShiftCreateModal({
useEffect(() => {
if (isOpen) {
setDate(defaultDate ?? "2026-04-10");
setDate(new Date(defaultDate ?? "2026-04-10"));
}
}, [defaultDate, isOpen]);
@@ -49,18 +50,12 @@ export default function ShiftCreateModal({
return;
}
const durationHours = (endMinutes - startMinutes) / 60;
createShift({
date,
startTime,
endTime,
durationHours,
wage,
department,
maxStaff,
registeredStaff: [],
status: "available",
});
onClose();
@@ -103,8 +98,8 @@ export default function ShiftCreateModal({
<input
title="Date"
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
value={formatDateISO(date)}
onChange={(e) => setDate(new Date(e.target.value))}
className="text-foreground w-full rounded-xl border border-(--color-border-light) px-3 py-2.5 text-sm transition outline-none focus:border-(--color-primary) focus:ring-1 focus:ring-(--color-primary)"
/>
</div>
@@ -19,12 +19,12 @@ export default function ShiftDetailModal({
if (!isOpen || !shift) return null;
const dept = DEPARTMENTS.find((d) => d.id === shift.department);
// const dept = DEPARTMENTS.find((d) => d.id === shift.department);
const isManager = user?.role === "manager";
const isRegistered = user
? shift.registeredStaff.some((s) => s.id === user.id)
? shift.registeredStaff!.some((s) => s.id === user.id)
: false;
const isFull = shift.registeredStaff.length >= shift.maxStaff;
const isFull = shift.registeredStaff!.length >= shift.maxStaff;
const handleRegister = () => {
if (!user) return;
@@ -47,7 +47,7 @@ export default function ShiftDetailModal({
setTimeout(onClose, 1200);
};
const handleManagerUnregister = (staffId: number) => {
const handleManagerUnregister = (staffId: string) => {
unregisterShift(shift.id, staffId);
setSuccess("Đã xóa nhân viên khỏi ca.");
};
@@ -81,12 +81,12 @@ export default function ShiftDetailModal({
{/* Header */}
<div className="flex items-center justify-between border-b border-(--color-border-light) px-5 py-4">
<div className="flex items-center gap-3">
{dept && <i className={`${dept.icon} text-(--color-primary)`}></i>}
{/* {dept && <i className={`${dept.icon} text-(--color-primary)`}></i>} */}
<div>
<h2 className="text-foreground text-base font-bold">
Chi tiết ca làm
</h2>
<p className="text-xs text-(--color-text-muted)">{dept?.name}</p>
{/* <p className="text-xs text-(--color-text-muted)">{dept?.name}</p> */}
</div>
</div>
<button
@@ -102,13 +102,13 @@ export default function ShiftDetailModal({
{/* Body */}
<div className="space-y-4 px-5 py-4">
{/* Status badge */}
<div className="flex items-center gap-2">
{/* <div className="flex items-center gap-2">
<span
className={`rounded-full px-3 py-1 text-xs font-semibold ${statusColor[shift.status]}`}
>
{statusLabel[shift.status]}
</span>
</div>
</div> */}
{/* Shift info grid */}
<div className="grid grid-cols-2 gap-3">
@@ -141,7 +141,7 @@ export default function ShiftDetailModal({
Thời lượng
</p>
<p className="text-foreground mt-1 text-sm font-bold">
{shift.durationHours} giờ
{""} giờ
</p>
</div>
<div className="rounded-xl bg-gray-50 p-3">
@@ -157,16 +157,16 @@ export default function ShiftDetailModal({
{/* Registered staff */}
<div>
<p className="mb-2 text-xs font-semibold text-(--color-text-secondary)">
Nhân viên đã đăng ({shift.registeredStaff.length}/
Nhân viên đã đăng ({shift.registeredStaff!.length}/
{shift.maxStaff})
</p>
{shift.registeredStaff.length === 0 ? (
{(shift.registeredStaff && shift.registeredStaff.length === 0) ? (
<p className="text-xs text-(--color-text-muted) italic">
Chưa ai đăng
</p>
) : (
<div className="space-y-2">
{shift.registeredStaff.map((staff) => (
{shift.registeredStaff!.map((staff) => (
<div
key={staff.id}
className="flex items-center justify-between rounded-xl bg-gray-50 px-3 py-2"
@@ -176,7 +176,7 @@ export default function ShiftDetailModal({
<i className="fa-solid fa-user text-[10px] text-(--color-primary)"></i>
</div>
<span className="text-foreground text-sm font-medium">
{staff.name}
{staff.staffId}
</span>
</div>
{isManager && (
@@ -212,7 +212,7 @@ export default function ShiftDetailModal({
{/* Footer actions */}
<div className="flex gap-2 border-t border-(--color-border-light) px-5 py-4">
{!isRegistered &&
{/* {!isRegistered &&
!isFull &&
shift.status !== "approved_leave" &&
shift.status !== "absent" && (
@@ -224,7 +224,7 @@ export default function ShiftDetailModal({
<i className="fa-solid fa-calendar-plus mr-2"></i>
Đăng ký ca
</button>
)}
)} */}
{isRegistered && (
<button
type="button"
@@ -1,22 +1,22 @@
import type { ShiftSlot } from "@/lib/types";
import type { ShiftEntity } from "@/lib/types";
export interface WeeklyScheduleProps {
onShiftClick: (shift: ShiftSlot) => void;
onCreateShift?: (date: string) => void;
onShiftClick: (shift: ShiftEntity) => void;
onCreateShift?: (date: Date) => void;
mobileCalendarHeader?: boolean;
}
export interface MonthlyCalendarProps {
onShiftClick: (shift: ShiftSlot) => void;
onDateSelect?: (date: string) => void;
onShiftClick: (shift: ShiftEntity) => void;
onDateSelect?: (date: Date) => void;
}
export interface MobileShiftViewProps {
onShiftClick: (shift: ShiftSlot) => void;
onShiftClick: (shift: ShiftEntity) => void;
}
export interface ShiftDetailModalProps {
shift: ShiftSlot | null;
shift: ShiftEntity | null;
isOpen: boolean;
onClose: () => void;
}
@@ -24,5 +24,5 @@ export interface ShiftDetailModalProps {
export interface ShiftCreateModalProps {
isOpen: boolean;
onClose: () => void;
defaultDate?: string;
defaultDate?: Date;
}
@@ -37,7 +37,7 @@ function formatDateISO(d: Date): string {
}
function isToday(d: Date): boolean {
const today = new Date(2026, 3, 10);
const today = new Date(Date.now());
return (
d.getDate() === today.getDate() &&
d.getMonth() === today.getMonth() &&
@@ -59,36 +59,32 @@ export default function WeeklySchedule({
} = useShift();
const weekDates = getWeekDates();
const [selectedDate, setSelectedDate] = useState<string>(
formatDateISO(weekDates[0] ?? currentDate),
const [selectedDate, setSelectedDate] = useState<Date>(
weekDates[0] ?? currentDate,
);
const statusDotsByDate = useMemo(() => {
const map: Record<string, string[]> = {};
weekDates.forEach((date) => {
const dateStr = formatDateISO(date);
const dayShifts = getShiftsForDate(dateStr);
const dots: string[] = [];
if (dayShifts.some((s) => s.status === "available"))
dots.push("bg-sky-300");
if (dayShifts.some((s) => s.status === "registered"))
dots.push("bg-blue-600");
if (dayShifts.some((s) => s.status === "approved_leave"))
dots.push("bg-purple-400");
if (dayShifts.some((s) => s.status === "absent"))
dots.push("bg-rose-400");
map[dateStr] = dots.slice(0, 3);
// if (dayShifts.some((s) => s.status === "available"))
// dots.push("bg-sky-300");
// if (dayShifts.some((s) => s.status === "registered"))
// dots.push("bg-blue-600");
// if (dayShifts.some((s) => s.status === "approved_leave"))
// dots.push("bg-purple-400");
// if (dayShifts.some((s) => s.status === "absent"))
// dots.push("bg-rose-400");
map[date.toISOString()] = dots.slice(0, 3);
});
return map;
}, [weekDates, getShiftsForDate]);
const selectedDateObj = useMemo(() => {
const inWeek = weekDates.find((d) => formatDateISO(d) === selectedDate);
const selectedDateStr = useMemo(() => {
const inWeek = weekDates.find((d) => d === selectedDate);
return inWeek ?? weekDates[0] ?? currentDate;
}, [selectedDate, weekDates, currentDate]);
const selectedDateStr = formatDateISO(selectedDateObj);
const renderMobileDayView = mobileCalendarHeader && (
<div className="space-y-3">
<div className="rounded-xl border border-(--color-border-light) bg-white p-3">
@@ -116,14 +112,13 @@ export default function WeeklySchedule({
<div className="grid grid-cols-7 gap-1">
{weekDates.map((date, i) => {
const dateStr = formatDateISO(date);
const active = dateStr === selectedDateStr;
const dots = statusDotsByDate[dateStr] ?? [];
const active = date === selectedDateStr;
const dots = statusDotsByDate[date.toISOString()] ?? [];
return (
<button
key={i}
type="button"
onClick={() => setSelectedDate(dateStr)}
onClick={() => setSelectedDate(date)}
className={`flex cursor-pointer flex-col items-center gap-1 rounded-xl border-none p-1 ${
active ? "bg-(--color-primary)/10" : "bg-transparent"
}`}
@@ -135,14 +130,14 @@ export default function WeeklySchedule({
</span>
<span
className={`flex h-9 w-9 items-center justify-center rounded-full text-sm font-semibold ${
active || isToday(date)
active || isToday(new Date(date))
? "bg-indigo-500 text-white"
: i >= 5
? "text-pink-500"
: "text-sky-500"
}`}
>
{date.getDate()}
{new Date(date).getDate()}
</span>
<div className="flex min-h-2 items-center gap-0.5">
{dots.map((dot, idx) => (
@@ -160,9 +155,10 @@ export default function WeeklySchedule({
<div className="space-y-3">
{DEPARTMENTS.map((dept) => {
const deptShifts = getShiftsForDate(selectedDateStr).filter(
(s) => s.department === dept.id,
);
const deptShifts = getShiftsForDate(new Date(selectedDateStr));
// .filter(
// (s) => s.department === dept.id,
// );
if (deptShifts.length === 0 && !onCreateShift) return null;
return (
<div
@@ -219,14 +215,14 @@ export default function WeeklySchedule({
<th
key={i}
className={`border-r border-b border-(--color-border-light) px-2 py-3 text-center text-xs ${
isToday(date)
isToday(new Date(date))
? "bg-(--color-primary)/10 font-bold text-(--color-primary)"
: "bg-gray-50 font-semibold text-(--color-text-muted)"
}`}
>
<span className="block uppercase">{DAY_LABELS[i]}</span>
<span className="mt-0.5 block text-[11px] font-normal">
{formatDateShort(date)}
{date.toISOString()}
</span>
</th>
))}
@@ -246,15 +242,16 @@ export default function WeeklySchedule({
</div>
</td>
{weekDates.map((date, i) => {
const dateStr = formatDateISO(date);
const shifts = getShiftsForDate(dateStr).filter(
(s) => s.department === dept.id,
);
const dateStr = date;
const shifts = getShiftsForDate(new Date(date));
// .filter(
// (s) => s.department === dept.id,
// );
return (
<td
key={i}
className={`border-r border-b border-(--color-border-light) p-1.5 align-top ${
isToday(date) ? "bg-(--color-primary)/5" : ""
isToday(new Date(date)) ? "bg-(--color-primary)/5" : ""
}`}
>
<div className="flex min-h-20 flex-col gap-1">
+40 -40
View File
@@ -1,46 +1,46 @@
"use client";
import { ShopCard } from "@/components/molecules/cards";
import { MOCK_SHOPS } from "@/lib/constants";
// import { ShopCard } from "@/components/molecules/cards";
// import { MOCK_SHOPS } from "@/lib/constants";
import type { ShopGridProps } from "./ShopGrid.types";
// import type { ShopGridProps } from "./ShopGrid.types";
export default function ShopGrid({
searchName = "",
searchAddress = "",
}: ShopGridProps) {
const filtered = MOCK_SHOPS.filter((shop) => {
const matchesName =
searchName.trim() === "" ||
shop.name.toLowerCase().includes(searchName.toLowerCase());
const matchesAddress =
searchAddress.trim() === "" ||
shop.address.toLowerCase().includes(searchAddress.toLowerCase());
return matchesName && matchesAddress;
});
// export default function ShopGrid({
// searchName = "",
// searchAddress = "",
// }: ShopGridProps) {
// const filtered = MOCK_SHOPS.filter((shop) => {
// const matchesName =
// searchName.trim() === "" ||
// shop.name.toLowerCase().includes(searchName.toLowerCase());
// const matchesAddress =
// searchAddress.trim() === "" ||
// shop.address.toLowerCase().includes(searchAddress.toLowerCase());
// return matchesName && matchesAddress;
// });
if (filtered.length === 0) {
return (
<div className="flex flex-col items-center justify-center gap-4 py-24 text-(--color-text-muted)">
<i className="fa-solid fa-store text-5xl opacity-30"></i>
<p className="text-base font-medium">
No shops found matching your search
</p>
</div>
);
}
// if (filtered.length === 0) {
// return (
// <div className="flex flex-col items-center justify-center gap-4 py-24 text-(--color-text-muted)">
// <i className="fa-solid fa-store text-5xl opacity-30"></i>
// <p className="text-base font-medium">
// No shops found matching your search
// </p>
// </div>
// );
// }
return (
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{filtered.map((shop) => (
<ShopCard
key={shop.id}
id={shop.id}
name={shop.name}
address={shop.address}
image={shop.image}
/>
))}
</div>
);
}
// return (
// <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
// {filtered.map((shop) => (
// <ShopCard
// key={shop.id}
// id={shop.id}
// name={shop.name}
// address={shop.address}
// image={shop.image}
// />
// ))}
// </div>
// );
// }
+1 -1
View File
@@ -1,2 +1,2 @@
export { default as ShopGrid } from "./ShopGrid";
// export { default as ShopGrid } from "./ShopGrid";
export type { ShopGridProps } from "./ShopGrid.types";