1. Noldan ishchi kuchi taqvimini yaratish haqida
Veb-xizmatda taqvim sanalarni tanlash uchun mo‘ljallangan oddiy interfeysdek ko‘rinishi mumkin, ammo uni yaratishda har bir oydagi kunlar soni, hafta kunlariga moslash, bo‘sh sana kataklari va har bir sana uchun ma’lumotlarni joylashtirish kabi bir qancha omillarni hisobga olish kerak. Ayniqsa, faqat sanalarni ko‘rsatadigan odatiy taqvimdan farqli ravishda, ishchi kuchi taqvimida ishchilar soni va summalar kabi ishga oid ma’lumotlar ham ko‘rsatilishi kerak. Shu sababli xizmat talablariga javob beradigan tuzilma zarur edi.
Ushbu loyiha uchun BanJang Note xizmatida ishlatiladigan ishchi kuchi taqvimini noldan yaratdim. Asosiy sanalarni ko‘rsatishdan tashqari, u har bir sana uchun ma’lumotlarni ham aks ettira oladi. Komponentlarning vazifalarini bir nechta ekranda qayta ishlatish mumkin bo‘ladigan tarzda ajratishga e’tibor qaratdim.
Taqvimning asosiy tuzilmasini yaratishda e’tiborga olish kerak bo‘lgan muhim jihatlardan biri sana hisob-kitoblarini ularning ekrandagi ko‘rinishidan ajratish edi. Sana hisob-kitoblari aniq bo‘lsa ham, sanalar to‘g‘ri hafta kunlari ostida joylashtirilmasa, taqvimdan to‘g‘ri foydalanib bo‘lmaydi. Aksincha, ekran tuzilmasi yaxshi loyihalangan bo‘lsa ham, sana hisob-kitoblaridagi xatolar ayrim sanalarning tushib qolishiga yoki noto‘g‘ri joylarda ko‘rsatilishiga olib kelishi mumkin. Shu bois sanalarni hisoblash mantiqini ham, natijalarni ekranga joylashtirish tuzilmasini ham ko‘rib chiqdim.
2. React komponentlari va sana ma’lumotlari tuzilmasi
Avval vazifalarni quyidagicha taqsimladim: butun taqvimni boshqaruvchi CustomCalendar, bir oylik sanalarni ko‘rsatuvchi CalendarMonthGrid va sanalar massivini yaratuvchi buildWeeks funksiyasi.
CustomCalendar taqvimning umumiy holati va ekran joylashuvini boshqaradi, CalendarMonthGrid esa o‘zi qabul qilgan oy uchun hafta kunlari va sanalarni ko‘rsatadi. buildWeeks berilgan oy sanalarini hisoblab, ularni haftalik guruhlarga ajratadi.
Funksiyalarni shu tarzda ajratish sana hisoblash mantiqi va ekran tuzilmasini alohida ko‘rib chiqish imkonini beradi. Sanalarni yaratish usulini o‘zgartirganda interfeysning boshqa qismlariga kiritilishi kerak bo‘lgan o‘zgarishlar doirasini qisqartiradi, shuningdek, interfeysni yangilashning sana hisob-kitoblariga ta’sirini cheklaydi.
Har bir sana uchun ma’lumotlarni CalendarDayData turi yordamida belgiladim.
export type CalendarDayData = {
effort?: number;
amount?: number;
holidayName?: string;
totalWorkers?: number;
attendedWorkers?: number;
memo?: string;
};
Mavjud ma’lumotlar sanadan sanaga farq qilishi mumkinligi uchun har bir xususiyatga ? qo‘shdim. Ayrim sanalarda faqat ishchilar soni bo‘lishi, boshqalarida esa bayram nomi yoki qayd bo‘lishi mumkin. Shu sababli tuzilma faqat zarur ma’lumotlarni uzatishga imkon beradi.
Har bir sana uchun ma’lumotlar sana satrlari kalit sifatida ishlatiladigan obyekt ichida boshqariladi.
type WorkdaysCalendarProps = {
value: Dayjs | null;
onChange: (date: Dayjs | null) => void;
data?: Record<string, CalendarDayData>;
};
Record<string, CalendarDayData> — satrli kalitlarni har bir sana haqidagi ma’lumotlarni o‘z ichiga olgan obyektlar bilan bog‘laydigan tur. Sanalarni kalit sifatida ishlatish muayyan sana uchun kerakli ma’lumotni bevosita topish imkonini beradi.
const calendarData: Record<string, CalendarDayData> = {
'2026-10-01': {
effort: 1,
},
'2026-10-02': {
effort: 0.5,
memo: '오전 근무',
},
};
Bu tuzilmadan foydalanish uchun sana kalitlarining formati bir xil bo‘lishi kerak. 2026-10-01 va 2026.10.01 turli satrlar, shuning uchun sanalarni yaratishda ham, ma’lumotlarni qidirishda ham bir xil formatdan foydalanish lozim. Ushbu amalga oshirishda sanalarni YYYY-MM-DD formatiga keltirish uchun Day.js’ning format() usulidan foydalandim.
3. Day.js yordamida sanalarni hisoblash
Taqvim yaratishning muhim qismi — muayyan oy sanalarini aniq hisoblash. Oylar bo‘yicha kunlar soni turlicha bo‘ladi, oyning boshidagi bo‘sh joylar esa birinchi kun qaysi hafta kuniga to‘g‘ri kelishiga bog‘liq. Shu sababli taqvim ko‘rinishini yaratish uchun sanalarni shunchaki oyning 1-kunidan boshlab sanab chiqish yetarli emas.
Ushbu loyiha uchun sana hisob-kitoblarini soddalashtirish maqsadida Day.js’dan foydalandim. Day.js — sanalarni yaratish, oydagi kunlar sonini tekshirish, sanalarni surish, taqqoslash va satrlarga aylantirish funksiyalarini taqdim etadigan JavaScript kutubxonasi.
Avval dayjs() yordamida joriy sanani yaratdim va muayyan vaqt birligining boshini olish uchun startOf() dan foydalandim.
const [visibleMonth, setVisibleMonth] = useState(value ?? dayjs());
const next = visibleMonth
.add(delta, 'month')
.startOf('month');
visibleMonth hozir ko‘rsatilayotgan oyni anglatadi. Agar ota-komponentdan sana uzatilsa, taqvim shu sanadan boshlanadi; aks holda joriy sana ishlatiladi.
Oylar orasida o‘tishda add() yordamida oylar sonini qo‘shaman, so‘ng sanani o‘sha oyning birinchi kuniga o‘rnatish uchun startOf('month') dan foydalanaman. Masalan, 2026-yil 16-oktabrga bir oy qo‘shilsa, 16-noyabr bo‘ladi, ammo startOf('month') qo‘llangach, sana 1-noyabrga o‘rnatiladi. Sanalarni oylar bo‘yicha boshqarishda kunni saqlab qolish shart bo‘lmagani uchun bu usul oylarni almashtirish asosini aniq belgilaydi.
daysInMonth() berilgan oydagi kunlar sonini qaytaradi. Shu tufayli har bir oydagi kunlar sonini shartli ifodalar yordamida alohida ko‘rib chiqish shart emas.
for (let day = 1; day <= month.daysInMonth(); day += 1) {
days.push(month.startOf('month').date(day));
}
Sikl oyning 1-kunidan oxirgi kunigacha ishlaydi. date(day) — oyning kunini o‘rnatadigan usul bo‘lib, oyning birinchi kuniga nisbatan har bir sanani yaratadi. Fevraldagi kabi kunlar soni yilga qarab o‘zgarsa ham, daysInMonth() amaldagi kunlar sonini qaytaradi va shu mantiqdan foydalanish imkonini beradi.
Hafta kunini aniqlash uchun day() dan foydalanaman. Bu usul yakshanbani 0, dushanbani 1 va shanbani 6 bilan ifodalaydi. Oyning birinchi kunidagi hafta kunini tekshirish sanalardan oldin nechta bo‘sh joy kerakligini hisoblash imkonini beradi.
Sanalarni satrlarga aylantiradigan format() ham muhim vazifani bajaradi.
const dateKey = date.format('YYYY-MM-DD');
const monthTitle = visibleMonth.format('YYYY년 M월');
Birinchi kod sana uchun ma’lumotlarni olishda ishlatiladigan kalitni yaratadi, ikkinchisi esa ekranda ko‘rsatiladigan oy sarlavhasini yaratadi. MM oyni doim ikki raqam bilan ifodalaydi, M esa bir xonali oylarni boshiga nol qo‘shmasdan ifodalaydi. Shu sababli ma’lumot kaliti uchun bir xil formatdagi YYYY-MM-DD, ekran sarlavhasi uchun esa o‘qishga qulayroq YYYY yil M oy formati ishlatiladi.
Sanalarni taqqoslash uchun isSame() dan foydalandim.
const isSelected = value?.isSame(date, 'day');
const isToday = date.isSame(dayjs(), 'day');
isSame() ga ikkinchi argument sifatida 'day' uzatilsa, vaqtlar emas, sanalarning o‘zi taqqoslanadi. Ikki sana bir oy ichida ekanini tekshirish uchun 'month' ko‘rsatish mumkin. Taqqoslash birligini maqsadga qarab belgilash sanalar va oylarni alohida ko‘rib chiqish imkonini beradi.
Day.js’ning yana bir xususiyati — o‘zgarmaslikdir. add() va startOf() asl sana obyektini bevosita o‘zgartirmasdan, hisob-kitob natijasini qaytaradi. Bu asl sanani saqlab, yangilangan sanani alohida boshqarish imkonini beradi. Sana hisob-kitoblari paytida asl qiymatning kutilmaganda o‘zgarib ketishining oldini olishga yordam beradi va React holatidagi o‘zgarishlar oqimini boshqarishni osonlashtiradi.
4. Sanalarni haftalarga ajratish
Sanalarni hisoblab chiqqach, ularni haqiqiy taqvim ko‘rinishida ko‘rsatish uchun massivga joylashtirish kerak. Ushbu amalga oshirishda sanalar massivini yaratib, uni har birida yettita elementdan iborat haftalik guruhlarga ajratish uchun buildWeeks funksiyasidan foydalandim.
const buildWeeks = (month: Dayjs) => {
const days: (Dayjs | null)[] = Array.from(
{ length: month.startOf('month').day() },
() => null,
);
for (let day = 1; day <= month.daysInMonth(); day += 1) {
days.push(month.startOf('month').date(day));
}
return Array.from(
{ length: Math.ceil(days.length / 7) },
(_, index) => days.slice(index * 7, index * 7 + 7),
);
};
Birinchi bosqichda oyning birinchi kunidan oldin kerak bo‘ladigan bo‘sh joylarni yarataman. startOf('month').day() yordamida oyning birinchi kunidagi hafta kunini aniqlab, massivga shuncha null qiymat qo‘shaman. Birinchi kun yakshanba bo‘lsa, bo‘sh joy kerak bo‘lmaydi; chorshanba bo‘lsa, boshiga uchta bo‘sh joy kerak bo‘ladi.
Bu yerda belgilangan uzunlikdagi massivni yaratish uchun Array.from() dan foydalandim. Sanalar bo‘lmagan o‘rinlar null bilan ifodalanadi, massiv turi esa unda haqiqiy sanalar ham, bo‘sh joylar ham bo‘lishi mumkinligini ko‘rsatish uchun (Dayjs | null)[] deb belgilangan.
Ikkinchi bosqichda daysInMonth() qaytargan kunlar sonicha sikl orqali sanalarni qo‘shaman. Nihoyat, butun massiv uzunligini 7 ga bo‘lib, kerakli haftalar sonini hisoblash uchun Math.ceil() ni qo‘llayman. Massivni yettitadan elementli guruhlarga ajratish uchun slice() dan foydalandim.
Bu jarayon sanalarning yassi massivini haftalar bo‘yicha guruhlangan massivga aylantiradi. Ekranda tashqi sikl har bir haftani yaratishi, ichki sikl esa har bir sana katagini ko‘rsatishi mumkin.
Biroq, joriy amalga oshirishda oyning birinchi kunidan oldin kerakli bo‘sh joylar qo‘shiladi, lekin oxirgi haftaning qolgan kataklarini to‘ldirish uchun alohida bo‘sh joylar yaratilmaydi. Natijada oxirgi hafta massivi yettitadan kam elementni o‘z ichiga olishi mumkin. Har bir qator bir xil tuzilishga ega bo‘lishi kerak bo‘lsa, sana massivi oxiriga bo‘sh joylar qo‘shish yoki qator va ustun o‘lchamlarini bir xil saqlash uchun CSS joylashuvidan foydalanish mumkin.
Bu misoldan ko‘rinib turibdiki, sana massivi qanday yaratilishi ekrandagi tekislanishga ham ta’sir qiladi. Taqvimdagi tekislanish bilan bog‘liq muammo yuzaga kelganda, faqat CSS’ni emas, balki sana massiviga bo‘sh joylar to‘g‘ri kiritilgan-kiritilmaganini va har bir haftada nechta element borligini ham tekshirish muhim.
5. CSS Grid yordamida taqvim joylashuvini yaratish
Sanalarni hisoblab chiqqach, natijadagi ma’lumotlarni haqiqiy taqvim ko‘rinishida ko‘rsatish kerak. Ushbu amalga oshirishda hafta kunlari qismini sanalar qismidan ajratib, har bir haftani alohida qator qilib joylashtirdim.
<div className="custom-calendar-weekdays">
{WEEKDAYS.map((weekday) => (
<div key={weekday} className="custom-calendar-weekday">
{weekday}
</div>
))}
</div>
<div className="custom-calendar-grid">
{buildWeeks(month).map((week, weekIdx) => (
<div className="custom-calendar-week" key={weekIdx}>
{week.map((date, idx) => {
if (!date) {
return (
<div
key={`empty-${weekIdx}-${idx}`}
className="custom-calendar-day empty"
/>
);
}
return (
<div
key={date.format('YYYY-MM-DD')}
className="custom-calendar-day"
>
<div className="custom-calendar-day-number">
{date.date()}
</div>
</div>
);
})}
</div>
))}
</div>
Hafta kunlari WEEKDAYS massivi bo‘ylab takrorlash orqali, sanalar qismi esa buildWeeks() qaytargan haftalar bo‘yicha guruhlangan massivni takrorlash orqali ko‘rsatiladi. Ma’lumotlar massivi tuzilmasini interfeys tuzilmasiga moslashtirish sana hisob-kitoblari natijalari qanday ko‘rsatilishini tushunishni osonlashtiradi.
Bo‘sh joylarni ham alohida elementlar sifatida ko‘rsatish muhim. Sanasi bo‘lmagan elementlar shunchaki tashlab ketilsa, haqiqiy sanalar oldinga siljib, noto‘g‘ri hafta kunlari ostida ko‘rinishi mumkin. Shu sababli sanalar to‘g‘ri joylarda aks etishi uchun bo‘sh joylar sana kataklari bilan bir xil joylashuv oqimiga amal qilishi kerak.
CSS Grid yordamida yaratilgan taqvimda yettita ustunning bir xil bo‘lishini ta’minlash muhim. Ma’lumotlar miqdori o‘zgarganda ham har bir sana katagining kengligi o‘zgarmasligi, sana raqamlari va ishchilar sonining joylashuvi ham bir xil qolishi kerak.
Chegaralarni ham hisobga olish kerak. Har bir yonma-yon sana katagining barcha tomonlariga chegara qo‘yish chetlarning ustma-ust tushishiga va qalinroq ko‘rinishiga olib kelishi mumkin. Boshqa tomondan, buni tuzatish uchun ayrim chegaralarni olib tashlash oxirgi qator yoki ustunda chiziqlar yo‘qolishiga sabab bo‘lishi mumkin. Shuning uchun chegaralar uchun alohida kataklar yoki butun soha javob berishini belgilab, bo‘sh joylar ham qo‘shilgan barcha elementlar bir xil qoidalarga amal qilishini ta’minlang.
Taqvimning oxirgi qatori ham sana massivi tuzilishiga qarab farq qilishi mumkin. Yuqorida aytilganidek, oxirgi haftaga bo‘sh joylar qo‘shilmasa, bu qatorning ko‘rinishi CSS uni qanday joylashtirishiga bog‘liq bo‘ladi. Shu sababli kerakli joylashuvni yaratish uchun nafaqat CSS’ni, balki sana ma’lumotlari qanday yaratilishini ham ko‘rib chiqish zarur.
Ushbu amalga oshirish taqvim interfeysida ma’lumotlar yaratish va ekran joylashuvini bir-biridan alohida ko‘rib chiqish qiyinligini ko‘rsatdi. Taqvim joylashuvi barqaror bo‘lishi uchun sana hisob-kitoblari aniqligini, bo‘sh joylar to‘g‘ri ifodalanganini, har bir qator va ustun bir xil qoidalarga amal qilishini birgalikda hisobga olish kerak.
6. Amalga oshirishdan olingan saboqlar
Ishchi kuchi taqvimini noldan yaratish orqali taqvim shunchaki sanalarni sanab ko‘rsatadigan interfeys emasligini angladim. Bu sana hisob-kitoblari, ma’lumotlar tuzilmalari, komponentlar dizayni va CSS joylashuvi o‘zaro hamkorlikda ishlashini talab qiladigan funksiyadir.
Day.js yordamida oyning boshlanishi va undagi kunlar sonini aniqlash, hafta kunlarini tekshirish, sanalar yaratish va ularni kerakli formatga o‘tkazish jarayonini aniq ifodalash mumkin. Ayniqsa, startOf(), daysInMonth(), day(), date(), format(), add() va isSame() usullaridan tegishli vazifalar uchun foydalanish sana hisob-kitoblarida kerak bo‘ladigan shartli mantiqni kamaytirishga va kod maqsadini aniqroq ifodalashga yordam berdi.
Sana hisoblash mantiqini alohida funksiyaga ajratib, uni interfeysni yaratuvchi komponentlarga ulash ham texnik xizmat ko‘rsatish nuqtayi nazaridan foydali. Sanalarni yaratishni ularning ko‘rinishidan ajratish har bir qismini alohida ko‘rib chiqish va keyinchalik yangi funksiyalar qo‘shilganda o‘zgarishlar doirasini aniqlashni osonlashtiradi.
Eng muhimi, sana massivi tuzilmasi ekrandagi tekislanishga bevosita ta’sir qilishini angladim. Sanalar to‘g‘ri hafta kunlari ostida ko‘rinishi uchun birinchi kundan oldingi bo‘sh joylarni aniq hisoblash va oxirgi qator shaklini ham hisobga olish kerak. Ekrandagi muammoni hal qilayotganda faqat CSS’ni o‘zgartirish emas, balki ma’lumotlar qanday yaratilganini ham tekshirish zarur.
Ushbu loyiha menga nafaqat dizaynga mos ekran yaratishni, balki interfeysning aniq ishlashi uchun zarur bo‘lgan hisoblash tamoyillari va ma’lumotlar tuzilmalarini ham tushunish imkonini berdi. Barqaror va qo‘llab-quvvatlash oson bo‘lgan kod yozish uchun kutubxona usullaridan foydalanishning o‘zi yetarli emasligini o‘rgandim: har bir usul qanday qiymat qaytarishini va uni amalga oshirishda nima uchun ishlatish kerakligini tushunish muhim.
KKAMJJING