@underverse-ui/underverse 1.0.3 → 1.0.5

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/dist/index.cjs CHANGED
@@ -733,6 +733,8 @@ var defaultTranslations = {
733
733
  month: "Month",
734
734
  week: "Week",
735
735
  day: "Day",
736
+ sprint: "Sprint",
737
+ sprints: "{n} sprints",
736
738
  resourcesHeader: "Resources",
737
739
  expandGroup: "Expand group",
738
740
  collapseGroup: "Collapse group",
@@ -809,6 +811,8 @@ var defaultTranslations = {
809
811
  month: "Th\xE1ng",
810
812
  week: "Tu\u1EA7n",
811
813
  day: "Ng\xE0y",
814
+ sprint: "Sprint",
815
+ sprints: "{n} sprint",
812
816
  resourcesHeader: "T\xE0i nguy\xEAn",
813
817
  expandGroup: "M\u1EDF nh\xF3m",
814
818
  collapseGroup: "Thu g\u1ECDn nh\xF3m",
@@ -885,6 +889,8 @@ var defaultTranslations = {
885
889
  month: "\uC6D4",
886
890
  week: "\uC8FC",
887
891
  day: "\uC77C",
892
+ sprint: "\uC2A4\uD504\uB9B0\uD2B8",
893
+ sprints: "{n} \uC2A4\uD504\uB9B0\uD2B8",
888
894
  resourcesHeader: "\uB9AC\uC18C\uC2A4",
889
895
  expandGroup: "\uADF8\uB8F9 \uD3BC\uCE58\uAE30",
890
896
  collapseGroup: "\uADF8\uB8F9 \uC811\uAE30",
@@ -961,6 +967,8 @@ var defaultTranslations = {
961
967
  month: "\u6708",
962
968
  week: "\u9031",
963
969
  day: "\u65E5",
970
+ sprint: "\u30B9\u30D7\u30EA\u30F3\u30C8",
971
+ sprints: "{n} \u30B9\u30D7\u30EA\u30F3\u30C8",
964
972
  resourcesHeader: "\u30EA\u30BD\u30FC\u30B9",
965
973
  expandGroup: "\u30B0\u30EB\u30FC\u30D7\u3092\u5C55\u958B",
966
974
  collapseGroup: "\u30B0\u30EB\u30FC\u30D7\u3092\u6298\u308A\u305F\u305F\u3080",
@@ -8571,6 +8579,10 @@ function startOfZonedMonth(date, timeZone) {
8571
8579
  const p = getZonedParts(date, timeZone);
8572
8580
  return new Date(zonedTimeToUtcMs({ year: p.year, month: p.month, day: 1, hour: 0, minute: 0, second: 0 }, timeZone));
8573
8581
  }
8582
+ function startOfZonedYear(date, timeZone) {
8583
+ const p = getZonedParts(date, timeZone);
8584
+ return new Date(zonedTimeToUtcMs({ year: p.year, month: 1, day: 1, hour: 0, minute: 0, second: 0 }, timeZone));
8585
+ }
8574
8586
  function addZonedDays(date, days, timeZone) {
8575
8587
  const p = getZonedParts(date, timeZone);
8576
8588
  return new Date(zonedTimeToUtcMs({ ...p, day: p.day + days }, timeZone));
@@ -8583,6 +8595,9 @@ function addZonedMonths(date, months, timeZone) {
8583
8595
  const clampedDay = Math.min(p.day, daysInTargetMonth);
8584
8596
  return new Date(zonedTimeToUtcMs({ year: next.year, month: next.month, day: clampedDay, hour: p.hour, minute: p.minute, second: p.second }, timeZone));
8585
8597
  }
8598
+ function addZonedYears(date, years, timeZone) {
8599
+ return addZonedMonths(date, years * 12, timeZone);
8600
+ }
8586
8601
  function startOfZonedWeek(date, weekStartsOn, timeZone) {
8587
8602
  const p = getZonedParts(date, timeZone);
8588
8603
  const weekday = new Date(Date.UTC(p.year, p.month - 1, p.day)).getUTCDay();
@@ -8821,6 +8836,13 @@ function defaultSlotHeader(slotStart, view, locale, timeZone) {
8821
8836
  if (view === "day") {
8822
8837
  return getDtf(locale, timeZone, { hour: "2-digit", minute: "2-digit", hourCycle: "h23" }).format(slotStart);
8823
8838
  }
8839
+ if (view === "sprint") {
8840
+ const { week } = getIsoWeekInfo(slotStart, timeZone);
8841
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "inline-flex flex-col items-center leading-tight", children: [
8842
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70", children: "S" }),
8843
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("span", { className: "text-sm font-semibold text-foreground", children: String(week).padStart(2, "0") })
8844
+ ] });
8845
+ }
8824
8846
  const weekday = getDtf(locale, timeZone, { weekday: "short" }).format(slotStart);
8825
8847
  const day = getDtf(locale, timeZone, { day: "numeric" }).format(slotStart);
8826
8848
  return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("span", { className: "inline-flex flex-col items-center leading-tight", children: [
@@ -8881,7 +8903,10 @@ function getGroupResourceCounts(resources) {
8881
8903
  function computeSlotStarts(args) {
8882
8904
  const { view, date, timeZone, weekStartsOn, dayTimeStepMinutes, dayRangeMode, workHours } = args;
8883
8905
  const baseDayStart = startOfZonedDay(date, timeZone);
8884
- const start = view === "month" ? startOfZonedMonth(date, timeZone) : view === "week" ? startOfZonedWeek(date, weekStartsOn, timeZone) : baseDayStart;
8906
+ const start = view === "month" ? startOfZonedMonth(date, timeZone) : view === "week" ? startOfZonedWeek(date, weekStartsOn, timeZone) : view === "sprint" ? (() => {
8907
+ const yearStart = startOfZonedYear(date, timeZone);
8908
+ return startOfZonedWeek(yearStart, weekStartsOn, timeZone);
8909
+ })() : baseDayStart;
8885
8910
  if (view === "day") {
8886
8911
  const step = Math.max(5, Math.min(240, Math.trunc(dayTimeStepMinutes)));
8887
8912
  const stepMs = step * 6e4;
@@ -8898,6 +8923,21 @@ function computeSlotStarts(args) {
8898
8923
  }
8899
8924
  return { start: start2, end: end3, slotStarts: slotStarts2 };
8900
8925
  }
8926
+ if (view === "sprint") {
8927
+ const yearStart = startOfZonedYear(date, timeZone);
8928
+ const nextYearStart = startOfZonedYear(addZonedMonths(yearStart, 12, timeZone), timeZone);
8929
+ const lastDayOfYear = addZonedDays(nextYearStart, -1, timeZone);
8930
+ const lastSlotStart = startOfZonedWeek(lastDayOfYear, weekStartsOn, timeZone);
8931
+ const end2 = addZonedDays(lastSlotStart, 7, timeZone);
8932
+ const slotStarts2 = [];
8933
+ let cur2 = start;
8934
+ let guard2 = 0;
8935
+ while (cur2.getTime() < end2.getTime() && guard2++ < 60) {
8936
+ slotStarts2.push(cur2);
8937
+ cur2 = addZonedDays(cur2, 7, timeZone);
8938
+ }
8939
+ return { start, end: end2, slotStarts: slotStarts2 };
8940
+ }
8901
8941
  const end = view === "month" ? startOfZonedMonth(addZonedMonths(start, 1, timeZone), timeZone) : addZonedDays(start, 7, timeZone);
8902
8942
  const slotStarts = [];
8903
8943
  let cur = start;
@@ -8946,7 +8986,8 @@ var import_jsx_runtime34 = require("react/jsx-runtime");
8946
8986
  var VIEW_ICONS = {
8947
8987
  month: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.CalendarRange, { className: "h-4 w-4" }),
8948
8988
  week: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.CalendarDays, { className: "h-4 w-4" }),
8949
- day: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.Calendar, { className: "h-4 w-4" })
8989
+ day: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.Calendar, { className: "h-4 w-4" }),
8990
+ sprint: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.CalendarDays, { className: "h-4 w-4" })
8950
8991
  };
8951
8992
  function CalendarTimelineHeader(props) {
8952
8993
  const {
@@ -8957,6 +8998,8 @@ function CalendarTimelineHeader(props) {
8957
8998
  newEventDisabled,
8958
8999
  onNewEventClick,
8959
9000
  activeView,
9001
+ availableViews,
9002
+ showResourceColumn,
8960
9003
  sizeConfig,
8961
9004
  navigate,
8962
9005
  now,
@@ -8968,6 +9011,12 @@ function CalendarTimelineHeader(props) {
8968
9011
  headerRef,
8969
9012
  slotHeaderNodes
8970
9013
  } = props;
9014
+ const resolvedAvailableViews = React28.useMemo(
9015
+ () => availableViews?.length ? availableViews : ["month", "week", "day", "sprint"],
9016
+ [availableViews]
9017
+ );
9018
+ const showViewSwitcher = resolvedAvailableViews.length > 1;
9019
+ const showLeftColumn = showResourceColumn ?? true;
8971
9020
  const dt = useTranslations("DateTimePicker");
8972
9021
  const locale = useLocale();
8973
9022
  const [todayOpen, setTodayOpen] = React28.useState(false);
@@ -9026,7 +9075,7 @@ function CalendarTimelineHeader(props) {
9026
9075
  onApplyDateTime(tempDate);
9027
9076
  setTodayOpen(false);
9028
9077
  }, [onApplyDateTime, tempDate]);
9029
- return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "sticky top-0 z-30 bg-linear-to-b from-background via-background to-background/95 border-b border-border/40 backdrop-blur-xl", children: [
9078
+ return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "sticky top-0 z-30 bg-linear-to-b from-card via-card to-card/95 border-b border-border/40 backdrop-blur-xl", children: [
9030
9079
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: cn("flex items-center justify-between gap-4", sizeConfig.headerPaddingClass), children: [
9031
9080
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-1.5 min-w-0", children: [
9032
9081
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center bg-muted/40 rounded-full p-1 gap-0.5", children: [
@@ -9037,7 +9086,7 @@ function CalendarTimelineHeader(props) {
9037
9086
  size: "icon",
9038
9087
  onClick: () => navigate(-1),
9039
9088
  "aria-label": labels.prev,
9040
- className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-background/80 transition-all duration-200"),
9089
+ className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-card/80 transition-all duration-200"),
9041
9090
  children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.ChevronLeft, { className: "h-4 w-4" })
9042
9091
  }
9043
9092
  ),
@@ -9052,7 +9101,7 @@ function CalendarTimelineHeader(props) {
9052
9101
  {
9053
9102
  variant: "ghost",
9054
9103
  size: "sm",
9055
- className: cn(sizeConfig.controlButtonTextClass, "rounded-full hover:bg-background/80 font-medium transition-all duration-200"),
9104
+ className: cn(sizeConfig.controlButtonTextClass, "rounded-full hover:bg-card/80 font-medium transition-all duration-200"),
9056
9105
  children: labels.today
9057
9106
  }
9058
9107
  ),
@@ -9132,7 +9181,7 @@ function CalendarTimelineHeader(props) {
9132
9181
  size: "icon",
9133
9182
  onClick: () => navigate(1),
9134
9183
  "aria-label": labels.next,
9135
- className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-background/80 transition-all duration-200"),
9184
+ className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-card/80 transition-all duration-200"),
9136
9185
  children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.ChevronRight, { className: "h-4 w-4" })
9137
9186
  }
9138
9187
  )
@@ -9152,7 +9201,7 @@ function CalendarTimelineHeader(props) {
9152
9201
  children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "hidden sm:inline", children: newEventLabel })
9153
9202
  }
9154
9203
  ) : null,
9155
- /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex items-center bg-muted/40 rounded-full p-1 gap-0.5", children: ["month", "week", "day"].map((v) => /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
9204
+ showViewSwitcher ? /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { className: "flex items-center bg-muted/40 rounded-full p-1 gap-0.5", children: resolvedAvailableViews.map((v) => /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
9156
9205
  Button_default,
9157
9206
  {
9158
9207
  variant: activeView === v ? "default" : "ghost",
@@ -9161,7 +9210,7 @@ function CalendarTimelineHeader(props) {
9161
9210
  className: cn(
9162
9211
  sizeConfig.controlButtonTextClass,
9163
9212
  "rounded-full font-medium transition-all duration-200 gap-1.5",
9164
- activeView === v ? "bg-primary text-primary-foreground shadow-sm shadow-primary/25" : "hover:bg-background/80 text-muted-foreground hover:text-foreground"
9213
+ activeView === v ? "bg-primary text-primary-foreground shadow-sm shadow-primary/25" : "hover:bg-card/80 text-muted-foreground hover:text-foreground"
9165
9214
  ),
9166
9215
  children: [
9167
9216
  VIEW_ICONS[v],
@@ -9169,14 +9218,14 @@ function CalendarTimelineHeader(props) {
9169
9218
  ]
9170
9219
  },
9171
9220
  v
9172
- )) })
9221
+ )) }) : null
9173
9222
  ] })
