All checks were successful
Deploy Docs to Synology / deploy (push) Successful in 3s
172 lines
6.4 KiB
JavaScript
172 lines
6.4 KiB
JavaScript
// components/Calendar.js
|
|
import { $, h } from "sigpro";
|
|
|
|
export const Calendar = (props) => {
|
|
const internalDate = $(new Date());
|
|
const hoverDate = $(null);
|
|
const startHour = $(0);
|
|
const endHour = $(0);
|
|
|
|
const isRangeMode = () => {
|
|
const r = typeof props.range === "function" ? props.range() : props.range;
|
|
return r === true;
|
|
};
|
|
|
|
const now = new Date();
|
|
const todayStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
|
|
const formatDate = (d) => {
|
|
const year = d.getFullYear();
|
|
const month = String(d.getMonth() + 1).padStart(2, "0");
|
|
const day = String(d.getDate()).padStart(2, "0");
|
|
return `${year}-${month}-${day}`;
|
|
};
|
|
|
|
const getCurrentValue = () => {
|
|
return typeof props.value === "function" ? props.value() : props.value;
|
|
};
|
|
|
|
const selectDate = (date) => {
|
|
const dateStr = formatDate(date);
|
|
const current = getCurrentValue();
|
|
|
|
if (isRangeMode()) {
|
|
if (!current?.start || (current.start && current.end)) {
|
|
const newValue = {
|
|
start: dateStr,
|
|
end: null,
|
|
...(props.hour && { startHour: startHour() }),
|
|
};
|
|
props.onChange?.(newValue);
|
|
} else {
|
|
const start = current.start;
|
|
let newValue;
|
|
if (dateStr < start) {
|
|
newValue = { start: dateStr, end: start };
|
|
} else {
|
|
newValue = { start, end: dateStr };
|
|
}
|
|
if (props.hour) {
|
|
newValue.startHour = current.startHour !== undefined ? current.startHour : startHour();
|
|
newValue.endHour = endHour();
|
|
}
|
|
props.onChange?.(newValue);
|
|
}
|
|
} else {
|
|
const newValue = props.hour ? `${dateStr}T${String(startHour()).padStart(2, "0")}:00:00` : dateStr;
|
|
props.onChange?.(newValue);
|
|
}
|
|
};
|
|
|
|
const move = (m) => {
|
|
const d = internalDate();
|
|
internalDate(new Date(d.getFullYear(), d.getMonth() + m, 1));
|
|
};
|
|
|
|
const moveYear = (y) => {
|
|
const d = internalDate();
|
|
internalDate(new Date(d.getFullYear() + y, d.getMonth(), 1));
|
|
};
|
|
|
|
const HourSlider = ({ value: hVal, onChange: onHourChange }) => {
|
|
return h("div", { class: "flex-1" }, [
|
|
h("div", { class: "flex gap-2 items-center" }, [
|
|
h("input", {
|
|
type: "range",
|
|
min: 0,
|
|
max: 23,
|
|
value: hVal,
|
|
class: "range range-xs flex-1",
|
|
oninput: (e) => onHourChange(parseInt(e.target.value))
|
|
}),
|
|
h("span", { class: "text-sm font-mono min-w-[48px] text-center" },
|
|
() => String(typeof hVal === "function" ? hVal() : hVal).padStart(2, "0") + ":00"
|
|
)
|
|
])
|
|
]);
|
|
};
|
|
|
|
return h("div", { class: `p-4 bg-base-100 border border-base-300 shadow-2xl rounded-box w-80 select-none ${props.class ?? ''}`.trim() }, [
|
|
h("div", { class: "flex justify-between items-center mb-4 gap-1" }, [
|
|
h("div", { class: "flex gap-0.5" }, [
|
|
h("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(-1) },
|
|
h("span", { class: "icon-[lucide--chevrons-left]" })
|
|
),
|
|
h("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(-1) },
|
|
h("span", { class: "icon-[lucide--chevron-left]" })
|
|
)
|
|
]),
|
|
h("span", { class: "font-bold uppercase flex-1 text-center" }, [
|
|
() => internalDate().toLocaleString("es-ES", { month: "short", year: "numeric" })
|
|
]),
|
|
h("div", { class: "flex gap-0.5" }, [
|
|
h("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => move(1) },
|
|
h("span", { class: "icon-[lucide--chevron-right]" })
|
|
),
|
|
h("button", { type: "button", class: "btn btn-ghost btn-xs px-1", onclick: () => moveYear(1) },
|
|
h("span", { class: "icon-[lucide--chevrons-right]" })
|
|
)
|
|
])
|
|
]),
|
|
|
|
h("div", { class: "grid grid-cols-7 gap-1", onmouseleave: () => hoverDate(null) }, [
|
|
...["L", "M", "X", "J", "V", "S", "D"].map((d) => h("div", { class: "text-[10px] opacity-40 font-bold text-center" }, d)),
|
|
() => {
|
|
const d = internalDate();
|
|
const year = d.getFullYear();
|
|
const month = d.getMonth();
|
|
const firstDay = new Date(year, month, 1).getDay();
|
|
const offset = firstDay === 0 ? 6 : firstDay - 1;
|
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
|
|
const cells = [];
|
|
for (let i = 0; i < offset; i++) cells.push(h("div"));
|
|
|
|
for (let i = 1; i <= daysInMonth; i++) {
|
|
const date = new Date(year, month, i);
|
|
const dStr = formatDate(date);
|
|
|
|
cells.push(
|
|
h("button", {
|
|
type: "button",
|
|
class: () => {
|
|
const v = getCurrentValue();
|
|
const h = hoverDate();
|
|
const isStart = typeof v === "string" ? v.split("T")[0] === dStr : v?.start === dStr;
|
|
const isEnd = v?.end === dStr;
|
|
let inRange = false;
|
|
|
|
if (isRangeMode() && v?.start) {
|
|
const start = v.start;
|
|
if (!v.end && h) {
|
|
inRange = (dStr > start && dStr <= h) || (dStr < start && dStr >= h);
|
|
} else if (v.end) {
|
|
inRange = dStr > start && dStr < v.end;
|
|
}
|
|
}
|
|
|
|
const base = "btn btn-xs p-0 aspect-square min-h-0 h-auto font-normal relative";
|
|
const state = isStart || isEnd ? "btn-primary z-10" : inRange ? "bg-primary/20 border-none rounded-none" : "btn-ghost";
|
|
const today = dStr === todayStr ? "ring-1 ring-primary ring-inset font-black text-primary" : "";
|
|
|
|
return `${base} ${state} ${today}`.trim();
|
|
},
|
|
onmouseenter: () => { if (isRangeMode()) hoverDate(dStr); },
|
|
onclick: () => selectDate(date)
|
|
}, i.toString())
|
|
);
|
|
}
|
|
return cells;
|
|
}
|
|
]),
|
|
|
|
props.hour ? h("div", { class: "mt-3 pt-2 border-t border-base-300" }, [
|
|
isRangeMode()
|
|
? h("div", { class: "flex gap-4" }, [
|
|
HourSlider({ value: startHour, onChange: (h) => startHour(h) }),
|
|
HourSlider({ value: endHour, onChange: (h) => endHour(h) })
|
|
])
|
|
: HourSlider({ value: startHour, onChange: (h) => startHour(h) })
|
|
]) : null
|
|
]);
|
|
}; |