Mundarija (39)
- Bu darsda
- 1. Nega bu kerak?
- 2. Oy setkasi: kalendar ichida nima bor
- 2.1 Uchta savol
- 2.2 O'quv modeli: setkani hisoblash
- 2.3 Sanani satr qilib saqlash
- 3. Sana tanlash: tugmalar, band kunlar va klaviatura
- 3.1 Har kun — tugma
- 3.2 Klaviatura: 42 ta Tab emas
- 4. Sanalar oralig'i: kelish — ketish
- 4.1 Oraliq qanday tanlanadi
- 4.2 Oxiri ichidami yoki tashqarisidami?
- 5. Vaqt slotlari: bo'sh stol bormi?
- 5.1 Slot nima
- 5.2 Kalendarning "hafta ko'rinishi"
- 6. Vaqt zonasi: 19:00 kimning soati?
- 6.1 Bron — lahza
- 6.2 Tuzoq: brauzer zonasi
- 7. Kutubxonalar
- 7.1 Ikki xil vazifa
- 7.2 react-day-picker
- 7.3 react-big-calendar
- 7.4 FullCalendar 7
- 7.5 Schedule-X
- 8. Biz sinab ko'rdik: hajm, CSP va bog'liqlik to'qnashuvi
- 8.1 Hajm
- 8.2 CSP va Trusted Types
- 8.3 Ikki kalendar bitta loyihada sig'madi
- 8.4 Nega vazifalar da kalendar yo'q
- 9. Hujumchi nigohi
- 10. Ko'p uchraydigan xatolar
- 11. Mashqlar
- 1-mashq (oson): bo'sh joylarni to'ldiring
- 2-mashq (o'rta): oyni almashtirish
- 3-mashq (qiyin): stol kishi soniga qarab
- 4-mashq: tajriba qadami (ixtiyoriy)
- 12. Real ishda
- Xulosa
- Manbalar
React kalendar va bron qilish UI: oy setkasi, sanalar oralig'i, vaqt slotlari va kutubxonalar
Qisqacha: Kalendar — sanalar jadvali: oy 7 ustunli setkaga joylanadi, har kun tugma bo'ladi, band kunlar o'chiriladi. Bron esa ikki qadam: sana (yoki sanalar oralig'i), keyin vaqt sloti — bo'sh stol bor-yo'qligi yarim ochiq oraliqlar
[boshi, oxiri)bilan tekshiriladi. Vaqt restoran zonasida ko'rsatiladi, serverga lahza (UTC) bo'lib boradi. Tayyor kutubxonalar: sana tanlash uchun react-day-picker (+19.6 KB gzip), oy/hafta jadvali uchun react-big-calendar (+58.6), FullCalendar (+56.5) yoki Schedule-X (+47.5).
Bu darsda
- Oy setkasini kutubxonasiz hisoblaysiz: oyda necha kun, 1-kun haftaning qaysi kuni, nega
Date.UTC. - Kalendarni klaviatura bilan boshqariladigan qilasiz: roving tabindex, strelkalar,
aria-disabled. - Sanalar oralig'ini (kelish — ketish) va vaqt slotlarini band joylarni hisobga olib tanlaysiz.
- Bron vaqtini restoran zonasida ko'rsatasiz va serverga to'g'ri lahza yuborasiz.
- To'rt kutubxonani (react-day-picker, react-big-calendar, FullCalendar, Schedule-X) vazifasi, hajmi va CSP natijasi bo'yicha tanlaysiz.
Oldin bilishingiz kerak: Date va vaqt zonalari, Temporal API va sana kutubxonalari, Intl.DateTimeFormat, Maxsus inputlar: sana, Fokus boshqaruvi va klaviatura navigatsiyasi.
1. Nega bu kerak?
O'tgan darsda 3D sahnalar bilan 20-qismni yopdik. 21-qism — real ilovalarda doim so'raladigan "integratsiyalar": kalendar, tarjima, grafik, xarita, to'lov.
«Bahor»ga mehmonlar qo'ng'iroq qilib stol band qilishadi. Jasur aka daftarga yozadi: "20-oktabr, 19:00, 4 kishi". Ikki muammo bor. Kechqurun telefon to'xtamaydi. Daftarda esa bir stolga ikki mehmon yozilib qoladi — 19:00 dagi bron 20:00 dagi bilan ustma-ust tushganini hech kim sezmaydi.
Jasur aka saytda bron (booking — oldindan joy band qilish) sahifasini so'radi. Mehmon kunni tanlaydi, keyin bo'sh vaqtni. Band kunlar va to'lgan vaqtlar tanlanmaydi.
Maxsus inputlar darsida <input type="date"> va uning UTC tuzog'ini ko'rgan edik. U yerda "band kunlarni kulrang qilish, oraliq — kalendar kutubxonasi ishi" degan edik. Bugun shu va'dani bajaramiz. Avval g'oyani kutubxonasiz quramiz, keyin tayyor kutubxonalarni o'lchov bilan solishtiramiz.
sequenceDiagram
participant M as Mehmon
participant K as Kalendar
participant S as Server
M->>K: 20-oktabrni tanlaydi
K->>S: shu kun bandligi?
S-->>K: band bronlar ro'yxati
K->>M: bo'sh vaqtlar: 17:00, 19:00
M->>S: 19:00, 4 kishi
S-->>M: tasdiq yoki "band bo'lib qoldi"Nimaga qarang: kalendar faqat taklif qiladi. Oxirgi so'z — serverda. Ikki mehmon bir vaqtda bir slotni bosishi mumkin, buni faqat server ajrata oladi.
2. Oy setkasi: kalendar ichida nima bor
2.1 Uchta savol
Har oylik kalendar uchta savolga javob beradi:
- Oyda necha kun bor (28, 29, 30 yoki 31)?
- 1-kun haftaning qaysi kuniga tushadi?
- Hafta qaysi kundan boshlanadi? O'zbekistonda va Rossiyada — dushanbadan, AQShda — yakshanbadan.
Javoblar tayyor bo'lsa, oy — shunchaki 7 ustunli jadval. Boshida va oxirida bo'sh kataklar qoladi.
2.2 O'quv modeli: setkani hisoblash
Quyidagi — o'quv modeli, hech qaysi kutubxonaning kodi emas. Natija oynasida kalendar kutubxonalari yo'q, lekin g'oya shu:
// O'quv modeli: oy setkasi — kutubxonasiz
const WEEKDAYS = ["Du", "Se", "Ch", "Pa", "Ju", "Sh", "Ya"];
function daysInMonth(year, month) {
// keyingi oyning 0-kuni = shu oyning oxirgi kuni
return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
}
function firstWeekday(year, month) {
// getUTCDay: 0 = yakshanba. Bizda hafta dushanbadan: 0 = Du
const day = new Date(Date.UTC(year, month, 1)).getUTCDay();
return (day + 6) % 7;
}
function monthWeeks(year, month) {
const cells = Array(firstWeekday(year, month)).fill(null);
for (let d = 1; d <= daysInMonth(year, month); d++) {
cells.push(d);
}
while (cells.length % 7 !== 0) cells.push(null);
const weeks = [];
for (let i = 0; i < cells.length; i += 7) {
weeks.push(cells.slice(i, i + 7));
}
return weeks;
}
export default function App() {
const weeks = monthWeeks(2026, 9); // 9 — oktabr (oylar 0 dan)
console.log(weeks.length, weeks[0]);
return (
<table>
<caption>Oktabr 2026</caption>
<thead>
<tr>
{WEEKDAYS.map((w) => (
<th key={w}>{w}</th>
))}
</tr>
</thead>
<tbody>
{weeks.map((week, i) => (
<tr key={i}>
{week.map((d, j) => (
<td key={j}>{d ?? "·"}</td>
))}
</tr>
))}
</tbody>
</table>
);
}Konsolda:
5 (7) [null, null, null, 1, 2, 3, 4]Ekranda:
Oktabr 2026
Du Se Ch Pa Ju Sh Ya
· · · 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31 ·Oktabr 2026 — 5 hafta qatori. 1-oktabr payshanbaga tushdi, shuning uchun boshida uchta bo'sh katak.
Uch nozik joy:
month + 1, 0— "keyingi oyning nolinchi kuni".Dateuni oldingi kunga, ya'ni shu oyning oxirgi kuniga aylantiradi. Kabisa yilini (fevral 29) ham o'zi biladi.(day + 6) % 7—getUTCDayyakshanbani 0 deb sanaydi. Biz haftani dushanbadan boshlaymiz: yakshanba 6 bo'ladi, dushanba 0.Date.UTC— hisob soat mintaqasiga bog'liq bo'lmasin.new Date(2026, 9, 1)mahalliy vaqtda yasaladi.Dateva vaqt zonalari darsida ko'rganimizdek, ba'zi zonalarda yarim tun boshqa sanaga "sakrab" ketishi mumkin. Biz faqat kalendar sanasini hisoblayapmiz — soat kerak emas, shuning uchun hammasi UTC'da.
Tekshirib ko'ring:
daysInMonth(2028, 1)nima qaytaradi?
Javob
29. month = 1 — fevral (oylar 0 dan). Date.UTC(2028, 2, 0) — "martning 0-kuni", ya'ni fevralning oxirgi kuni. 2028 — kabisa yili, shuning uchun 29. Qo'lda "fevral — 28" deb yozilgan jadval to'rt yilda bir marta xato qiladi, Date esa yo'q.
2.3 Sanani satr qilib saqlash
Tanlangan sanani state'da qanday saqlaymiz? Date obyekti emas, "2026-10-20" satri. Uch sabab:
- Satrlarni
===bilan solishtirish mumkin. IkkitaDateobyekti esa bir xil lahza bo'lsa ham===dafalse(obyektlar havola bo'yicha solishtiriladi). "YYYY-MM-DD"satrlari<bilan to'g'ri tartiblanadi — "o'tgan kunmi?" degan savol bitta taqqoslash.- Serverga ham aynan shunday yuboriladi. Zona yo'q, UTC tuzog'i yo'q.
Buni Maxsus inputlar darsida forma uchun ko'rgan edik. Kalendarda ham qoida o'sha.
Oy nomlarini o'zimiz yozamiz. Intl o'zbekcha nomlarni berishi kerak edi, lekin kanon tekshiruvi topdi: Chrome 155 da Intl.DateTimeFormat("uz") sanani «2026 M10 17» ko'rinishida chiqaradi (Node'da esa to'g'ri). Bu muammo va uning yechimi — i18n II darsida.
3. Sana tanlash: tugmalar, band kunlar va klaviatura
3.1 Har kun — tugma
Kalendar o'qish uchun emas, bosish uchun. Demak har kun — <button>. O'tgan kunlar va band kunlar tanlanmasin:
import { useState } from "react";
const MONTHS = ["yanvar", "fevral", "mart", "aprel", "may", "iyun",
"iyul", "avgust", "sentabr", "oktabr", "noyabr", "dekabr"];
const pad = (n) => String(n).padStart(2, "0");
const toIso = (y, m, d) => `${y}-${pad(m + 1)}-${pad(d)}`;
const TODAY = "2026-10-17"; // misol har kuni bir xil chiqsin
const BOOKED = ["2026-10-21", "2026-10-22"]; // tadbir kunlari
function isDisabled(date) {
return date < TODAY || BOOKED.includes(date);
}
function DatePicker({ year, month, isSelected, onPick }) {
return (
<table>
<caption>{`${MONTHS[month]} ${year}`}</caption>
<tbody>
{monthWeeks(year, month).map((week, i) => (
<tr key={i}>
{week.map((d, j) => {
if (d === null) return <td key={j} />;
const date = toIso(year, month, d);
return (
<td key={j}>
<button
type="button"
disabled={isDisabled(date)}
aria-pressed={isSelected(date)}
onClick={() => onPick(date)}
>
{d}
</button>
</td>
);
})}
</tr>
))}
</tbody>
</table>
);
}
export default function App() {
const [date, setDate] = useState(null);
return (
<>
<DatePicker
year={2026}
month={9}
isSelected={(d) => d === date}
onPick={setDate}
/>
<p>{date ? `Tanlandi: ${date}` : "Sana tanlang"}</p>
</>
);
}Ekranda:
oktabr 2026
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tanlandi: 2026-10-20«Ishga tushir» ni bosing va 21-ni bosib ko'ring — hech narsa bo'lmaydi: tugma disabled. aria-pressed ekran o'quvchiga "tanlangan" holatni aytadi — Malika 20-ni "bosilgan" deb eshitadi.
Bu blok davomi — oldingi blokdagi monthWeeks shu yerda ham ishlaydi. isSelected va onPick — funksiya prop'lar. Shu sababli bitta DatePicker ham bitta sana, ham oraliq uchun ishlaydi (keyingi bo'limlarda).
3.2 Klaviatura: 42 ta Tab emas
Bobur faqat klaviatura bilan ishlaydi. Yuqoridagi kalendarda u Tab bosib har kundan o'tadi — oktabr oxiriga yetish uchun 15 marta. Kalendardan chiqish uchun esa yana 15 marta.
ARIA Authoring Practices dagi sana tanlash naqshi buni boshqacha hal qiladi:
| Tugma | Nima qiladi |
|---|---|
| Tab | kalendarga bitta to'xtash, keyingi Tab — undan chiqish |
| ← → | bir kun oldin / keyin |
| ↑ ↓ | bir hafta oldin / keyin |
| Enter, Space | tanlash |
Bunga roving tabindex (ko'chib yuruvchi fokus, Fokus boshqaruvi darsida ko'rgan edik) yordam beradi: faqat bitta kunning tabIndex i 0, qolganlari −1. Strelka bosilganda "0" boshqa kunga ko'chadi va fokus o'sha tugmaga o'tadi.
Yana bitta o'zgarish. disabled tugma fokus ololmaydi — strelka 21-ga kelib to'xtab qoladi. Shuning uchun band kun aria-disabled="true" oladi. U fokus oladi, ekran o'quvchi "band" deb o'qiydi, lekin bosilganda tanlanmaydi:
import { useEffect, useRef, useState } from "react";
const DAY_MS = 86_400_000;
const STEPS = {
ArrowLeft: -1,
ArrowRight: 1,
ArrowUp: -7,
ArrowDown: 7,
};
function addDays(date, n) {
// "YYYY-MM-DD" — UTC yarim tuni; +n kun, yana satr
const time = Date.parse(date) + n * DAY_MS;
return new Date(time).toISOString().slice(0, 10);
}
function DatePicker({ year, month, isSelected, onPick }) {
const [focused, setFocused] = useState(toIso(year, month, 1));
const buttons = useRef(new Map());
const byKeyboard = useRef(false);
useEffect(() => {
if (byKeyboard.current) buttons.current.get(focused)?.focus();
}, [focused]);
function onKeyDown(event) {
const step = STEPS[event.key];
if (step === undefined) return; // Enter/Space — tugmaning o'zi
event.preventDefault(); // sahifa aylanib ketmasin
const next = addDays(focused, step);
if (!next.startsWith(`${year}-${pad(month + 1)}`)) return;
byKeyboard.current = true;
setFocused(next);
}
return (
<table onKeyDown={onKeyDown}>
<caption>{`${MONTHS[month]} ${year}`}</caption>
<tbody>
{monthWeeks(year, month).map((week, i) => (
<tr key={i}>
{week.map((d, j) => {
if (d === null) return <td key={j} />;
const date = toIso(year, month, d);
const off = isDisabled(date);
const name = `${d}-${MONTHS[month]}`;
const label = off ? `${name}, band` : name;
return (
<td key={j}>
<button
type="button"
ref={(el) => {
buttons.current.set(date, el);
}}
tabIndex={date === focused ? 0 : -1}
aria-disabled={off || undefined}
aria-pressed={isSelected(date)}
aria-label={label}
onFocus={() => console.log("fokus:", label)}
onClick={() => {
setFocused(date);
if (!off) onPick(date);
}}
>
{d}
</button>
</td>
);
})}
</tr>
))}
</tbody>
</table>
);
}
export default function App() {
const [date, setDate] = useState(null);
useEffect(() => {
if (date) console.log("tanlandi:", date);
}, [date]);
return (
<DatePicker
year={2026}
month={9}
isSelected={(d) => d === date}
onPick={setDate}
/>
);
}Konsolda:
fokus: 20-oktabr
tanlandi: 2026-10-20
fokus: 21-oktabr, band
fokus: 28-oktabr
tanlandi: 2026-10-28Qadamma-qadam:
| Qadam | Fokus | Tanlangan |
|---|---|---|
| 20-ni bosish | 20 | 2026-10-20 |
| → | 21 (band) | o'zgarmadi |
| Enter | 21 | o'zgarmadi — aria-disabled |
| ↓ | 28 | o'zgarmadi |
| Enter | 28 | 2026-10-28 |
Nimaga qarang: Enter 21-da hech narsa tanlamadi, lekin fokus u yerga keldi va Malika "21-oktabr, band" ni eshitdi. disabled bo'lsa, u bu kunni umuman bilmay qolardi.
byKeyboard ref'i nega kerak? Birinchi chizishda effekt ham ishlaydi. Usiz sahifa ochilishi bilan fokus kalendarga "sakrab" ketardi — bu foydalanuvchini adashtiradi. Fokusni faqat strelka bosilgandan keyin ko'chiramiz.
O'quv modelimizda strelka oy chegarasida to'xtaydi. Haqiqiy kutubxonalar keyingi oyga o'tadi, PageUp/PageDown bilan oyni, Home/End bilan haftaning boshi-oxirini almashtiradi. Bular — tayyor kutubxona tejaydigan mehnat.
Tekshirib ko'ring: Nega
tabIndexhamma kunda 0 emas?
Javob
Hammasida 0 bo'lsa, har kun Tab to'xtash joyi bo'ladi. Bobur kalendardan o'tish uchun 31 marta Tab bosadi. Roving tabindex'da kalendar bitta to'xtash: Tab — ichkariga, strelkalar — ichida yurish, yana Tab — chiqish. Jadval, menyu va tablar ham shu naqshda ishlaydi.
4. Sanalar oralig'i: kelish — ketish
4.1 Oraliq qanday tanlanadi
Sardor (stajyor) ta'til so'ramoqchi: 23-oktabrdan 26-oktabrgacha. Mehmonxona va dacha bronlari ham shunday ishlaydi. Bu — sanalar oralig'i (date range): ikki bosish, boshi va oxiri.
Qoidalar:
- Birinchi bosish — oraliq boshi.
- Ikkinchi bosish — oxiri. Agar u boshidan oldin bo'lsa, yangi boshlanish deb olinadi.
- Oraliq ichiga band kun tushsa — tanlov qaytadan boshlanadi. «Bahor»da 21–22-oktabr katta tadbir, hamma ishda.
- Oraliq to'liq bo'lsa, keyingi bosish yangi oraliqni boshlaydi.
function pickRange(range, date) {
if (range.from === null || range.to !== null) {
return { from: date, to: null };
}
if (date < range.from) return { from: date, to: null };
for (let d = range.from; d <= date; d = addDays(d, 1)) {
if (isDisabled(d)) return { from: date, to: null };
}
return { from: range.from, to: date };
}
function countDays({ from, to }) {
return (Date.parse(to) - Date.parse(from)) / DAY_MS + 1;
}
export default function App() {
const [range, setRange] = useState({ from: null, to: null });
useEffect(() => {
if (range.to) {
const days = countDays(range);
console.log(`${range.from} — ${range.to}: ${days} kun`);
} else if (range.from) {
console.log(`boshi: ${range.from}`);
}
}, [range]);
const inRange = (d) =>
d === range.from ||
(range.to !== null && range.from <= d && d <= range.to);
return (
<DatePicker
year={2026}
month={9}
isSelected={inRange}
onPick={(d) => setRange((r) => pickRange(r, d))}
/>
);
}Konsolda:
fokus: 23-oktabr
boshi: 2026-10-23
fokus: 26-oktabr
2026-10-23 — 2026-10-26: 4 kun
fokus: 19-oktabr
boshi: 2026-10-19
fokus: 24-oktabr
boshi: 2026-10-24Birinchi oraliq tayyor: 23–26, 4 kun. Keyin 19 → 24 tanlandi, lekin ular orasida 21–22 band. 3-qoida ishladi: 24 yangi boshlanish bo'ldi. O'tgan kunlar (17-dan oldin) esa umuman bosilmaydi — isDisabled ularni ham o'chiradi. Malika uchun ham har kunda aria-pressed bor — oraliqdagi har kun "bosilgan".
setRange((r) => pickRange(r, d)) — funksiyali yangilash (17-qism): yangi oraliq har doim eng so'nggi state'dan hisoblanadi.
4.2 Oxiri ichidami yoki tashqarisidami?
Sardorning ta'tili "23 dan 26 gacha" — 26 ham ta'til. Bu yopiq oraliq: ikkala chet ichida, countDays ga + 1 shuning uchun.
Mehmonxonada esa "23 dan 26 gacha" — 3 kecha. 26-kuni mehmon chiqib ketadi va xona boshqa mehmonga bo'sh. Bu yarim ochiq oraliq (half-open interval) [23, 26): boshi ichida, oxiri tashqarida. Intervallar darsidagi o'sha tushuncha.
Kutubxonalar odatda ikkinchisini ishlatadi. FullCalendar'ning select hodisasida end — eksklyuziv (hujjatda shunday yozilgan): payshanba bilan tugaydigan butun kunlik tanlovda end — juma. Kalendar kutubxonasini ulayotganda birinchi savol: "end ichidami?"
Tekshirib ko'ring: Mehmonxona bronida oraliq
[2026-10-23, 2026-10-26). Keyingi mehmon 26-oktabrdan joy olsa, bronlar ustma-ust tushadimi?
Javob
Yo'q. Yarim ochiq oraliqda 26 birinchi bronga kirmaydi — u faqat ikkinchisining boshi. Ikki oraliq a.boshi < b.oxiri && b.boshi < a.oxiri bo'lsa kesishadi: b.boshi < a.oxiri — "2026-10-26" < "2026-10-26" — false. Yopiq oraliqda esa (Sardorning ta'tili kabi) 26 ikkalasida ham bo'lardi.
5. Vaqt slotlari: bo'sh stol bormi?
5.1 Slot nima
Sana tanlandi. Endi soat. «Bahor»ning kechki bronlari 17:00 dan 22:00 gacha, har bron 2 soat, boshlanishi har 30 daqiqada. Bu bo'laklar — vaqt slotlari (time slot): oldindan bo'lingan, tanlanadigan vaqt oralig'i. Tish shifokori, sartaroshxona, avtoservis bronlari ham shunday.
"Slot" so'zini React'da boshqa ma'noda ham uchratgansiz — komponent ichidagi bo'sh joy (compound komponentlar). Bu darsda "slot" — faqat vaqt bo'lagi.
Zalda uchta stol. 20-oktabr uchun bronlar allaqachon bor. Har slot uchun savol: shu 2 soat ichida qaysi stollar bo'sh? Stol bo'sh — agar uning hech bir broni slot bilan kesishmasa. Kesishish — o'sha yarim ochiq oraliq formulasi.
import { useState } from "react";
const toMin = (hhmm) => {
const [h, m] = hhmm.split(":").map(Number);
return h * 60 + m;
};
const toHhmm = (min) =>
`${String(Math.floor(min / 60)).padStart(2, "0")}:` +
String(min % 60).padStart(2, "0");
const OPEN = toMin("17:00");
const CLOSE = toMin("22:00");
const STEP = 30; // har 30 daqiqada boshlanadi
const LENGTH = 120; // bron 2 soat
const TABLES = [1, 2, 3];
const BOOKINGS = [
{ table: 1, start: "18:00", end: "20:00" },
{ table: 1, start: "20:00", end: "22:00" },
{ table: 2, start: "19:00", end: "21:00" },
{ table: 3, start: "17:00", end: "19:00" },
];
// [a, b) va [c, d) kesishadimi — yarim ochiq oraliqlar
const overlaps = (a, b, c, d) => a < d && c < b;
function freeTables(start) {
const end = start + LENGTH;
return TABLES.filter(
(t) =>
!BOOKINGS.some(
(b) =>
b.table === t &&
overlaps(start, end, toMin(b.start), toMin(b.end)),
),
);
}
export default function App() {
const [picked, setPicked] = useState(null);
const slots = [];
for (let t = OPEN; t + LENGTH <= CLOSE; t += STEP) slots.push(t);
return (
<>
<ul>
{slots.map((t) => {
const free = freeTables(t);
return (
<li key={t}>
<button
type="button"
disabled={free.length === 0}
onClick={() => setPicked({ t, table: free[0] })}
>
{toHhmm(t)}
</button>
{` bo'sh stol: ${free.length}`}
</li>
);
})}
</ul>
<p>
{picked
? `Tanlandi: ${toHhmm(picked.t)}–` +
`${toHhmm(picked.t + LENGTH)}, ${picked.table}-stol`
: "Vaqt tanlang"}
</p>
</>
);
}Ekranda:
17:00 bo'sh stol: 1
17:30 bo'sh stol: 0
18:00 bo'sh stol: 0
18:30 bo'sh stol: 0
19:00 bo'sh stol: 1
19:30 bo'sh stol: 1
20:00 bo'sh stol: 1
Tanlandi: 19:00–21:00, 3-stolIkki joyga qarang:
- 19:00 — 3-stol bo'sh. Uning broni 17:00–19:00. Yarim ochiq oraliqda 19:00 eski bronga kirmaydi:
19:00 < 19:00—false. Yopiq oraliq bilan hisoblasak, 19:00 "band" chiqardi va restoran bitta bronni bekorga yo'qotardi. - Oxirgi slot — 20:00.
t + LENGTH <= CLOSEsharti: 20:30 dan boshlangan bron 22:30 da tugardi, oshxona esa 22:00 da yopiladi.
free[0] — eng kichik raqamli bo'sh stol. Haqiqiy restoranda stol kishi soniga qarab tanlanadi: 2 kishiga 6 o'rinli stol berilmaydi. Buni 3-mashqda qilasiz.
5.2 Kalendarning "hafta ko'rinishi"
Mehmonga slot ro'yxati yetadi. Jasur akaga esa boshqa ko'rinish kerak: hafta bo'yicha jadval, ustunlar — kunlar, qatorlar — soatlar, ichida bronlar to'rtburchak bo'lib turadi. Bu Google Calendar ko'rinishi. Uni qo'lda yozish — bir necha kunlik ish: ustma-ust tushgan bronlarni yonma-yon joylash, sudrab ko'chirish, chetidan cho'zish. Aynan shu yerda tayyor kutubxona o'zini oqlaydi — «Kutubxonalar» bo'limida ko'ramiz.
6. Vaqt zonasi: 19:00 kimning soati?
6.1 Bron — lahza
«Bahor» Toshkentda. Toshkent — UTC+5, yozgi vaqtga o'tish yo'q. Mehmon esa Moskvada (UTC+3) turib, onasiga stol band qilyapti. U ekranda "19:00" ni ko'radi. Bu Toshkent soati bo'lishi kerak — ovqat Toshkentda yeyiladi.
Qoida: slotlar joy (restoran) zonasida ko'rsatiladi, serverga lahza bo'lib boradi. Lahza — UTC'dagi aniq nuqta (Date va vaqt zonalari):
// Bron: 20-oktabr, 19:00 — «Bahor» vaqti (Toshkent, UTC+5)
const booking = new Date("2026-10-20T19:00:00+05:00");
console.log(booking.toISOString()); // 2026-10-20T14:00:00.000Z
function timeIn(date, timeZone) {
return new Intl.DateTimeFormat("en-GB", {
timeZone,
hour: "2-digit",
minute: "2-digit",
}).format(date);
}
console.log(timeIn(booking, "Asia/Tashkent")); // 19:00
console.log(timeIn(booking, "Europe/Moscow")); // 17:00Bitta lahza — ikki soat: Toshkentda 19:00, Moskvada 17:00. Kalendar timeZone: "Asia/Tashkent" bilan ko'rsatsa, Moskvadagi mehmon ham 19:00 ni ko'radi. en-GB — 24 soatli 19:00 formati uchun (Intl.DateTimeFormat). Biz faqat raqamlarni olyapmiz, oy nomi kerak emas.
6.2 Tuzoq: brauzer zonasi
Endi xato variant. Kalendar "19:00" ni mahalliy vaqt deb new Date("2026-10-20T19:00") qildi. Moskvadagi brauzer buni o'z zonasida tushunadi, ya'ni +03:00 bilan:
// ❌ Moskvadagi brauzer "19:00" ni o'z soati deb oldi
const wrong = new Date("2026-10-20T19:00:00+03:00");
console.log(wrong.toISOString()); // 2026-10-20T16:00:00.000Z
console.log(timeIn(wrong, "Asia/Tashkent")); // 21:00Mehmon 19:00 ni tanladi, oshxona uni 21:00 da kutadi. Toshkentdagi dasturchi bu xatoni hech qachon ko'rmaydi — uning brauzeri ham UTC+5.
Davosi:
- Slot vaqtini joy zonasi bilan lahzaga aylantiring:
+05:00yoki Temporal'dagiZonedDateTime[Asia/Tashkent]bilan (Temporal). Yozgi vaqti bor zonalarda (Yevropa, AQSh) offsetni qo'lda yozib bo'lmaydi — Temporal yoki kutubxona kerak. - Faqat sana bo'lsa (ta'til, tug'ilgan kun) —
"YYYY-MM-DD"satri, umuman lahza emas. - Kutubxonalarda zona sozlamasi bor: react-day-picker'da
timeZoneprop, FullCalendar'datimeZoneopsiyasi. Sukut bo'yicha ular brauzer zonasida ishlaydi.
Tekshirib ko'ring: Server bronni
2026-10-20T14:00:00.000Zdeb saqladi. Toshkentdagi Jasur aka va Moskvadagi mehmon bu bronni o'z ekranlaridatimeIn(…, "Asia/Tashkent")bilan ko'rsa, nimani ko'radi?
Javob
Ikkalasi ham 19:00. Lahza bitta, zona ham bitta (Asia/Tashkent) — brauzerning zonasi ishtirok etmaydi. Shuning uchun bron ilovalari "restoran vaqti" ni alohida yozadi: "19:00 (Toshkent vaqti)".
7. Kutubxonalar
7.1 Ikki xil vazifa
Kalendar kutubxonalari ikki guruhga bo'linadi:
| Vazifa | Kutubxona | Qayerda |
|---|---|---|
| Sana yoki oraliq tanlash (forma ichida) | react-day-picker | bron formasi, filtr, tug'ilgan kun |
| Voqealarni ko'rsatish va boshqarish (oy/hafta/kun) | react-big-calendar, FullCalendar, Schedule-X | admin panel, xodimlar jadvali, zal bandligi |
«Bahor»ning mehmon sahifasiga birinchisi, Jasur akaning paneliga ikkinchisi kerak.
7.2 react-day-picker
shadcn/ui'ning Calendar komponenti aynan shu kutubxona ustida (Maxsus inputlar darsida ko'rgandik). Oraliq rejimi — bitta prop:
import { useState } from "react";
import { DayPicker } from "react-day-picker";
import type { DateRange } from "react-day-picker";
import "react-day-picker/style.css";
function VacationPicker({ booked }: { booked: Date[] }) {
const [range, setRange] = useState<DateRange | undefined>();
return (
<DayPicker
mode="range"
selected={range}
onSelect={setRange}
disabled={[{ before: new Date() }, ...booked]}
excludeDisabled // oraliqqa band kun tushsa — qaytadan
min={1}
max={14}
/>
);
}Bizning o'quv modelimizdagi 3-qoida (pickRange) — bu yerda excludeDisabled. min/max — oraliq uzunligi chegarasi. Klaviatura (strelkalar, PageUp/PageDown, Home/End) va ARIA — tayyor. timeZone va locale proplari bor.
Versiya haqida: kanon react-day-picker 10.0.2 ni o'lchadi. 10-versiya hujjati yangi loyihalarga paketning yangi nomini — @daypicker/react ni tavsiya qiladi. Eski nom moslik uchun ishlab turibdi. Eski maqolalarda esa lokal date-fns/locale dan olinadi (8-versiya) — 10 da u kerak emas.
7.3 react-big-calendar
Google Calendar uslubidagi oy/hafta/kun ko'rinishlari. Sanalarni hisoblash uchun "lokalizator" (date-fns, Day.js, Moment) talab qiladi — kanon date-fns 4.4.0 bilan o'lchadi:
import { Calendar, dateFnsLocalizer } from "react-big-calendar";
import { format, getDay, parse, startOfWeek } from "date-fns";
import "react-big-calendar/lib/css/react-big-calendar.css";
const localizer = dateFnsLocalizer({
format, parse, startOfWeek, getDay, locales: {},
});
<Calendar
localizer={localizer}
events={bookings} // { title, start: Date, end: Date }
defaultView="week"
views={["month", "week", "day"]}
step={30} // bitta katak — 30 daqiqa
timeslots={2} // soatga 2 katak
selectable
onSelectSlot={({ start, end }) => offerBooking(start, end)}
/>;selectable + onSelectSlot — bo'sh joyni sichqoncha bilan "chizib" bron taklif qilish. Bronni sudrab ko'chirish alohida qo'shimcha bilan ulanadi.
7.4 FullCalendar 7
Imkoniyatlari keng kalendar: oy, hafta, kun, ro'yxat ko'rinishlari. React, Vue, Angular va oddiy JS uchun bir xil API. 7-versiyada plaginlar @fullcalendar/react/… ichiga ko'chdi:
import FullCalendar from "@fullcalendar/react";
import timeGridPlugin from "@fullcalendar/react/timegrid";
import interactionPlugin from "@fullcalendar/react/interaction";
import "@fullcalendar/react/skeleton.css";
<FullCalendar
plugins={[timeGridPlugin, interactionPlugin]}
initialView="timeGridWeek"
slotDuration="00:30:00"
selectable // interaction plagini kerak
select={(info) => console.log(info.startStr, info.endStr)}
events={[{ title: "Bron: 4 kishi", start: "2026-10-20T19:00:00" }]}
/>;Ikki narsa. select dagi end — eksklyuziv («Oxiri ichidami yoki tashqarisidami?» bo'limida aytdik). 7-versiya temporal-polyfill ni talab qiladi — sanalarni ichida Temporal bilan hisoblaydi. Eski (6-versiya) maqolalarda @fullcalendar/daygrid kabi alohida paketlarni ko'rasiz.
Kanon faqat oy ko'rinishini (dayGridMonth) o'lchadi. Yuqoridagi hafta ko'rinishi va select — hujjat bo'yicha, ishga tushirilmagan.
7.5 Schedule-X
Yangi avlod kalendari: zamonaviy dizayn, ko'rinishlar alohida funksiyalar. Kanon tajribasidagi kod:
import { ScheduleXCalendar, useCalendarApp } from "@schedule-x/react";
import { createViewMonthGrid } from "@schedule-x/calendar";
function Kalendar() {
const calendar = useCalendarApp({
views: [createViewMonthGrid()],
events: [],
});
return <ScheduleXCalendar calendarApp={calendar} />;
}U ham Temporal polyfill'iga tayanadi — va aynan shu joyda kutilmagan muammo chiqdi (keyingi bo'lim).
8. Biz sinab ko'rdik: hajm, CSP va bog'liqlik to'qnashuvi
8.1 Hajm
Kanon tajribasida (tajriba/kutubxonalar/) har kutubxonaning eng kichik ishlaydigan misoli Vite 8.3.3 bilan yig'ildi. Hajm — faqat React'dan ortig'i, gzip:
- react-day-picker 10.0.2+1.6 KB CSS19,6 KB
- Schedule-X 4.9.147,5 KB
- FullCalendar 7.1.1+1.2 KB CSS56,5 KB
- react-big-calendar 1.20.0 + date-fns+2.4 KB CSS58,6 KB
Manba: O'lchandi: kanon tajribasi, Vite 8.3.3, React 19.3.0, gzip -9, Chrome 155, natijalar/tajriba/kutubxonalar-21.txt, 2026-10-17
Taqqoslash uchun: React va react-dom'ning o'zi — 66.1 KB. Bitta sana tanlagich uchun +19.6 KB — sezilarli, lekin qabul qilsa bo'ladi. To'liq hafta jadvali esa React'ning deyarli yarmicha. Shuning uchun uni faqat kerakli sahifada, kech yuklash bilan ulash mantiqli.
8.2 CSP va Trusted Types
Har misol vazifalar ning CSP'si va Trusted Types ostida ochildi (Brauzer xavfsizlik modeli). Xom natijadan (kutubxonalar-21.txt):
kalendar-bigcalendar JS 127686 (+ 58.6 KB) CSS 2394 | CSP 0 | xato: yo'q | ochiq: tashqi yo'q
kalendar-daypicker JS 87813 (+ 19.6 KB) CSS 1623 | CSP 0 | xato: yo'q | ochiq: tashqi yo'q
kalendar-fullcalendar JS 125530 (+ 56.5 KB) CSS 1210 | CSP 0 | xato: yo'q | ochiq: tashqi yo'q
kalendar-schedulex JS 116313 (+ 47.5 KB) CSS 0 | CSP img-src×1 | xato: Loading the image 'data:image/svg+xml,%3c%3fxml version='1.0' encoding='utf-8'%3f%3e%3c!-- Uploaded | ochiq: tashqi yo'qUch kutubxona — CSP 0: innerHTML yo'q, sahifaga <style> qo'shmaydi, tashqi serverga bormaydi. Schedule-X esa bitta buzilish berdi: o'z ikonkasini data: manzilli SVG rasm qilib chizadi, img-src esa data: ga ruxsat bermaydi. Kalendar ishlaydi, faqat ikonka ko'rinmaydi. Tuzatish — CSP'ga img-src 'self' data: qo'shish. data: rasmlar odatda xavfsiz hisoblanadi, lekin bu baribir siyosatni kengaytirish — ongli qaror bo'lsin.
8.3 Ikki kalendar bitta loyihada sig'madi
Kanonda to'rttasi bitta tajriba loyihasiga o'rnatildi va birinchi urinish to'xtadi (kutubxonalar-ornatish.txt):
# 1-urinish: ERESOLVE — temporal-polyfill: @fullcalendar/core 7.1.1 ^1.0.1, @schedule-x/calendar 4.9.1 0.3.2 (bitta loyihada ikkalasi bo'lmaydi) → --legacy-peer-depsFullCalendar 7 temporal-polyfill ning ^1.0.1 versiyasini, Schedule-X 4.9.1 esa aynan 0.3.2 ni peer bog'liqlik qilib so'raydi (bog'liqlik turlari). Ikki talab bir-biriga zid. Tajribada --legacy-peer-deps bilan o'rnatildi va Schedule-X temporal-polyfill 1.0.5 bilan qayta o'lchandi (yuqoridagi raqam). Haqiqiy loyihada bu yo'l xavfli: Schedule-X o'zi sinalmagan polyfill bilan ishlaydi.
Saboq: Temporal hali hamma brauzerda yo'q (Temporal), kutubxonalar esa uni allaqachon polyfill bilan ishlatyapti — polyfill versiyalari to'qnashadi. Ikki kalendar kutubxonasini bitta loyihada ishlatmang.
8.4 Nega vazifalar da kalendar yo'q
Bu darsda vazifalar kodi o'zgarmaydi. Sabab oddiy: vazifada sana maydoni yo'q. 14-qismda "keyingi muddat" bo'yicha tartiblash qadami shu sababli qo'shilmagan edi. Kalendar faqat sana bor joyda ma'noga ega. Kanon kutubxonalarni alohida tajribada o'lchadi — keyin vazifalar ga muddat qo'shilsa, tanlov raqamlar bilan tayyor: forma uchun react-day-picker, CSP 0.
9. Hujumchi nigohi
Kalendar o'tgan kunlarni, band slotlarni va 22:00 dan keyingi vaqtni o'chirdi. Lekin bu — faqat interfeys. Hujumchi brauzerni chetlab, to'g'ridan-to'g'ri so'rov yuboradi:
fetch("/api/bron", {
method: "POST",
body: JSON.stringify({
sana: "2026-10-21",
vaqt: "03:00",
kishi: 500,
}),
});Band kun, tungi soat, 500 kishi. Server tekshirmasa, bularning hammasi bazaga yoziladi. Yana bir hujum — ikki marta bron: ikki so'rov bir paytda kelsa, ikkalasi ham "bo'sh" ni ko'radi va ikkalasi yoziladi.
Himoya — faqat serverda:
- Ish soati, o'tgan sana, kishi soni — server qoidalari bilan qayta tekshiriladi (klientdagi
isDisabled— faqat qulaylik). - Bandlik bazada tranzaksiya yoki noyoblik cheklovi bilan tekshiriladi — ikkinchi so'rov "band bo'lib qoldi" oladi. Kalendar bu javobni chiroyli ko'rsatishi kerak (diagrammadagi oxirgi qadam).
- Bir IP'dan yuzlab bron — so'rovlar chegarasi (rate limit).
Bular backend qismlarida (24-qismdan) to'liq yoziladi. Frontend dasturchining vazifasi — serverning rad javobini kutish va mehmonga tushunarli qilib aytish.
10. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Kalendar bir oy siljigan | new Date(2026, 10, 1) — bu noyabr (oylar 0 dan) |
month ni 0 dan hisoblang, nomlarni massivdan oling |
| Ba'zi mehmonlarda sana bir kun oldinga ketgan | new Date("2026-10-20") — UTC yarim tuni, mahalliy vaqtda kecha |
sanani "YYYY-MM-DD" satri qilib saqlang |
| Hafta yakshanbadan boshlanadi | getDay() da 0 = yakshanba |
(day + 6) % 7 yoki kutubxonada weekStartsOn |
| 19:00 band ko'rinadi, aslida bo'sh | oraliq yopiq deb hisoblangan (<=) |
yarim ochiq: a < d && c < b |
| Klaviatura band kunda to'xtab qoladi | band kun disabled |
aria-disabled + bosishni e'tiborsiz qoldirish |
| Moskvadagi mehmonning broni 2 soat siljigan | slot brauzer zonasida lahzaga aylangan | joy zonasi bilan: +05:00 yoki Temporal |
npm i → ERESOLVE … temporal-polyfill |
ikki kalendar turli polyfill so'raydi | bitta kalendar kutubxonasi |
Loading the image 'data:image/svg+xml…' violates … img-src |
Schedule-X ikonkasi | img-src 'self' data: — ongli qaror bilan |
11. Mashqlar
Mashqlar kurs/mashqlar/21/01-kalendar-bron/ da. Jonli yechimlarni «Ishga tushir» bilan sinang.
1-mashq (oson): bo'sh joylarni to'ldiring
new Date(Date.UTC(2026, 10, 1))qaysi oyning 1-kuni:getUTCDay()0 qaytarsa, bu haftaning:- 18:00–20:00 va 20:00–22:00 bronlari yarim ochiq oraliqda kesishadimi (ha/yo'q):
- react-day-picker'da oraliq tanlash:
mode="" - Klaviatura band kunga ham borishi uchun
disabledo'rniga:
Yechim
Oylar 0 dan, shuning uchun 10 — noyabr. Yakshanbani getUTCDay 0 deb sanaydi. Shu sababli haftani (day + 6) % 7 bilan dushanbadan boshlaymiz. Bronlarda esa 20:00 < 20:00 — false, demak ular kesishmaydi: bitta stol ketma-ket ikki mehmonga xizmat qiladi.
DayPicker'ning oraliq rejimi — mode="range". Band kunda aria-disabled tugma fokus oladi va ekran o'quvchi uni "band" deb o'qiydi, disabled esa fokusdan butunlay chiqarib tashlaydi.
2-mashq (o'rta): oyni almashtirish
«Oy setkasi» bo'limidagi monthWeeks ga «Oldingi oy» va «Keyingi oy» tugmalarini qo'shing. Sarlavha — noyabr 2026 kabi (oy nomlari — MONTHS massividan). Dekabrdan keyin yanvar va yil +1 bo'lsin.
Ishora: oy va yilni bitta son qilib hisoblash qulay — year * 12 + month. Undan yilni Math.floor(n / 12) bilan, oyni n % 12 bilan qaytarasiz (arifmetik operatorlar).
Sinov: oktabr 2026 dan uch marta «Keyingi oy» bosilsa, konsolga sarlavha va birinchi hafta chiqsin.
Yechim
import { useEffect, useState } from "react";
const MONTHS = ["yanvar", "fevral", "mart", "aprel", "may", "iyun",
"iyul", "avgust", "sentabr", "oktabr", "noyabr", "dekabr"];
function monthWeeks(year, month) {
const first = new Date(Date.UTC(year, month, 1)).getUTCDay();
const days = new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
const cells = Array((first + 6) % 7).fill(null);
for (let d = 1; d <= days; d++) cells.push(d);
while (cells.length % 7 !== 0) cells.push(null);
const weeks = [];
for (let i = 0; i < cells.length; i += 7) {
weeks.push(cells.slice(i, i + 7));
}
return weeks;
}
export default function App() {
// bitta son: yil * 12 + oy
const [index, setIndex] = useState(2026 * 12 + 9);
const year = Math.floor(index / 12);
const month = index % 12;
const title = `${MONTHS[month]} ${year}`;
const weeks = monthWeeks(year, month);
useEffect(() => {
console.log(title, weeks[0].join(" "));
}, [title, weeks]);
return (
<section>
<button type="button" onClick={() => setIndex((i) => i - 1)}>
Oldingi oy
</button>
<h2>{title}</h2>
<button type="button" onClick={() => setIndex((i) => i + 1)}>
Keyingi oy
</button>
</section>
);
}Konsolda:
oktabr 2026 1 2 3 4
noyabr 2026 1
dekabr 2026 1 2 3 4 5 6
yanvar 2027 1 2 3Massivdagi null lar join(" ") da bo'sh satrga aylanadi. Shuning uchun qator boshida bo'sh joylar bor: 1-yanvar 2027 — juma, oldida to'rtta bo'sh katak.
Hisoblagich index bitta son bo'lgani uchun yil almashishini alohida tekshirish shart emas: 2026 × 12 + 11 + 1 = 2027 × 12 + 0. Effekt ham title o'zgargandagina ishlaydi.
3-mashq (qiyin): stol kishi soniga qarab
«Vaqt slotlari» bo'limidagi hisobni kengaytiring. Stollar har xil:
const TABLES = [
{ id: 1, seats: 2 },
{ id: 2, seats: 4 },
{ id: 3, seats: 6 },
];findTable(bookings, start, guests) funksiyasini yozing: shu vaqtdan 2 soatga bo'sh va seats >= guests bo'lgan stollardan eng kichigini qaytarsin (yo'q bo'lsa — null). Bronlar — darsdagi BOOKINGS (table — stol id si). Keyin 4 kishilik oila uchun 17:00 dan 20:00 gacha har slotni konsolga chiqaring: 17:00 → 3-stol yoki 17:30 → bo'sh stol yo'q.
Ishora: avval bo'sh stollarni filter, keyin toSorted((a, b) => a.seats - b.seats) (nusxa bilan o'zgartirish) va birinchisi.
Yechim
const toMin = (hhmm) => {
const [h, m] = hhmm.split(":").map(Number);
return h * 60 + m;
};
const toHhmm = (min) =>
`${String(Math.floor(min / 60)).padStart(2, "0")}:` +
String(min % 60).padStart(2, "0");
const LENGTH = 120;
const TABLES = [
{ id: 1, seats: 2 },
{ id: 2, seats: 4 },
{ id: 3, seats: 6 },
];
const BOOKINGS = [
{ table: 1, start: "18:00", end: "20:00" },
{ table: 1, start: "20:00", end: "22:00" },
{ table: 2, start: "19:00", end: "21:00" },
{ table: 3, start: "17:00", end: "19:00" },
];
const overlaps = (a, b, c, d) => a < d && c < b;
function findTable(bookings, start, guests) {
const end = start + LENGTH;
const free = TABLES.filter(
(t) =>
t.seats >= guests &&
!bookings.some(
(b) =>
b.table === t.id &&
overlaps(start, end, toMin(b.start), toMin(b.end)),
),
);
return free.toSorted((a, b) => a.seats - b.seats)[0] ?? null;
}
for (let t = toMin("17:00"); t <= toMin("20:00"); t += 30) {
const table = findTable(BOOKINGS, t, 4);
const text = table ? `${table.id}-stol` : "bo'sh stol yo'q";
console.log(`${toHhmm(t)} → ${text}`);
}Konsolda:
17:00 → 2-stol
17:30 → bo'sh stol yo'q
18:00 → bo'sh stol yo'q
18:30 → bo'sh stol yo'q
19:00 → 3-stol
19:30 → 3-stol
20:00 → 3-stol17:00 da 2-stol (4 o'rin) va 3-stol (6 o'rin) emas — 2-stol band emas edi va u kichikroq. 1-stol 2 o'rinli, 4 kishiga sig'maydi. 19:00 dan 2-stol band (19:00–21:00), shuning uchun 6 o'rinli 3-stol. Eng kichik mos stol — restoran o'rinlarini tejash qoidasi: katta stol katta oilaga qolsin.
4-mashq: tajriba qadami (ixtiyoriy)
vazifalar ga tegmang. Alohida papkada (kurs/tajriba/kalendar/) Vite + React loyiha oching va react-day-picker@10.0.2 bilan Sardorning ta'til formasini yasang: mode="range", band kunlar, excludeDisabled, max={14}. Keyin ikki narsani tekshiring: npm run build dagi gzip hajmi va index.html ga vazifalar dagi CSP <meta> i qo'shilganda konsoldagi xatolar.
Yechim
Tekshiruv ro'yxati:
- O'rnatish:
npm i -E react-day-picker@10.0.2(yoki yangi nomi@daypicker/react) —date-fnsalohida kerak emas. - Band kun bosilmaydi; oraliqqa band kun tushsa, tanlov qaytadan boshlanadi; 14 kundan uzun oraliq tanlanmaydi.
- Strelkalar, PageUp/PageDown ishlaydi; Tab kalendarga bitta to'xtash.
- Build: Vite oxirida har faylning gzip hajmini chiqaradi. Kanonda faqat React'dan ortig'i — 19.6 KB JS va 1.6 KB CSS. Sizning raqamingiz forma va boshqa kodga qarab biroz farq qiladi.
- CSP bilan: konsol toza bo'lishi kerak (kanonda — 0 buzilish).
12. Real ishda
- Bron xizmatlari (restoran, salon, klinika, mehmonxona) — slot hisobi va bandlik serverda, frontend faqat bo'sh vaqtlarni so'raydi. Ko'p xizmatlarda slot ro'yxati API'dan tayyor keladi.
- Admin panellar va CRM — xodimlar jadvali, zal bandligi: react-big-calendar yoki FullCalendar. FullCalendar'ning resurslar (xona, stol, xodim) bo'yicha ko'rinishlari — pullik Premium litsenziyada (fullcalendar.io/pricing).
- Formalar — sana yoki oraliq tanlagich: react-day-picker, shadcn/ui
Calendar. Ko'p jamoalar oddiy<input type="date">bilan boshlaydi va faqat band kunlar kerak bo'lganda kutubxonaga o'tadi. - Intervyu savollari: "Ikki bron kesishishini qanday tekshirasiz?", "Bron vaqtini qaysi zonada saqlaysiz?", "Kalendar klaviaturada qanday ishlashi kerak?", "
endeksklyuziv nima degani?".
Xulosa
- Oy setkasi: kunlar soni (
Date.UTC(y, m + 1, 0)), 1-kunning hafta kuni ((day + 6) % 7), 7 ustun; sana state'da"YYYY-MM-DD"satri. - Kalendar klaviaturada — roving tabindex: bitta Tab to'xtashi, strelkalar, band kun
aria-disabled. - Oraliq: ikki bosish, band kun tushsa — qaytadan; ta'til — yopiq oraliq, mehmonxona va slotlar — yarim ochiq
[boshi, oxiri). - Vaqt sloti bo'sh — agar stolning birorta broni u bilan kesishmasa:
a < d && c < b. - Vaqt joy zonasida ko'rsatiladi, serverga lahza bo'lib boradi; brauzer zonasiga ishonmang.
- Kutubxonalar: tanlash — react-day-picker (+19.6 KB), jadval — react-big-calendar (+58.6), FullCalendar (+56.5), Schedule-X (+47.5, CSP'da
data:ikonka); ikki Temporal'li kalendar bitta loyihada sig'madi.
Keyingi dars: i18n I: react-i18next bilan ko'p tilli ilova — «Bahor» va vazifalar ruscha gapira boshlaydi: matnlar tarjima fayllariga chiqadi, ko'plik shakllari ("2 задачи", "5 задач") kutubxona hisobida.
Manbalar
- React DayPicker 10: "Range mode", "Disabling dates", "Setting time zone", "Upgrading" — daypicker.dev
- react-big-calendar: "Calendar props" (
selectable,onSelectSlot,step,timeslots) — jquense.github.io/react-big-calendar - FullCalendar 7: "Upgrading from v6", "selectable", "select callback" — fullcalendar.io/docs; litsenziya — fullcalendar.io/pricing
- Schedule-X: "React" — schedule-x.dev
- WAI-ARIA APG: "Date Picker Dialog Example", "Grid pattern" — w3.org/WAI/ARIA/apg
- MDN:
Date.UTC(),Intl.DateTimeFormat(timeZone) — developer.mozilla.org - Kanon:
natijalar/tajriba/kutubxonalar-21.txt,kutubxonalar-ornatish.txt,tajriba/kutubxonalar/src/k/kalendar-*.tsx(2026-10-17)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!