9174
9223
  ] }),
9175
9224
  /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex border-t border-border/20", children: [
9176
- /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
9225
+ showLeftColumn ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
9177
9226
  "div",
9178
9227
  {
9179
- className: "shrink-0 border-r border-border/30 bg-muted/20 flex items-center justify-center relative group/uv-ct-top-left",
9228
+ className: "shrink-0 border-r border-border/30 bg-card/60 flex items-center justify-center relative group/uv-ct-top-left",
9180
9229
  style: { width: effectiveResourceColumnWidth, minWidth: effectiveResourceColumnWidth },
9181
9230
  children: [
9182
9231
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "text-xs font-medium text-muted-foreground/70 uppercase tracking-wider", children: resourcesHeaderLabel }),
@@ -9202,7 +9251,7 @@ function CalendarTimelineHeader(props) {
9202
9251
  ) : null
9203
9252
  ]
9204
9253
  }
9205
- ),
9254
+ ) : null,
9206
9255
  /* @__PURE__ */ (0, import_jsx_runtime34.jsx)("div", { ref: headerRef, className: "flex-1 min-w-0 overflow-x-auto overflow-y-hidden scrollbar-none", children: slotHeaderNodes })
9207
9256
  ] })
9208
9257
  ] });
@@ -9231,7 +9280,7 @@ function DefaultGroupRow(props) {
9231
9280
  "span",
9232
9281
  {
9233
9282
  className: cn(
9234
- "inline-flex items-center justify-center w-5 h-5 rounded-md bg-background/60 transition-transform duration-200",
9283
+ "inline-flex items-center justify-center w-5 h-5 rounded-md bg-card/60 transition-transform duration-200",
9235
9284
  collapsed ? "" : "rotate-180"
9236
9285
  ),
9237
9286
  children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react19.ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
@@ -9252,7 +9301,7 @@ function ResourceRowCell(props) {
9252
9301
  "div",
9253
9302
  {
9254
9303
  className: cn(
9255
- "h-full w-full flex items-center border-b border-border/30 bg-linear-to-r from-background to-background/95 relative",
9304
+ "h-full w-full flex items-center border-b border-border/30 bg-linear-to-r from-card to-card/95 relative",
9256
9305
  sizeConfig.resourceRowClass,
9257
9306
  "hover:from-muted/30 hover:to-muted/10 transition-all duration-200 group/uv-ct-row-header"
9258
9307
  ),
@@ -9312,7 +9361,7 @@ var CalendarTimelineGridOverlay = React29.memo(function CalendarTimelineGridOver
9312
9361
  if (showToday && idx === todaySlotIdx) return null;
9313
9362
  const left = slotLefts[idx] ?? 0;
9314
9363
  const width = slotWidths[idx] ?? 0;
9315
- return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "absolute top-0 h-full bg-destructive/5", style: { left, width }, "aria-hidden": true }, `we_${idx}`);
9364
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "absolute top-0 h-full bg-muted/20", style: { left, width }, "aria-hidden": true }, `we_${idx}`);
9316
9365
  }) : null,
