classcard-ui 0.2.1515 → 0.2.1516

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "classcard-ui",
3
- "version": "0.2.1515",
3
+ "version": "0.2.1516",
4
4
  "main": "dist/classcard-ui.umd.min.js",
5
5
  "scripts": {
6
6
  "serve": "vue-cli-service serve",
@@ -81,47 +81,92 @@
81
81
  :key="getDateKey(date)"
82
82
  type="button"
83
83
  :class="{
84
- 'flex h-14 flex-col py-2 px-3 hover:bg-gray-100 focus:z-10': true,
84
+ 'flex h-16 flex-col items-center py-2 px-1 hover:bg-gray-100 focus:z-10': true,
85
85
  'bg-white': isCurrentMonth(date),
86
- 'bg-gray-50': !isCurrentMonth(date),
87
- 'font-semibold': isSelected(date) || isToday(date),
88
- 'text-white': isSelected(date),
89
- 'text-indigo-600': !isSelected(date) && isToday(date),
90
- 'text-gray-900':
91
- !isSelected(date) && isCurrentMonth(date) && !isToday(date),
92
- 'text-gray-500':
93
- !isSelected(date) &&
94
- !isCurrentMonth(date) &&
95
- !isToday(date),
86
+ 'bg-gray-100': !isCurrentMonth(date),
96
87
  }"
97
88
  @click="handleDateClick(date)"
98
89
  >
99
90
  <time
100
91
  :datetime="formatForDateTime(date)"
92
+ class="flex h-5 items-center justify-center rounded-full p-1.5 text-sm"
101
93
  :class="{
102
- 'ml-auto': true,
103
- 'flex h-6 w-6 items-center justify-center rounded-full':
104
- isSelected(date),
105
- 'bg-indigo-600': isSelected(date) && isToday(date),
106
- 'bg-gray-900': isSelected(date) && !isToday(date),
94
+ 'bg-gray-900 font-semibold text-white': isToday(date),
95
+ 'bg-indigo-100 font-semibold text-indigo-700':
96
+ isSelected(date) && !isToday(date),
97
+ 'text-gray-900':
98
+ !isToday(date) &&
99
+ !isSelected(date) &&
100
+ isCurrentMonth(date),
101
+ 'text-gray-400':
102
+ !isToday(date) &&
103
+ !isSelected(date) &&
104
+ !isCurrentMonth(date),
107
105
  }"
108
106
  >{{ getDayFromDate(date) }}</time
109
107
  >
110
108
  <span v-if="eventsCount" class="sr-only"
111
109
  >{{ getEventsCountForDate(date) }} events</span
112
110
  >
113
- <span
114
- v-if="eventsCount && getEventsCountForDate(date) > 0"
115
- class="-mx-0.5 mt-auto flex flex-wrap-reverse"
111
+ <div
112
+ v-if="getDayGlyphs(date)"
113
+ class="mt-auto flex h-2 items-center justify-center gap-1"
116
114
  >
117
115
  <span
118
- class="mx-0.5 mb-1 h-1.5 w-1.5 rounded-full bg-gray-400"
119
- ></span>
120
- </span>
116
+ v-for="(dot, i) in getDayGlyphs(date).dots"
117
+ :key="dot + '-' + i"
118
+ class="flex"
119
+ >
120
+ <c-icon
121
+ v-if="dot === 'cancelled'"
122
+ name="x-mark-solid-v2"
123
+ type="solid"
124
+ viewBox="0 0 24 24"
125
+ class="h-3 w-3 text-red-600"
126
+ cursor-type="cursor-pointer"
127
+ ></c-icon>
128
+ <span
129
+ v-else
130
+ class="block h-2 w-2 rounded-full"
131
+ :class="{
132
+ 'bg-green-600': dot === 'confirmed',
133
+ 'border-2 border-yellow-600': dot === 'awaiting',
134
+ 'bg-gray-400': dot === 'unknown',
135
+ }"
136
+ ></span>
137
+ </span>
138
+ <span
139
+ v-if="getDayGlyphs(date).extra > 0"
140
+ class="text-xs leading-none text-gray-500"
141
+ >+{{ getDayGlyphs(date).extra }}</span
142
+ >
143
+ </div>
121
144
  </button>
122
145
  </div>
123
146
  </div>
124
147
  </div>
