Spaces:
Paused
Paused
| /** | |
| * src/composables/useHeatmap.js | |
| * ────────────────────────────── | |
| * SRP: Generate calendar heatmap data for current month. | |
| * Returns an array of day objects for rendering. | |
| */ | |
| import { computed } from 'vue' | |
| /** | |
| * @param {Object} activityMap - { 'YYYY-MM-DD': count } from API | |
| */ | |
| export function useHeatmap(activityMap = {}) { | |
| const today = new Date() | |
| const calendarData = computed(() => { | |
| const year = today.getFullYear() | |
| const month = today.getMonth() | |
| const daysInMonth = new Date(year, month + 1, 0).getDate() | |
| const todayDate = today.getDate() | |
| // Day of week for 1st of month (0=Sun → adjust to Mon-start) | |
| let firstDow = new Date(year, month, 1).getDay() | |
| firstDow = firstDow === 0 ? 6 : firstDow - 1 // Mon=0 | |
| const days = [] | |
| // Empty cells before day 1 | |
| for (let i = 0; i < firstDow; i++) { | |
| days.push({ day: null, empty: true, level: 0 }) | |
| } | |
| // Day cells | |
| for (let d = 1; d <= daysInMonth; d++) { | |
| const key = `${year}-${String(month + 1).padStart(2, '0')}-${String(d).padStart(2, '0')}` | |
| const count = activityMap[key] ?? 0 | |
| let level = 0 | |
| if (count >= 3) level = 3 | |
| else if (count === 2) level = 2 | |
| else if (count === 1) level = 1 | |
| days.push({ | |
| day: d, | |
| empty: false, | |
| isToday: d === todayDate, | |
| level, | |
| count, | |
| key, | |
| }) | |
| } | |
| return days | |
| }) | |
| const monthLabel = computed(() => { | |
| return today.toLocaleDateString('vi-VN', { month: 'long', year: 'numeric' }) | |
| }) | |
| return { calendarData, monthLabel } | |
| } | |