9317
9366
  showToday ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
9318
9367
  "div",
@@ -9375,7 +9424,8 @@ function useTimelineSlots(args) {
9375
9424
  dayRangeMode,
9376
9425
  workHours,
9377
9426
  resolvedNow,
9378
- formatters
9427
+ formatters,
9428
+ dueDateSprint
9379
9429
  } = args;
9380
9430
  const { slots, range } = React31.useMemo(() => {
9381
9431
  const { start, end, slotStarts: slotStarts2 } = computeSlotStarts({
@@ -9388,17 +9438,89 @@ function useTimelineSlots(args) {
9388
9438
  workHours
9389
9439
  });
9390
9440
  const todayStart = startOfZonedDay(resolvedNow, resolvedTimeZone).getTime();
9391
- const slotItems = slotStarts2.map((s) => ({
9441
+ const nowMs = resolvedNow.getTime();
9442
+ const sprintDefs = (() => {
9443
+ if (activeView !== "sprint") return [];
9444
+ if (!dueDateSprint) return [];
9445
+ const out = [];
9446
+ for (const v of Object.values(dueDateSprint)) {
9447
+ const startInput = v.range_date?.start;
9448
+ const endInput = v.range_date?.end;
9449
+ if (startInput == null || endInput == null) continue;
9450
+ const startRaw = toDate2(startInput);
9451
+ const endRaw = toDate2(endInput);
9452
+ if (Number.isNaN(startRaw.getTime()) || Number.isNaN(endRaw.getTime())) continue;
9453
+ const startMs = startOfZonedDay(startRaw, resolvedTimeZone).getTime();
9454
+ const endMs = startOfZonedDay(endRaw, resolvedTimeZone).getTime();
9455
+ if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) continue;
9456
+ if (endMs <= startMs) continue;
9457
+ out.push({ startMs, endMs, title: v.title });
9458
+ }
9459
+ out.sort((a, b) => a.startMs - b.startMs);
9460
+ return out;
9461
+ })();
9462
+ const sprintRangeText = (() => {
9463
+ if (activeView !== "sprint") return null;
9464
+ const df = getDtf(resolvedLocale, resolvedTimeZone, { month: "short", day: "numeric" });
9465
+ return (startMs, endMs) => {
9466
+ const a = df.format(new Date(startMs));
9467
+ const b = df.format(new Date(endMs - 1));
9468
+ return a === b ? a : `${a}\u2013${b}`;
9469
+ };
9470
+ })();
9471
+ const matchSprintDef = (slotStart, idx) => {
9472
+ if (activeView !== "sprint") return null;
9473
+ if (sprintDefs.length === 0) return null;
9474
+ const sMs = startOfZonedDay(slotStart, resolvedTimeZone).getTime();
9475
+ const byRange = sprintDefs.find((d) => sMs >= d.startMs && sMs < d.endMs) ?? null;
9476
+ return byRange ?? sprintDefs[idx] ?? null;
9477
+ };
9478
+ const slotItems = slotStarts2.map((s, idx) => ({
9392
9479
  start: s,
9393
- label: formatters?.slotHeader?.(s, { view: activeView, locale: resolvedLocale, timeZone: resolvedTimeZone }) ?? defaultSlotHeader(s, activeView, resolvedLocale, resolvedTimeZone),
9394
- isToday: startOfZonedDay(s, resolvedTimeZone).getTime() === todayStart,
9395
- isWeekend: activeView === "day" ? false : (() => {
9480
+ label: formatters?.slotHeader?.(s, { view: activeView, locale: resolvedLocale, timeZone: resolvedTimeZone }) ?? (() => {
9481
+ if (activeView !== "sprint") return defaultSlotHeader(s, activeView, resolvedLocale, resolvedTimeZone);
9482
+ const match = matchSprintDef(s, idx);
9483
+ if (match && sprintRangeText) {
9484
+ const rangeText = sprintRangeText(match.startMs, match.endMs);
9485
+ return React31.createElement(
9486
+ "span",
9487
+ { className: "inline-flex flex-col items-center leading-tight" },
9488
+ React31.createElement("span", { className: "text-[11px] font-semibold text-foreground truncate max-w-[8rem]" }, match.title),
9489
+ React31.createElement("span", { className: "text-[10px] font-medium text-muted-foreground/70" }, rangeText)
9490
+ );
9491
+ }
9492
+ return React31.createElement(
9493
+ "span",
9494
+ { className: "inline-flex flex-col items-center leading-tight" },
9495
+ React31.createElement("span", { className: "text-[10px] font-medium uppercase tracking-wider text-muted-foreground/70" }, "S"),
9496
+ React31.createElement("span", { className: "text-sm font-semibold text-foreground" }, String(idx + 1).padStart(2, "0"))
9497
+ );
9498
+ })(),
9499
+ isToday: (() => {
9500
+ if (activeView !== "sprint") return startOfZonedDay(s, resolvedTimeZone).getTime() === todayStart;
9501
+ const match = matchSprintDef(s, idx);
9502
+ const sprintEndMs = match ? match.endMs : addZonedDays(s, 7, resolvedTimeZone).getTime();
9503
+ return nowMs >= s.getTime() && nowMs < sprintEndMs;
9504
+ })(),
9505
+ isWeekend: activeView === "day" || activeView === "sprint" ? false : (() => {
9396
9506
  const wd = getZonedWeekday(s, resolvedTimeZone);
9397
9507
  return wd === 0 || wd === 6;
9398
9508
  })()
9399
9509
  }));
9400
9510
  return { slots: slotItems, range: { start, end } };
9401
- }, [activeView, activeDate, dayRangeMode, dayTimeStepMinutes, formatters, resolvedLocale, resolvedNow, resolvedTimeZone, weekStartsOn, workHours]);
9511
+ }, [
9512
+ activeView,
9513
+ activeDate,
9514
+ dayRangeMode,
9515
+ dayTimeStepMinutes,
9516
+ dueDateSprint,
9517
+ formatters,
9518
+ resolvedLocale,
9519
+ resolvedNow,
9520
+ resolvedTimeZone,
9521
+ weekStartsOn,
9522
+ workHours
9523
+ ]);
9402
9524
  const slotStarts = React31.useMemo(() => slots.map((s) => s.start), [slots]);
9403
9525
  const todaySlotIdx = React31.useMemo(() => slots.findIndex((s) => s.isToday), [slots]);
9404
9526
  const weekendSlotIdxs = React31.useMemo(() => {
@@ -9685,9 +9807,11 @@ function CalendarTimeline({
9685
9807
  eventSheetOpen,
9686
9808
  defaultEventSheetOpen,
9687
9809
  onEventSheetOpenChange,
9810
+ onlyView,
9688
9811
  view,
9689
9812
  defaultView = "month",
9690
9813
  onViewChange,
9814
+ dueDateSprint,
9691
9815
  date,
9692
9816
  defaultDate,
9693
9817
  onDateChange,
@@ -9700,6 +9824,7 @@ function CalendarTimeline({
9700
9824
  groupCollapsed,
9701
9825
  defaultGroupCollapsed,
9702
9826
  onGroupCollapsedChange,
9827
+ hideResourceColumn,
9703
9828
  resourceColumnWidth,
9704
9829
  defaultResourceColumnWidth,
9705
9830
  onResourceColumnWidthChange,
@@ -9719,6 +9844,8 @@ function CalendarTimeline({
9719
9844
  adaptiveSlotWidths,
9720
9845
  dayEventStyle = "span",
9721
9846
  dayEventMaxWidth,
9847
+ monthEventStyle = "span",
9848
+ monthEventMaxWidth,
9722
9849
  dayTimeStepMinutes = 60,
9723
9850
  enableEventTooltips = true,
9724
9851
  dayHeaderMode = "full",
@@ -9772,6 +9899,7 @@ function CalendarTimeline({
9772
9899
  },
9773
9900
  [isControlledEventSheetOpen, onEventSheetOpenChange, setSelectedEventId]
9774
9901
  );
9902
+ const showResourceColumn = !hideResourceColumn;
9775
9903
  const sizeConfig = React32.useMemo(() => getSizeConfig(size), [size]);
9776
9904
  const densityClass = sizeConfig.densityClass;
9777
9905
  const eventHeight = sizeConfig.eventHeight;
@@ -9781,16 +9909,18 @@ function CalendarTimeline({
9781
9909
  const cfg = enableLayoutResize;
9782
9910
  if (!cfg) return false;
9783
9911
  if (isViewOnly) return false;
9912
+ if (!showResourceColumn) return false;
9784
9913
  if (cfg === true) return true;
9785
9914
  return cfg.column !== false;
9786
- }, [enableLayoutResize, isViewOnly]);
9915
+ }, [enableLayoutResize, isViewOnly, showResourceColumn]);
9787
9916
  const canResizeRow = React32.useMemo(() => {
9788
9917
  const cfg = enableLayoutResize;
9789
9918
  if (!cfg) return false;
9790
9919
  if (isViewOnly) return false;
9920
+ if (!showResourceColumn) return false;
9791
9921
  if (cfg === true) return true;
9792
9922
  return cfg.row !== false;
9793
- }, [enableLayoutResize, isViewOnly]);
9923
+ }, [enableLayoutResize, isViewOnly, showResourceColumn]);
9794
9924
  const isControlledResourceColumnWidth = resourceColumnWidth !== void 0;
9795
9925
  const [internalResourceColumnWidth, setInternalResourceColumnWidth] = React32.useState(() => {
9796
9926
  const init = defaultResourceColumnWidth ?? sizeConfig.resourceColumnWidth;
@@ -9801,7 +9931,7 @@ function CalendarTimeline({
9801
9931
  if (defaultResourceColumnWidth == null) return;
9802
9932
  setInternalResourceColumnWidth(defaultResourceColumnWidth);
9803
9933
  }, [defaultResourceColumnWidth, isControlledResourceColumnWidth]);
9804
- const effectiveResourceColumnWidth = isControlledResourceColumnWidth ? resourceColumnWidth : internalResourceColumnWidth;
9934
+ const effectiveResourceColumnWidth = showResourceColumn ? isControlledResourceColumnWidth ? resourceColumnWidth : internalResourceColumnWidth : 0;
9805
9935
  const isControlledRowHeight = rowHeight !== void 0;
9806
9936
  const [internalRowHeight, setInternalRowHeight] = React32.useState(() => defaultRowHeight ?? sizeConfig.rowHeight);
9807
9937
  React32.useEffect(() => {
@@ -9810,14 +9940,29 @@ function CalendarTimeline({
9810
9940
  setInternalRowHeight(defaultRowHeight);
9811
9941
  }, [defaultRowHeight, isControlledRowHeight]);
9812
9942
  const effectiveRowHeight = isControlledRowHeight ? rowHeight : internalRowHeight;
9813
- const effectiveSlotMinWidth = slotMinWidth ?? sizeConfig.slotMinWidth;
9943
+ const baseSlotMinWidth = slotMinWidth ?? sizeConfig.slotMinWidth;
9814
9944
  const colMin = minResourceColumnWidth ?? 160;
9815
9945
  const colMax = maxResourceColumnWidth ?? 520;
9816
9946
  const rowMin = minRowHeight ?? 36;
9817
9947
  const rowMax = maxRowHeight ?? 120;
9948
+ const availableViews = React32.useMemo(
9949
+ () => onlyView ? [onlyView] : ["month", "week", "day", "sprint"],
9950
+ [onlyView]
9951
+ );
9818
9952
  const isControlledView = view !== void 0;
9819
- const [internalView, setInternalView] = React32.useState(defaultView);
9820
- const activeView = isControlledView ? view : internalView;
9953
+ const [internalView, setInternalView] = React32.useState(() => onlyView ?? defaultView);
9954
+ const activeView = onlyView ? onlyView : isControlledView ? view : internalView;
9955
+ const effectiveSlotMinWidth = React32.useMemo(() => {
9956
+ if (slotMinWidth == null) {
9957
+ if (activeView === "month" && monthEventStyle === "compact") {
9958
+ return clamp4(Math.round(sizeConfig.slotMinWidth * 0.55), 32, sizeConfig.slotMinWidth);
9959
+ }
9960
+ if (activeView === "sprint") {
9961
+ return clamp4(Math.round(sizeConfig.slotMinWidth * 0.4), 18, 48);
9962
+ }
9963
+ }
9964
+ return baseSlotMinWidth;
9965
+ }, [activeView, baseSlotMinWidth, monthEventStyle, sizeConfig.slotMinWidth, slotMinWidth]);
9821
9966
  const isControlledDate = date !== void 0;
9822
9967
  const [internalDate, setInternalDate] = React32.useState(() => defaultDate ?? /* @__PURE__ */ new Date());
9823
9968
  const activeDate = isControlledDate ? date : internalDate;
@@ -9830,6 +9975,7 @@ function CalendarTimeline({
9830
9975
  month: labels?.month ?? t("month"),
9831
9976
  week: labels?.week ?? t("week"),
9832
9977
  day: labels?.day ?? t("day"),
9978
+ sprint: labels?.sprint ?? t("sprint"),
9833
9979
  newEvent: labels?.newEvent ?? t("newEvent"),
9834
9980
  createEventTitle: labels?.createEventTitle ?? t("createEventTitle"),
9835
9981
  create: labels?.create ?? t("create"),
@@ -9846,10 +9992,11 @@ function CalendarTimeline({
9846
9992
  );
9847
9993
  const setView = React32.useCallback(
9848
9994
  (next) => {
9995
+ if (onlyView) return;
9849
9996
  if (!isControlledView) setInternalView(next);
9850
9997
  onViewChange?.(next);
9851
9998
  },
9852
- [isControlledView, onViewChange]
9999
+ [isControlledView, onViewChange, onlyView]
9853
10000
  );
9854
10001
  const setDate = React32.useCallback(
9855
10002
  (next) => {
@@ -9869,6 +10016,10 @@ function CalendarTimeline({
9869
10016
  setDate(addZonedDays(base, dir * 7, resolvedTimeZone));
9870
10017
  return;
9871
10018
  }
10019
+ if (activeView === "sprint") {
10020
+ setDate(addZonedYears(base, dir, resolvedTimeZone));
10021
+ return;
10022
+ }
9872
10023
  setDate(addZonedDays(base, dir, resolvedTimeZone));
9873
10024
  },
9874
10025
  [activeDate, activeView, resolvedTimeZone, setDate]
@@ -9894,7 +10045,8 @@ function CalendarTimeline({
9894
10045
  dayRangeMode,
9895
10046
  workHours,
9896
10047
  resolvedNow,
9897
- formatters
10048
+ formatters,
10049
+ dueDateSprint
9898
10050
  });
9899
10051
  React32.useEffect(() => {
9900
10052
  onRangeChange?.(range);
@@ -9958,7 +10110,7 @@ function CalendarTimeline({
9958
10110
  setEventSheetOpen(false);
9959
10111
  }
9960
10112
  }, [activeEventSheetOpen, activeSelectedEventId, effectiveEnableEventSheet, selectedEvent, setEventSheetOpen]);
9961
- useHorizontalScrollSync({ bodyRef, headerRef, leftRef });
10113
+ useHorizontalScrollSync({ bodyRef, headerRef, leftRef: showResourceColumn ? leftRef : void 0 });
9962
10114
  const virt = virtualization?.enabled;
9963
10115
  const overscan = virtualization?.overscan ?? 8;
9964
10116
  const isControlledRowHeights = rowHeights !== void 0;
@@ -10140,9 +10292,15 @@ function CalendarTimeline({
10140
10292
  const rangeText = ya === yb ? `${a} \u2013 ${b}, ${ya}` : `${a}, ${ya} \u2013 ${b}, ${yb}`;
10141
10293
  return `${l.week} ${week} \u2022 ${rangeText}`;
10142
10294
  }
10295
+ if (activeView === "sprint") {
10296
+ const fmtYear = getDtf(resolvedLocale, resolvedTimeZone, { year: "numeric" });
10297
+ const yearText = fmtYear.format(activeDate);
10298
+ const count = Math.max(0, slots.length);
10299
+ return `${l.sprint} \u2022 ${yearText} \u2022 ${t("sprints", { n: count })}`;
10300
+ }
10143
10301
  const fmt = getDtf(resolvedLocale, resolvedTimeZone, { weekday: "long", year: "numeric", month: "long", day: "numeric" });
10144
10302
  return fmt.format(range.start);
10145
- }, [activeDate, activeView, formatters, l.week, range.end, range.start, resolvedLocale, resolvedTimeZone]);
10303
+ }, [activeDate, activeView, formatters, l.sprint, l.week, range.end, range.start, resolvedLocale, resolvedTimeZone, slots.length, t]);
10146
10304
  const createMode = interactions?.createMode ?? "drag";
10147
10305
  const canCreate = !isViewOnly && (interactions?.creatable ?? false) && !!onCreateEvent;
10148
10306
  const [createOpen, setCreateOpen] = React32.useState(false);
@@ -10157,11 +10315,54 @@ function CalendarTimeline({
10157
10315
  disabled: r.disabled ?? false
10158
10316
  }));
10159
10317
  }, [resources]);
10160
- const slotPickerLabel = React32.useMemo(() => {
10318
+ const formatCreateBoundaryLabel = React32.useMemo(() => {
10161
10319
  const timeFmt = getDtf(resolvedLocale, resolvedTimeZone, { hour: "2-digit", minute: "2-digit", hourCycle: "h23" });
10162
10320
  const dayFmt = getDtf(resolvedLocale, resolvedTimeZone, { weekday: "short", month: "short", day: "numeric" });
10163
- return (d) => activeView === "day" ? timeFmt.format(d) : dayFmt.format(d);
10164
- }, [activeView, resolvedLocale, resolvedTimeZone]);
10321
+ const yearFmt = getDtf(resolvedLocale, resolvedTimeZone, { year: "numeric" });
10322
+ const sprintDefs = (() => {
10323
+ if (!dueDateSprint) return [];
10324
+ const out = [];
10325
+ for (const v of Object.values(dueDateSprint)) {
10326
+ const startInput = v.range_date?.start;
10327
+ const endInput = v.range_date?.end;
10328
+ if (startInput == null || endInput == null) continue;
10329
+ const startRaw = toDate2(startInput);
10330
+ const endRaw = toDate2(endInput);
10331
+ if (Number.isNaN(startRaw.getTime()) || Number.isNaN(endRaw.getTime())) continue;
10332
+ const startMs = startOfZonedDay(startRaw, resolvedTimeZone).getTime();
10333
+ const endMs = startOfZonedDay(endRaw, resolvedTimeZone).getTime();
10334
+ if (endMs <= startMs) continue;
10335
+ out.push({ startMs, endMs, title: v.title });
10336
+ }
10337
+ out.sort((a, b) => a.startMs - b.startMs);
10338
+ return out;
10339
+ })();
10340
+ const sprintTitleForStart = (slotStart, idx) => {
10341
+ if (activeView !== "sprint") return null;
10342
+ if (sprintDefs.length === 0) return null;
10343
+ const sMs = startOfZonedDay(slotStart, resolvedTimeZone).getTime();
10344
+ const match = sprintDefs.find((d) => sMs >= d.startMs && sMs < d.endMs) ?? sprintDefs[idx] ?? null;
10345
+ if (!match) return null;
10346
+ return typeof match.title === "string" || typeof match.title === "number" ? String(match.title) : null;
10347
+ };
10348
+ return (d, opts) => {
10349
+ if (activeView === "day") return timeFmt.format(d);
10350
+ if (activeView === "sprint") {
10351
+ const y = yearFmt.format(d);
10352
+ const idx = opts?.boundaryIdx;
10353
+ if (typeof idx === "number") {
10354
+ const slotStart = slotStarts[clamp4(idx, 0, Math.max(0, slotStarts.length - 1))] ?? d;
10355
+ const dynamicTitle = sprintTitleForStart(slotStart, idx);
10356
+ if (dynamicTitle) return `${dynamicTitle} \u2022 ${dayFmt.format(d)}, ${y}`;
10357
+ const sprintNumber = opts?.kind === "start" ? idx + 1 : Math.min(idx + 1, Math.max(1, slotStarts.length));
10358
+ const sprintText = `${l.sprint} ${String(sprintNumber).padStart(2, "0")}`;
10359
+ const dateText = dayFmt.format(d);
10360
+ return `${sprintText} \u2022 ${dateText}, ${y}`;
10361
+ }
10362
+ }
10363
+ return dayFmt.format(d);
10364
+ };
10365
+ }, [activeView, dueDateSprint, l.sprint, resolvedLocale, resolvedTimeZone, slotStarts, slotStarts.length]);
10165
10366
  const openCreate = React32.useCallback(() => {
10166
10367
  if (!canCreate) return;
10167
10368
  if (activeEventSheetOpen) setEventSheetOpen(false);
@@ -10198,16 +10399,16 @@ function CalendarTimeline({
10198
10399
  setCreateEndIdx((prev) => Math.min(slots.length, Math.max(prev, createStartIdx + 1)));
10199
10400
  }, [createStartIdx, slots.length]);
10200
10401
  const createStartOptions = React32.useMemo(() => {
10201
- return slotStarts.map((d, idx) => ({ label: slotPickerLabel(d), value: idx }));
10202
- }, [slotStarts, slotPickerLabel]);
10402
+ return slotStarts.map((d, idx) => ({ label: formatCreateBoundaryLabel(d, { kind: "start", boundaryIdx: idx }), value: idx }));
10403
+ }, [formatCreateBoundaryLabel, slotStarts]);
10203
10404
  const createEndOptions = React32.useMemo(() => {
10204
10405
  const out = [];
10205
10406
  for (let idx = createStartIdx + 1; idx <= slotStarts.length; idx++) {
10206
10407
  const boundary = idx >= slotStarts.length ? range.end : slotStarts[idx];
10207
- out.push({ label: slotPickerLabel(boundary), value: idx });
10408
+ out.push({ label: formatCreateBoundaryLabel(boundary, { kind: "end", boundaryIdx: idx }), value: idx });
10208
10409
  }
10209
10410
  return out;
10210
- }, [createStartIdx, range.end, slotPickerLabel, slotStarts]);
10411
+ }, [createStartIdx, formatCreateBoundaryLabel, range.end, slotStarts]);
10211
10412
  const commitCreate = React32.useCallback(() => {
10212
10413
  if (!onCreateEvent) return;
10213
10414
  if (!createResourceId) return;
@@ -10261,6 +10462,9 @@ function CalendarTimeline({
10261
10462
  const stepMs = Math.trunc(Math.max(5, Math.min(240, Math.trunc(dayTimeStepMinutes))) * 6e4);
10262
10463
  return { start, end: new Date(start.getTime() + stepMs) };
10263
10464
  }
10465
+ if (activeView === "sprint") {
10466
+ return { start, end: addZonedDays(start, 7, resolvedTimeZone) };
10467
+ }
10264
10468
  return { start, end: addZonedDays(start, 1, resolvedTimeZone) };
10265
10469
  },
10266
10470
  [activeView, dayTimeStepMinutes, resolvedTimeZone, slotStarts]
@@ -10543,7 +10747,7 @@ function CalendarTimeline({
10543
10747
  className: cn(
10544
10748
  sizeConfig.slotHeaderClass,
10545
10749
  activeView !== "day" && s.isToday && "bg-primary/8 border-l-primary/40",
10546
- activeView !== "day" && !s.isToday && s.isWeekend && "bg-destructive/8 text-destructive-foreground"
10750
+ activeView !== "day" && !s.isToday && s.isWeekend && "bg-muted/25"
10547
10751
  ),
10548
10752
  dayHeaderMarks
10549
10753
  },
@@ -10574,11 +10778,13 @@ function CalendarTimeline({
10574
10778
  {
10575
10779
  title,
10576
10780
  resourcesHeaderLabel: t("resourcesHeader"),
10577
- labels: { today: l.today, prev: l.prev, next: l.next, month: l.month, week: l.week, day: l.day },
10781
+ labels: { today: l.today, prev: l.prev, next: l.next, month: l.month, week: l.week, day: l.day, sprint: l.sprint },
10578
10782
  newEventLabel: l.newEvent,
10579
10783
  newEventDisabled: isViewOnly || !canCreate || resources.length === 0,
10580
10784
  onNewEventClick: isViewOnly ? void 0 : openCreate,
10581
10785
  activeView,
10786
+ availableViews,
10787
+ showResourceColumn,
10582
10788
  sizeConfig,
10583
10789
  navigate,
10584
10790
  now: resolvedNow,
@@ -10607,8 +10813,9 @@ function CalendarTimeline({
10607
10813
  "div",
10608
10814
  {
10609
10815
  className: cn(
10610
- "border border-border/40 rounded-2xl md:rounded-3xl overflow-hidden bg-background/95 backdrop-blur-sm",
10611
- "shadow-sm hover:shadow-md transition-shadow duration-300",
10816
+ "rounded-2xl md:rounded-3xl overflow-hidden bg-card text-card-foreground backdrop-blur-sm",
10817
+ "border border-border shadow-sm md:hover:shadow-md",
10818
+ "transition-[transform,box-shadow,border-color,background-color] duration-300 ease-soft",
10612
10819
  densityClass,
10613
10820
  className
10614
10821
  ),
@@ -10616,7 +10823,7 @@ function CalendarTimeline({
10616
10823
  children: [
10617
10824
  Header,
10618
10825
  /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)("div", { className: "flex min-h-0", children: [
10619
- /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
10826
+ showResourceColumn ? /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
10620
10827
  "div",
10621
10828
  {
10622
10829
  ref: leftRef,
@@ -10645,7 +10852,7 @@ function CalendarTimeline({
10645
10852
  /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { style: { height: bottomSpacer } })
10646
10853
  ]
10647
10854
  }
10648
- ),
10855
+ ) : null,
10649
10856
  /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
10650
10857
  "div",
10651
10858
  {
@@ -10741,12 +10948,19 @@ function CalendarTimeline({
10741
10948
  })();
10742
10949
  const resource = resourceById.get(ev.resourceId);
10743
10950
  const tooltipTitle = ev.title || ev.id;
10744
- const shouldCompact = activeView === "day" && dayEventStyle === "compact";
10951
+ const shouldCompact = activeView === "day" && dayEventStyle === "compact" || activeView === "month" && monthEventStyle === "compact";
10745
10952
  const eventInsetX = 2;
10746
10953
  const leftInset = left + eventInsetX;
10747
10954
  const widthInset = Math.max(1, width - eventInsetX * 2);
10748
- const defaultMaxVisual = clamp4(Math.round(fixedSlotWidth * 1.2), 160, 360);
10749
- const maxVisual = clamp4(Math.round(dayEventMaxWidth ?? defaultMaxVisual), 80, 1200);
10955
+ const defaultMaxVisual = (() => {
10956
+ if (activeView === "month") return clamp4(Math.round(fixedSlotWidth * 2.5), 72, 360);
10957
+ return clamp4(Math.round(fixedSlotWidth * 1.2), 160, 360);
10958
+ })();
10959
+ const maxVisual = clamp4(
10960
+ Math.round(activeView === "month" ? monthEventMaxWidth ?? defaultMaxVisual : dayEventMaxWidth ?? defaultMaxVisual),
10961
+ 48,
10962
+ 1200
10963
+ );
10750
10964
  const visualWidth = shouldCompact ? Math.min(widthInset, maxVisual) : widthInset;
10751
10965
  const isClipped = shouldCompact && widthInset > visualWidth + 1;
10752
10966
  const block = /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
@@ -10791,7 +11005,7 @@ function CalendarTimeline({
10791
11005
  "transition-all duration-150 ease-out",
10792
11006
  "backdrop-blur-sm",
10793
11007
  ev.className,
10794
- isPreview && "ring-2 ring-primary/50 ring-offset-1 ring-offset-background scale-[1.02]"
11008
+ isPreview && "ring-2 ring-primary/50 ring-offset-1 ring-offset-card scale-[1.02]"
10795
11009
  ),
10796
11010
  style: {
10797
11011
  width: visualWidth,
@@ -10803,7 +11017,7 @@ function CalendarTimeline({
10803
11017
  },
10804
11018
  children: [
10805
11019
  node,
10806
- isClipped ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "pointer-events-none absolute inset-y-0 right-0 w-10 bg-linear-to-l from-background/50 to-transparent flex items-center justify-end pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "text-xs text-muted-foreground/80", children: "\u2026" }) }) : null
11020
+ isClipped ? /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("div", { className: "pointer-events-none absolute inset-y-0 right-0 w-10 bg-linear-to-l from-card/50 to-transparent flex items-center justify-end pr-2", children: /* @__PURE__ */ (0, import_jsx_runtime38.jsx)("span", { className: "text-xs text-muted-foreground/80", children: "\u2026" }) }) : null
10807
11021
  ]
10808
11022
  }
10809
11023
  ),
@@ -10859,7 +11073,7 @@ function CalendarTimeline({
10859
11073
  className: cn(
10860
11074
  "pointer-events-none absolute z-20",
10861
11075
  "h-5 w-5 rounded-full",
10862
- "bg-background/80 backdrop-blur-sm",
11076
+ "bg-card/80 backdrop-blur-sm",
10863
11077
  "border border-border/60 shadow-xs",
10864
11078
  "flex items-center justify-center"
10865
11079
  ),