148
+
149
+ <div class="flex items-center justify-center gap-x-5 gap-y-1 py-3">
150
+ <div class="flex items-center gap-1.5">
151
+ <span class="block h-2 w-2 rounded-full bg-green-600"></span>
152
+ <p class="text-sm text-gray-500">Confirmed</p>
153
+ </div>
154
+ <div class="flex items-center gap-1.5">
155
+ <span
156
+ class="block h-2 w-2 rounded-full border-2 border-yellow-600"
157
+ ></span>
158
+ <p class="text-sm text-gray-500">Awaiting</p>
159
+ </div>
160
+ <div class="flex items-center gap-1.5">
161
+ <c-icon
162
+ name="x-mark-solid-v2"
163
+ type="solid"
164
+ viewBox="0 0 24 24"
165
+ class="h-4 w-4 text-red-600"
166
+ ></c-icon>
167
+ <p class="text-sm text-gray-500">Cancelled</p>
168
+ </div>
169
+ </div>
125
170
  </div>
126
171
  <slot name="customCardsRender"></slot>
127
172
  <section
@@ -153,9 +198,24 @@
153
198
  :key="event.id"
154
199
  @click="handleEventClick(event)"
155
200
  >
156
- <div class="text-sm text-gray-900">
157
- {{ formatTimeFromDate(event.start) }} -
158
- {{ formatTimeFromDate(event.end) }}
201
+ <div class="flex items-start justify-between gap-2">
202
+ <div class="text-sm text-gray-900">
203
+ {{ formatTimeFromDate(event.start) }} -
204
+ {{ formatTimeFromDate(event.end) }}
205
+ </div>
206
+ <div
207
+ class="flex shrink-0 items-center gap-1 text-sm font-medium"
208
+ :class="statusTag(event).color"
209
+ >
210
+ <c-icon
211
+ :name="statusTag(event).icon"
212
+ type="solid"
213
+ :viewBox="statusTag(event).viewBox"
214
+ class="h-5 w-5 flex-shrink-0"
215
+ :class="statusTag(event).iconColor"
216
+ ></c-icon>
217
+ {{ statusTag(event).label }}
218
+ </div>
159
219
  </div>
160
220
  <div
161
221
  class="mt-1 flex text-sm font-semibold"
@@ -364,7 +424,7 @@ export default {
364
424
  },
