본문으로 건너뛰기
언어

Rate gridhospitality

Room type by date price grid with metric rows, editable base price, foldable channel rows, pending edits, minimum guardrail, month loading and bulk edit by date range.

설치

pnpm
pnpm dlx shadcn@4.23.0 add @uikit/rate-grid

의존 아이템buttonhospitality-metricsinputlocalestyle

Default

rate-grid-demo.tsx
import * as React from "react";import type { DateOnly } from "@plott/webkit-time";import { RateGrid, type RateChannel, type RateDay, type RateRoomType } from "@/components/ui/rate-grid";import type { RateChange } from "@/components/ui/rate-grid-model";
export const demoRoomTypes: RateRoomType[] = [  { id: "std", label: "Standard", minPriceMinor: 8000 },  { id: "ste", label: "Suite", minPriceMinor: 20000 },];
export const demoChannels: RateChannel[] = [  { id: "direct", label: "Direct" },  { id: "ota", label: "Partner site" },];
export function demoDay(roomTypeId: string, date: DateOnly): RateDay {  const day = Number(date.slice(8, 10));  const base = roomTypeId === "std" ? 12000 : 30000;  return {    priceMinor: base + day * 100,    sold: day % 5,    available: 5,    revenueMinor: (day % 5) * (base + day * 100),    channels: { direct: base + day * 100, ota: base + day * 120 },  };}
export default function RateGridDemo() {  const [overrides, setOverrides] = React.useState<Record<string, number>>({});  const getDay = (roomTypeId: string, date: DateOnly): RateDay => {    const day = demoDay(roomTypeId, date);    const key = `${roomTypeId}|${date}`;    return Object.hasOwn(overrides, key) ? { ...day, priceMinor: overrides[key] } : day;  };  const commit = async (changes: readonly RateChange[]) => {    setOverrides((prev) => ({ ...prev, ...Object.fromEntries(changes.map((c) => [`${c.roomTypeId}|${c.date}`, c.priceMinor])) }));  };  return (    <RateGrid      roomTypes={demoRoomTypes}      channels={demoChannels}      getDay={getDay}      initialMonth={"2026-01-01" as DateOnly}      currency="USD"      onLoadMonth={() => {}}      onCommit={commit}    />  );}