@underverse-ui/underverse 1.0.4 → 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 +233 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -2
- package/dist/index.d.ts +24 -2
- package/dist/index.js +233 -39
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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) :
|
|
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 {
|
|
@@ -8971,7 +9012,7 @@ function CalendarTimelineHeader(props) {
|
|
|
8971
9012
|
slotHeaderNodes
|
|
8972
9013
|
} = props;
|
|
8973
9014
|
const resolvedAvailableViews = React28.useMemo(
|
|
8974
|
-
() => availableViews?.length ? availableViews : ["month", "week", "day"],
|
|
9015
|
+
() => availableViews?.length ? availableViews : ["month", "week", "day", "sprint"],
|
|
8975
9016
|
[availableViews]
|
|
8976
9017
|
);
|
|
8977
9018
|
const showViewSwitcher = resolvedAvailableViews.length > 1;
|
|
@@ -9034,7 +9075,7 @@ function CalendarTimelineHeader(props) {
|
|
|
9034
9075
|
onApplyDateTime(tempDate);
|
|
9035
9076
|
setTodayOpen(false);
|
|
9036
9077
|
}, [onApplyDateTime, tempDate]);
|
|
9037
|
-
return /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "sticky top-0 z-30 bg-linear-to-b from-
|
|
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: [
|
|
9038
9079
|
/* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: cn("flex items-center justify-between gap-4", sizeConfig.headerPaddingClass), children: [
|
|
9039
9080
|
/* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center gap-1.5 min-w-0", children: [
|
|
9040
9081
|
/* @__PURE__ */ (0, import_jsx_runtime34.jsxs)("div", { className: "flex items-center bg-muted/40 rounded-full p-1 gap-0.5", children: [
|
|
@@ -9045,7 +9086,7 @@ function CalendarTimelineHeader(props) {
|
|
|
9045
9086
|
size: "icon",
|
|
9046
9087
|
onClick: () => navigate(-1),
|
|
9047
9088
|
"aria-label": labels.prev,
|
|
9048
|
-
className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-
|
|
9089
|
+
className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-card/80 transition-all duration-200"),
|
|
9049
9090
|
children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.ChevronLeft, { className: "h-4 w-4" })
|
|
9050
9091
|
}
|
|
9051
9092
|
),
|
|
@@ -9060,7 +9101,7 @@ function CalendarTimelineHeader(props) {
|
|
|
9060
9101
|
{
|
|
9061
9102
|
variant: "ghost",
|
|
9062
9103
|
size: "sm",
|
|
9063
|
-
className: cn(sizeConfig.controlButtonTextClass, "rounded-full hover:bg-
|
|
9104
|
+
className: cn(sizeConfig.controlButtonTextClass, "rounded-full hover:bg-card/80 font-medium transition-all duration-200"),
|
|
9064
9105
|
children: labels.today
|
|
9065
9106
|
}
|
|
9066
9107
|
),
|
|
@@ -9140,7 +9181,7 @@ function CalendarTimelineHeader(props) {
|
|
|
9140
9181
|
size: "icon",
|
|
9141
9182
|
onClick: () => navigate(1),
|
|
9142
9183
|
"aria-label": labels.next,
|
|
9143
|
-
className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-
|
|
9184
|
+
className: cn(sizeConfig.controlButtonIconClass, "rounded-full hover:bg-card/80 transition-all duration-200"),
|
|
9144
9185
|
children: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(import_lucide_react18.ChevronRight, { className: "h-4 w-4" })
|
|
9145
9186
|
}
|
|
9146
9187
|
)
|
|
@@ -9169,7 +9210,7 @@ function CalendarTimelineHeader(props) {
|
|
|
9169
9210
|
className: cn(
|
|
9170
9211
|
sizeConfig.controlButtonTextClass,
|
|
9171
9212
|
"rounded-full font-medium transition-all duration-200 gap-1.5",
|
|
9172
|
-
activeView === v ? "bg-primary text-primary-foreground shadow-sm shadow-primary/25" : "hover:bg-
|
|
9213
|
+
activeView === v ? "bg-primary text-primary-foreground shadow-sm shadow-primary/25" : "hover:bg-card/80 text-muted-foreground hover:text-foreground"
|
|
9173
9214
|
),
|
|
9174
9215
|
children: [
|
|
9175
9216
|
VIEW_ICONS[v],
|
|
@@ -9184,7 +9225,7 @@ function CalendarTimelineHeader(props) {
|
|
|
9184
9225
|
showLeftColumn ? /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(
|
|
9185
9226
|
"div",
|
|
9186
9227
|
{
|
|
9187
|
-
className: "shrink-0 border-r border-border/30 bg-
|
|
9228
|
+
className: "shrink-0 border-r border-border/30 bg-card/60 flex items-center justify-center relative group/uv-ct-top-left",
|
|
9188
9229
|
style: { width: effectiveResourceColumnWidth, minWidth: effectiveResourceColumnWidth },
|
|
9189
9230
|
children: [
|
|
9190
9231
|
/* @__PURE__ */ (0, import_jsx_runtime34.jsx)("span", { className: "text-xs font-medium text-muted-foreground/70 uppercase tracking-wider", children: resourcesHeaderLabel }),
|
|
@@ -9239,7 +9280,7 @@ function DefaultGroupRow(props) {
|
|
|
9239
9280
|
"span",
|
|
9240
9281
|
{
|
|
9241
9282
|
className: cn(
|
|
9242
|
-
"inline-flex items-center justify-center w-5 h-5 rounded-md bg-
|
|
9283
|
+
"inline-flex items-center justify-center w-5 h-5 rounded-md bg-card/60 transition-transform duration-200",
|
|
9243
9284
|
collapsed ? "" : "rotate-180"
|
|
9244
9285
|
),
|
|
9245
9286
|
children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(import_lucide_react19.ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })
|
|
@@ -9260,7 +9301,7 @@ function ResourceRowCell(props) {
|
|
|
9260
9301
|
"div",
|
|
9261
9302
|
{
|
|
9262
9303
|
className: cn(
|
|
9263
|
-
"h-full w-full flex items-center border-b border-border/30 bg-linear-to-r from-
|
|
9304
|
+
"h-full w-full flex items-center border-b border-border/30 bg-linear-to-r from-card to-card/95 relative",
|
|
9264
9305
|
sizeConfig.resourceRowClass,
|
|
9265
9306
|
"hover:from-muted/30 hover:to-muted/10 transition-all duration-200 group/uv-ct-row-header"
|
|
9266
9307
|
),
|
|
@@ -9320,7 +9361,7 @@ var CalendarTimelineGridOverlay = React29.memo(function CalendarTimelineGridOver
|
|
|
9320
9361
|
if (showToday && idx === todaySlotIdx) return null;
|
|
9321
9362
|
const left = slotLefts[idx] ?? 0;
|
|
9322
9363
|
const width = slotWidths[idx] ?? 0;
|
|
9323
|
-
return /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "absolute top-0 h-full bg-
|
|
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}`);
|
|
9324
9365
|
}) : null,
|
|
9325
9366
|
showToday ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
|
|
9326
9367
|
"div",
|
|
@@ -9383,7 +9424,8 @@ function useTimelineSlots(args) {
|
|
|
9383
9424
|
dayRangeMode,
|
|
9384
9425
|
workHours,
|
|
9385
9426
|
resolvedNow,
|
|
9386
|
-
formatters
|
|
9427
|
+
formatters,
|
|
9428
|
+
dueDateSprint
|
|
9387
9429
|
} = args;
|
|
9388
9430
|
const { slots, range } = React31.useMemo(() => {
|
|
9389
9431
|
const { start, end, slotStarts: slotStarts2 } = computeSlotStarts({
|
|
@@ -9396,17 +9438,89 @@ function useTimelineSlots(args) {
|
|
|
9396
9438
|
workHours
|
|
9397
9439
|
});
|
|
9398
9440
|
const todayStart = startOfZonedDay(resolvedNow, resolvedTimeZone).getTime();
|
|
9399
|
-
const
|
|
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) => ({
|
|
9400
9479
|
start: s,
|
|
9401
|
-
label: formatters?.slotHeader?.(s, { view: activeView, locale: resolvedLocale, timeZone: resolvedTimeZone }) ??
|
|
9402
|
-
|
|
9403
|
-
|
|
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 : (() => {
|
|
9404
9506
|
const wd = getZonedWeekday(s, resolvedTimeZone);
|
|
9405
9507
|
return wd === 0 || wd === 6;
|
|
9406
9508
|
})()
|
|
9407
9509
|
}));
|
|
9408
9510
|
return { slots: slotItems, range: { start, end } };
|
|
9409
|
-
}, [
|
|
9511
|
+
}, [
|
|
9512
|
+
activeView,
|
|
9513
|
+
activeDate,
|
|
9514
|
+
dayRangeMode,
|
|
9515
|
+
dayTimeStepMinutes,
|
|
9516
|
+
dueDateSprint,
|
|
9517
|
+
formatters,
|
|
9518
|
+
resolvedLocale,
|
|
9519
|
+
resolvedNow,
|
|
9520
|
+
resolvedTimeZone,
|
|
9521
|
+
weekStartsOn,
|
|
9522
|
+
workHours
|
|
9523
|
+
]);
|
|
9410
9524
|
const slotStarts = React31.useMemo(() => slots.map((s) => s.start), [slots]);
|
|
9411
9525
|
const todaySlotIdx = React31.useMemo(() => slots.findIndex((s) => s.isToday), [slots]);
|
|
9412
9526
|
const weekendSlotIdxs = React31.useMemo(() => {
|
|
@@ -9697,6 +9811,7 @@ function CalendarTimeline({
|
|
|
9697
9811
|
view,
|
|
9698
9812
|
defaultView = "month",
|
|
9699
9813
|
onViewChange,
|
|
9814
|
+
dueDateSprint,
|
|
9700
9815
|
date,
|
|
9701
9816
|
defaultDate,
|
|
9702
9817
|
onDateChange,
|
|
@@ -9729,6 +9844,8 @@ function CalendarTimeline({
|
|
|
9729
9844
|
adaptiveSlotWidths,
|
|
9730
9845
|
dayEventStyle = "span",
|
|
9731
9846
|
dayEventMaxWidth,
|
|
9847
|
+
monthEventStyle = "span",
|
|
9848
|
+
monthEventMaxWidth,
|
|
9732
9849
|
dayTimeStepMinutes = 60,
|
|
9733
9850
|
enableEventTooltips = true,
|
|
9734
9851
|
dayHeaderMode = "full",
|
|
@@ -9823,18 +9940,29 @@ function CalendarTimeline({
|
|
|
9823
9940
|
setInternalRowHeight(defaultRowHeight);
|
|
9824
9941
|
}, [defaultRowHeight, isControlledRowHeight]);
|
|
9825
9942
|
const effectiveRowHeight = isControlledRowHeight ? rowHeight : internalRowHeight;
|
|
9826
|
-
const
|
|
9943
|
+
const baseSlotMinWidth = slotMinWidth ?? sizeConfig.slotMinWidth;
|
|
9827
9944
|
const colMin = minResourceColumnWidth ?? 160;
|
|
9828
9945
|
const colMax = maxResourceColumnWidth ?? 520;
|
|
9829
9946
|
const rowMin = minRowHeight ?? 36;
|
|
9830
9947
|
const rowMax = maxRowHeight ?? 120;
|
|
9831
9948
|
const availableViews = React32.useMemo(
|
|
9832
|
-
() => onlyView ? [onlyView] : ["month", "week", "day"],
|
|
9949
|
+
() => onlyView ? [onlyView] : ["month", "week", "day", "sprint"],
|
|
9833
9950
|
[onlyView]
|
|
9834
9951
|
);
|
|
9835
9952
|
const isControlledView = view !== void 0;
|
|
9836
9953
|
const [internalView, setInternalView] = React32.useState(() => onlyView ?? defaultView);
|
|
9837
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]);
|
|
9838
9966
|
const isControlledDate = date !== void 0;
|
|
9839
9967
|
const [internalDate, setInternalDate] = React32.useState(() => defaultDate ?? /* @__PURE__ */ new Date());
|
|
9840
9968
|
const activeDate = isControlledDate ? date : internalDate;
|
|
@@ -9847,6 +9975,7 @@ function CalendarTimeline({
|
|
|
9847
9975
|
month: labels?.month ?? t("month"),
|
|
9848
9976
|
week: labels?.week ?? t("week"),
|
|
9849
9977
|
day: labels?.day ?? t("day"),
|
|
9978
|
+
sprint: labels?.sprint ?? t("sprint"),
|
|
9850
9979
|
newEvent: labels?.newEvent ?? t("newEvent"),
|
|
9851
9980
|
createEventTitle: labels?.createEventTitle ?? t("createEventTitle"),
|
|
9852
9981
|
create: labels?.create ?? t("create"),
|
|
@@ -9887,6 +10016,10 @@ function CalendarTimeline({
|
|
|
9887
10016
|
setDate(addZonedDays(base, dir * 7, resolvedTimeZone));
|
|
9888
10017
|
return;
|
|
9889
10018
|
}
|
|
10019
|
+
if (activeView === "sprint") {
|
|
10020
|
+
setDate(addZonedYears(base, dir, resolvedTimeZone));
|
|
10021
|
+
return;
|
|
10022
|
+
}
|
|
9890
10023
|
setDate(addZonedDays(base, dir, resolvedTimeZone));
|
|
9891
10024
|
},
|
|
9892
10025
|
[activeDate, activeView, resolvedTimeZone, setDate]
|
|
@@ -9912,7 +10045,8 @@ function CalendarTimeline({
|
|
|
9912
10045
|
dayRangeMode,
|
|
9913
10046
|
workHours,
|
|
9914
10047
|
resolvedNow,
|
|
9915
|
-
formatters
|
|
10048
|
+
formatters,
|
|
10049
|
+
dueDateSprint
|
|
9916
10050
|
});
|
|
9917
10051
|
React32.useEffect(() => {
|
|
9918
10052
|
onRangeChange?.(range);
|
|
@@ -10158,9 +10292,15 @@ function CalendarTimeline({
|
|
|
10158
10292
|
const rangeText = ya === yb ? `${a} \u2013 ${b}, ${ya}` : `${a}, ${ya} \u2013 ${b}, ${yb}`;
|
|
10159
10293
|
return `${l.week} ${week} \u2022 ${rangeText}`;
|
|
10160
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
|
+
}
|
|
10161
10301
|
const fmt = getDtf(resolvedLocale, resolvedTimeZone, { weekday: "long", year: "numeric", month: "long", day: "numeric" });
|
|
10162
10302
|
return fmt.format(range.start);
|
|
10163
|
-
}, [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]);
|
|
10164
10304
|
const createMode = interactions?.createMode ?? "drag";
|
|
10165
10305
|
const canCreate = !isViewOnly && (interactions?.creatable ?? false) && !!onCreateEvent;
|
|
10166
10306
|
const [createOpen, setCreateOpen] = React32.useState(false);
|
|
@@ -10175,11 +10315,54 @@ function CalendarTimeline({
|
|
|
10175
10315
|
disabled: r.disabled ?? false
|
|
10176
10316
|
}));
|
|
10177
10317
|
}, [resources]);
|
|
10178
|
-
const
|
|
10318
|
+
const formatCreateBoundaryLabel = React32.useMemo(() => {
|
|
10179
10319
|
const timeFmt = getDtf(resolvedLocale, resolvedTimeZone, { hour: "2-digit", minute: "2-digit", hourCycle: "h23" });
|
|
10180
10320
|
const dayFmt = getDtf(resolvedLocale, resolvedTimeZone, { weekday: "short", month: "short", day: "numeric" });
|
|
10181
|
-
|
|
10182
|
-
|
|
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]);
|
|
10183
10366
|
const openCreate = React32.useCallback(() => {
|
|
10184
10367
|
if (!canCreate) return;
|
|
10185
10368
|
if (activeEventSheetOpen) setEventSheetOpen(false);
|
|
@@ -10216,16 +10399,16 @@ function CalendarTimeline({
|
|
|
10216
10399
|
setCreateEndIdx((prev) => Math.min(slots.length, Math.max(prev, createStartIdx + 1)));
|
|
10217
10400
|
}, [createStartIdx, slots.length]);
|
|
10218
10401
|
const createStartOptions = React32.useMemo(() => {
|
|
10219
|
-
return slotStarts.map((d, idx) => ({ label:
|
|
10220
|
-
}, [
|
|
10402
|
+
return slotStarts.map((d, idx) => ({ label: formatCreateBoundaryLabel(d, { kind: "start", boundaryIdx: idx }), value: idx }));
|
|
10403
|
+
}, [formatCreateBoundaryLabel, slotStarts]);
|
|
10221
10404
|
const createEndOptions = React32.useMemo(() => {
|
|
10222
10405
|
const out = [];
|
|
10223
10406
|
for (let idx = createStartIdx + 1; idx <= slotStarts.length; idx++) {
|
|
10224
10407
|
const boundary = idx >= slotStarts.length ? range.end : slotStarts[idx];
|
|
10225
|
-
out.push({ label:
|
|
10408
|
+
out.push({ label: formatCreateBoundaryLabel(boundary, { kind: "end", boundaryIdx: idx }), value: idx });
|
|
10226
10409
|
}
|
|
10227
10410
|
return out;
|
|
10228
|
-
}, [createStartIdx, range.end,
|
|
10411
|
+
}, [createStartIdx, formatCreateBoundaryLabel, range.end, slotStarts]);
|
|
10229
10412
|
const commitCreate = React32.useCallback(() => {
|
|
10230
10413
|
if (!onCreateEvent) return;
|
|
10231
10414
|
if (!createResourceId) return;
|
|
@@ -10279,6 +10462,9 @@ function CalendarTimeline({
|
|
|
10279
10462
|
const stepMs = Math.trunc(Math.max(5, Math.min(240, Math.trunc(dayTimeStepMinutes))) * 6e4);
|
|
10280
10463
|
return { start, end: new Date(start.getTime() + stepMs) };
|
|
10281
10464
|
}
|
|
10465
|
+
if (activeView === "sprint") {
|
|
10466
|
+
return { start, end: addZonedDays(start, 7, resolvedTimeZone) };
|
|
10467
|
+
}
|
|
10282
10468
|
return { start, end: addZonedDays(start, 1, resolvedTimeZone) };
|
|
10283
10469
|
},
|
|
10284
10470
|
[activeView, dayTimeStepMinutes, resolvedTimeZone, slotStarts]
|
|
@@ -10561,7 +10747,7 @@ function CalendarTimeline({
|
|
|
10561
10747
|
className: cn(
|
|
10562
10748
|
sizeConfig.slotHeaderClass,
|
|
10563
10749
|
activeView !== "day" && s.isToday && "bg-primary/8 border-l-primary/40",
|
|
10564
|
-
activeView !== "day" && !s.isToday && s.isWeekend && "bg-
|
|
10750
|
+
activeView !== "day" && !s.isToday && s.isWeekend && "bg-muted/25"
|
|
10565
10751
|
),
|
|
10566
10752
|
dayHeaderMarks
|
|
10567
10753
|
},
|
|
@@ -10592,7 +10778,7 @@ function CalendarTimeline({
|
|
|
10592
10778
|
{
|
|
10593
10779
|
title,
|
|
10594
10780
|
resourcesHeaderLabel: t("resourcesHeader"),
|
|
10595
|
-
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 },
|
|
10596
10782
|
newEventLabel: l.newEvent,
|
|
10597
10783
|
newEventDisabled: isViewOnly || !canCreate || resources.length === 0,
|
|
10598
10784
|
onNewEventClick: isViewOnly ? void 0 : openCreate,
|
|
@@ -10627,8 +10813,9 @@ function CalendarTimeline({
|
|
|
10627
10813
|
"div",
|
|
10628
10814
|
{
|
|
10629
10815
|
className: cn(
|
|
10630
|
-
"
|
|
10631
|
-
"shadow-sm hover:shadow-md
|
|
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",
|
|
10632
10819
|
densityClass,
|
|
10633
10820
|
className
|
|
10634
10821
|
),
|
|
@@ -10761,12 +10948,19 @@ function CalendarTimeline({
|
|
|
10761
10948
|
})();
|
|
10762
10949
|
const resource = resourceById.get(ev.resourceId);
|
|
10763
10950
|
const tooltipTitle = ev.title || ev.id;
|
|
10764
|
-
const shouldCompact = activeView === "day" && dayEventStyle === "compact";
|
|
10951
|
+
const shouldCompact = activeView === "day" && dayEventStyle === "compact" || activeView === "month" && monthEventStyle === "compact";
|
|
10765
10952
|
const eventInsetX = 2;
|
|
10766
10953
|
const leftInset = left + eventInsetX;
|
|
10767
10954
|
const widthInset = Math.max(1, width - eventInsetX * 2);
|
|
10768
|
-
const defaultMaxVisual =
|
|
10769
|
-
|
|
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
|
+
);
|
|
10770
10964
|
const visualWidth = shouldCompact ? Math.min(widthInset, maxVisual) : widthInset;
|
|
10771
10965
|
const isClipped = shouldCompact && widthInset > visualWidth + 1;
|
|
10772
10966
|
const block = /* @__PURE__ */ (0, import_jsx_runtime38.jsxs)(
|
|
@@ -10811,7 +11005,7 @@ function CalendarTimeline({
|
|
|
10811
11005
|
"transition-all duration-150 ease-out",
|
|
10812
11006
|
"backdrop-blur-sm",
|
|
10813
11007
|
ev.className,
|
|
10814
|
-
isPreview && "ring-2 ring-primary/50 ring-offset-1 ring-offset-
|
|
11008
|
+
isPreview && "ring-2 ring-primary/50 ring-offset-1 ring-offset-card scale-[1.02]"
|
|
10815
11009
|
),
|
|
10816
11010
|
style: {
|
|
10817
11011
|
width: visualWidth,
|
|
@@ -10823,7 +11017,7 @@ function CalendarTimeline({
|
|
|
10823
11017
|
},
|
|
10824
11018
|
children: [
|
|
10825
11019
|
node,
|
|
10826
|
-
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-
|
|
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
|
|
10827
11021
|
]
|
|
10828
11022
|
}
|
|
10829
11023
|
),
|
|
@@ -10879,7 +11073,7 @@ function CalendarTimeline({
|
|
|
10879
11073
|
className: cn(
|
|
10880
11074
|
"pointer-events-none absolute z-20",
|
|
10881
11075
|
"h-5 w-5 rounded-full",
|
|
10882
|
-
"bg-
|
|
11076
|
+
"bg-card/80 backdrop-blur-sm",
|
|
10883
11077
|
"border border-border/60 shadow-xs",
|
|
10884
11078
|
"flex items-center justify-center"
|
|
10885
11079
|
),
|