365
425
  getEventsCountForDate(date) {
366
426
  let eventsCountObj = this.eventsCount.find((event) =>
367
- dayjs(date).isSame(dayjs(event.date), "day")
427
+ dayjs(date).isSame(dayjs(event.date), "day"),
368
428
  );
369
429
  if (eventsCountObj) {
370
430
  return eventsCountObj.eventsCount;
@@ -372,6 +432,72 @@ export default {
372
432
  return 0;
373
433
  }
374
434
  },
435
+ eventStatus(event) {
436
+ if (event.status === 7) return "cancelled";
437
+ if (event.isConfirmed === 0) return "awaiting";
438
+ return "confirmed";
439
+ },
440
+ statusTag(event) {
441
+ switch (this.eventStatus(event)) {
442
+ case "confirmed":
443
+ return {
444
+ icon: "check-circle-solid",
445
+ viewBox: "0 0 20 20",
446
+ color: "text-green-700",
447
+ iconColor: "text-green-600",
448
+ label: "Confirmed",
449
+ };
450
+ case "awaiting":
451
+ return {
452
+ icon: "clock-solid-v2",
453
+ viewBox: "0 0 24 24",
454
+ color: "text-yellow-700",
455
+ iconColor: "text-yellow-600",
456
+ label: "Awaiting",
457
+ };
458
+ case "cancelled":
459
+ default:
460
+ return {
461
+ icon: "x-mark-solid-v2",
462
+ viewBox: "0 0 24 24",
463
+ color: "text-red-700",
464
+ iconColor: "text-red-600",
465
+ label: "Cancelled",
466
+ };
467
+ }
468
+ },
469
+ getDayGlyphs(date) {
470
+ // Order exceptions first so truncation never hides a cancellation.
471
+ let rank = { cancelled: 0, awaiting: 1, confirmed: 2 };
472
+ // Per-class statuses come from eventsData (spans the visible month), so
473
+ // when it is loaded it is the source of truth — a day with no events
474
+ // gets no glyphs rather than falling back to a count.
475
+ if (this.eventsData) {
476
+ let dayEvents = this.eventsData.filter((event) =>
477
+ dayjs(date).isSame(dayjs(event.start), "day"),
478
+ );
479
+ if (!dayEvents.length) return null;
480
+ let sorted = dayEvents
481
+ .map((event) => this.eventStatus(event))
482
+ .sort((a, b) => rank[a] - rank[b]);
483
+ return {
484
+ dots: sorted.slice(0, sorted.length > 2 ? 1 : 2),
485
+ extra: sorted.length > 2 ? sorted.length - 1 : 0,
486
+ };
487
+ }
488
+ // eventsData unavailable but counts loaded: grey dot per class + +N.
489
+ let entry = this.eventsCount
490
+ ? this.eventsCount.find((event) =>
491
+ dayjs(date).isSame(dayjs(event.date), "day"),
492
+ )
493
+ : null;
494
+ if (!entry || !entry.eventsCount) return null;
495
+ let shown = Math.min(entry.eventsCount, 2);
496
+ return {
497
+ dots: Array(shown).fill("unknown"),
498
+ extra: entry.eventsCount - shown,
499
+ };
500
+ },
375
501
  getActiveStartAndEndDates() {
376
502
  return {
377
503
  startDate: this.daysArray[0],
@@ -380,7 +506,7 @@ export default {
380
506
  },
381
507
  getEventsForDate(date) {
382
508
  return this.eventsData.filter((event) =>
383
- dayjs(date).isSame(dayjs(event.start), "day")
509
+ dayjs(date).isSame(dayjs(event.start), "day"),
384
510
  );
385
511
  },
386
512
  handleEventClick(event) {
@@ -1,3 +1,4 @@
1
+ import dayjs from "dayjs";
1
2
  import { CCalendar } from "../components";
2
3
  import "./utils.css";
3
4
 
@@ -13,4 +14,64 @@ const Template = (args, { argTypes }) => ({
13
14
  template: '<c-calendar v-bind="$props" />',
14
15
  });
15
16
 
17
+ // Demo data lives in the current month so activeDate (dayjs()) lands on it.
18
+ const day = (d) => dayjs().date(d).format("YYYY-MM-DD");
19
+ const at = (d, h) => `${day(d)}T${String(h).padStart(2, "0")}:00:00`;
20
+
21
+ const statusFields = {
22
+ confirmed: { status: 1, isConfirmed: 1 },
23
+ awaiting: { status: 1, isConfirmed: 0 },
24
+ cancelled: { status: 7, isConfirmed: 1 },
25
+ };
26
+ const colors = {
27
+ confirmed: "#047857",
28
+ awaiting: "#1D4ED8",
29
+ cancelled: "#DC2626",
30
+ };
31
+
32
+ // day-of-month -> ordered list of class statuses for that day
33
+ const spec = {
34
+ 3: ["confirmed"],
35
+ 7: ["cancelled", "confirmed"],
36
+ 12: ["awaiting"],
37
+ 13: ["cancelled", "awaiting", "confirmed"],
38
+ 22: ["cancelled", "awaiting", "confirmed", "confirmed", "confirmed", "confirmed", "confirmed"],
39
+ 26: ["cancelled", "cancelled", "confirmed"],
40
+ };
41
+
42
+ let id = 0;
43
+ const eventsData = Object.entries(spec).flatMap(([d, list]) =>
44
+ list.map((st, i) => ({
45
+ id: ++id,
46
+ start: at(+d, 9 + i),
47
+ end: at(+d, 10 + i),
48
+ title: `${st[0].toUpperCase() + st.slice(1)} class`,
49
+ color: colors[st],
50
+ duration: 60,
51
+ subject: "Intermediate Course",
52
+ staff: "Jyoti Karna",
53
+ ...statusFields[st],
54
+ }))
55
+ );
56
+
57
+ // Real API shape: only date + count, no statuses.
58
+ const eventsCount = Object.entries(spec).map(([d, list]) => ({
59
+ date: day(+d),
60
+ eventsCount: list.length,
61
+ }));
62
+
16
63
  export const Default = Template.bind({});
64
+ Default.args = {
65
+ id: "cal",
66
+ value: day(13),
67
+ eventsCount,
68
+ eventsData,
69
+ };
70
+
71
+ // Status details failed but counts loaded: grey dot per class + +N.
72
+ export const StatusUnavailable = Template.bind({});
73
+ StatusUnavailable.args = {
74
+ id: "cal-no-status",
75
+ value: day(13),
76
+ eventsCount,
77
+ };