/* TargiApp — harmonogram: kalendarz tygodniowy (kolumny = dni), karty = osoby z rolą/godzinami/notatką.
Drag&drop kart między dniami + przeciąganie z listy zespołu + dropdown "Dodaj osobę".
Przełączanie tygodni, filtrowanie (rola + osoba) i sortowanie kart. */
// ---- helpers dat / tygodnia ----
const MONTHS_SHORT = ["sty", "lut", "mar", "kwi", "maj", "cze", "lip", "sie", "wrz", "paź", "lis", "gru"];
function isoDate(d) {
return d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
}
function mondayOf(dateStr) {
const d = new Date(dateStr); const off = (d.getDay() + 6) % 7;
d.setHours(0, 0, 0, 0); d.setDate(d.getDate() - off); return d;
}
function addDays(date, n) { const d = new Date(date); d.setDate(d.getDate() + n); return d; }
function AssignCard({ a, onEdit, onCardDrop }) {
const store = useStore();
const p = store.personById(a.personId);
const r = store.roleOf(a.role);
const [edge, setEdge] = useState(null); // 'top' | 'bottom' — wskaźnik miejsca wstawienia
return (
{
e.dataTransfer.setData("text/assignment", String(a.id));
e.dataTransfer.effectAllowed = "move";
e.currentTarget.classList.add("dragging");
}}
onDragEnd={(e) => e.currentTarget.classList.remove("dragging")}
onDragOver={(e) => {
e.preventDefault();
const rect = e.currentTarget.getBoundingClientRect();
setEdge(e.clientY < rect.top + rect.height / 2 ? "top" : "bottom");
}}
onDragLeave={() => setEdge(null)}
onDrop={(e) => {
e.preventDefault(); e.stopPropagation();
const before = edge !== "bottom";
setEdge(null);
onCardDrop(e, a, before);
}}
onClick={() => onEdit(a)}
>
{a.from}–{a.to}
{a.note ?
{a.note}
: null}
);
}
function AddPersonDropdown({ date }) {
const store = useStore();
const [open, setOpen] = useState(false);
const [q, setQ] = useState("");
const ref = useRef(null);
useEffect(() => {
if (!open) return;
function onDoc(e) { if (ref.current && !ref.current.contains(e.target)) setOpen(false); }
document.addEventListener("mousedown", onDoc);
return () => document.removeEventListener("mousedown", onDoc);
}, [open]);
const available = store.state.people
.filter((p) => store.fullName(p).toLowerCase().includes(q.toLowerCase()))
.sort((a, b) => store.fullName(a).localeCompare(store.fullName(b)));
return (
{open ? (
🔍
setQ(e.target.value)} />
{available.map((p) => (
{ store.addAssignment(p.id, date); setOpen(false); }}>
{store.fullName(p)}
))}
{!available.length ?
Brak dostępnych osób
: null}
) : null}
);
}
function DayColumn({ day, onEdit, roleFilter, personQuery, sortBy, setSortBy }) {
const store = useStore();
const [over, setOver] = useState(false);
const roleOrder = store.ROLES.map((r) => r.key);
const q = personQuery.trim().toLowerCase();
let items = store.assignmentsForDay(day.date)
.filter((a) => roleFilter.has(a.role))
.filter((a) => !q || store.fullName(store.personById(a.personId)).toLowerCase().includes(q));
if (sortBy !== "reczna") {
items = items.slice().sort((a, b) => {
if (sortBy === "rola") return roleOrder.indexOf(a.role) - roleOrder.indexOf(b.role) || (a.from || "").localeCompare(b.from || "");
if (sortBy === "nazwisko") {
const pa = store.personById(a.personId), pb = store.personById(b.personId);
return (pa.lastName || "").localeCompare(pb.lastName || "");
}
return (a.from || "").localeCompare(b.from || ""); // godzina
});
}
// w trybie "reczna" zostaje kolejność wg pozycji (z assignmentsForDay)
function onDrop(e) {
e.preventDefault(); setOver(false);
const aId = e.dataTransfer.getData("text/assignment");
const pId = e.dataTransfer.getData("text/person");
if (aId) store.moveAssignment(Number(aId), day.date);
else if (pId) store.addAssignment(Number(pId), day.date);
}
// upuszczenie na konkretną kartę = ustawienie kolejności (przełącza na tryb ręczny)
function onCardDrop(e, target, before) {
const aId = e.dataTransfer.getData("text/assignment");
const pId = e.dataTransfer.getData("text/person");
if (aId) {
const draggedId = Number(aId);
if (draggedId === target.id) return;
const dragged = store.state.assignments.find((x) => x.id === draggedId);
if (dragged && dragged.date !== day.date) store.moveAssignment(draggedId, day.date);
store.reorderAssignment(draggedId, target.id, before);
setSortBy("reczna");
} else if (pId) {
Promise.resolve(store.addAssignment(Number(pId), day.date)).then((created) => {
if (created) { store.reorderAssignment(created.id, target.id, before); setSortBy("reczna"); }
});
}
}
const cls = "daycol"
+ (over ? " drop" : "")
+ (day.isFair ? " fair" : "")
+ (day.weekend ? " weekend" : "")
+ (!day.isFair && !items.length ? " dim" : "");
const user = store.currentUser();
return (
{ e.preventDefault(); setOver(true); }}
onDragLeave={() => setOver(false)}
onDrop={onDrop}>
{items.length} os.
{day.dow} {day.dom}
{day.monthName}
{user && user.isAdmin ? (
{day.isFair ? (
store.setFairDayLabel(day.date, e.target.value)} />
) : null}
) : (day.isFair ?
{day.fairLabel || "Targi"} : null)}
{items.map((a) =>
)}
{!items.length ?
Przeciągnij osobę tutaj
lub dodaj poniżej
: null}
);
}
function Roster() {
const store = useStore();
const [q, setQ] = useState("");
const [sort, setSort] = useState("alfa");
let people = store.state.people.filter((p) => store.fullName(p).toLowerCase().includes(q.toLowerCase()));
people = people.sort((a, b) =>
sort === "dni"
? store.daysForPerson(b.id).size - store.daysForPerson(a.id).size || store.fullName(a).localeCompare(store.fullName(b))
: store.fullName(a).localeCompare(store.fullName(b))
);
return (
);
}
function EditModal({ a, onClose }) {
const store = useStore();
const p = store.personById(a.personId);
const [role, setRole] = useState(a.role);
const [from, setFrom] = useState(a.from);
const [to, setTo] = useState(a.to);
const [note, setNote] = useState(a.note);
function save() { store.updateAssignment(a.id, { role, from, to, note }); onClose(); }
return (
{ if (e.target === e.currentTarget) onClose(); }}>
{store.fullName(p)}
{fmtDayLong(a.date)}
{store.ROLES.map((r) => (
))}
);
}
function Toolbar({ weekStart, setWeekStart, weekDays, fairWeekStart, roleFilter, setRoleFilter, sortBy, setSortBy, search, setSearch }) {
const store = useStore();
const last = weekDays[6];
const label = weekDays[0].dom + " " + weekDays[0].monthName.split(" ")[0] + " – " + last.dom + " " + last.monthName;
function toggleRole(k) {
setRoleFilter((prev) => {
const next = new Set(prev);
if (next.has(k)) next.delete(k); else next.add(k);
return next;
});
}
const allOn = roleFilter.size === store.ROLES.length;
return (
Tydzień{label}
Role
{store.ROLES.map((r) => (
))}
🔍
setSearch(e.target.value)} />
);
}
function SchedulePage() {
const store = useStore();
const user = store.currentUser();
const [editing, setEditing] = useState(null);
const days0 = (store.state.fair.days && store.state.fair.days[0]) ? store.state.fair.days[0].date : isoDate(new Date());
const fairWeekStart = mondayOf(days0);
const [weekStart, setWeekStart] = useState(() => mondayOf(days0));
const [roleFilter, setRoleFilter] = useState(() => new Set(store.ROLES.map((r) => r.key)));
const [sortBy, setSortBy] = useState("reczna");
const [search, setSearch] = useState("");
// liczone inline (reaguje na zmiany dni targowych przez admina)
const fairMap = {};
(store.state.fair.days || []).forEach((d) => (fairMap[d.date] = d.label));
const dow = ["niedz.", "pon.", "wt.", "śr.", "czw.", "pt.", "sob."];
const weekDays = [];
for (let i = 0; i < 7; i++) {
const d = addDays(weekStart, i);
const ds = isoDate(d);
weekDays.push({
date: ds, dow: dow[d.getDay()], dom: d.getDate(),
monthName: MONTHS_SHORT[d.getMonth()] + " " + d.getFullYear(),
weekend: d.getDay() === 0 || d.getDay() === 6,
isFair: Object.prototype.hasOwnProperty.call(fairMap, ds),
fairLabel: fairMap[ds] || "",
});
}
const fairDaysCount = (store.state.fair.days || []).length;
const total = store.state.assignments.length;
const peopleOnFair = new Set(store.state.assignments.map((a) => a.personId)).size;
return (
{user.isAdmin
?
store.setFairName(e.target.value)} />
:
{store.state.fair.name}
}
📍 {store.state.fair.venue} · {fairDaysCount} dni targowych · {peopleOnFair} osób w grafiku · {total} przydziałów
{weekDays.map((day) => (
))}
{editing ?
setEditing(null)} /> : null}
);
}
Object.assign(window, { SchedulePage });