react-weekly-table
Version:
React weekly scheduler <br/> By default build time ranges for a week, supports up to 31 days <br/> Can work with different timezones, data always return to UTC+0
261 lines (260 loc) • 11 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
import { inputOffset, inputParser, outputMerger, outputOffset, random, } from '../utils';
import { useCanvas } from './CanvasProvider';
import { useCells } from './CellsProvider';
import { usePointerLock } from './PointerLockProvider';
import { useScheduler } from './Scheduler';
const TimeBlockContext = createContext({});
/**
* Timeblock provider context hook
* @returns [TimeBlockProps]{@link TimeBlockProps}
*/
export const useTimeBlock = () => useContext(TimeBlockContext);
/**
* Timeblock provider, keeps all timeblocks and main action with it
*/
const TimeBlockProvider = ({ children }) => {
const { columns, defaultValue, onChange, helperWidthProp, headerHeightProp, requiredTZOffset, } = useScheduler();
const { cells, setCells, widthStep, msTime, maxCellIndex, millisPerPixel } = useCells();
const { isDrawing } = useCanvas();
const { isLocking } = usePointerLock();
/**
* Browser timezone offset in ms
*/
const browserOffset = useMemo(() => {
return new Date().getTimezoneOffset() * 60 * 1000;
}, []);
/**
* Working offset state
*/
const [tzOffset, setTZOffset] = useState(browserOffset);
/**
* Primary timeblocks state
*/
const [blocks, setBlocks] = useState([]);
/**
* Temporary (preview) timeblocks state
*/
const [preview, setPreview] = useState([]);
/**
* History state
*/
const [history, setHistory] = useState([]);
/**
* History writer
*/
const handleHistory = useCallback((input) => {
setHistory(history.concat([input]));
}, [history]);
/**
* History rollback
*/
const undoHistory = useCallback(() => {
if (history.length < 2) {
return;
}
setBlocks(history[history.length - 2]);
setHistory([...history.slice(0, history.length - 1)]);
}, [history]);
/**
* Selecting between browser timezone and required timezone
* This is global-primary-important component value
*/
useEffect(() => {
if (!requiredTZOffset && requiredTZOffset !== 0) {
setTZOffset(browserOffset);
return;
}
setTZOffset(requiredTZOffset);
}, [browserOffset, requiredTZOffset]);
/**
* Moves timeblock to the nearest top cell after stopped move or resize it
*/
const setTopPosition = useCallback((block, withHeight) => {
var _a;
if (block.endTime - block.startTime < msTime)
return;
const newBlocks = blocks.filter((b) => b.id !== block.id);
let startRow = Math.abs(Math.round(block.startTime / msTime));
if (startRow < 0)
startRow = 0;
const needCells = cells.filter((cell) => cell.row === startRow)[0];
let position = (_a = needCells === null || needCells === void 0 ? void 0 : needCells.position) === null || _a === void 0 ? void 0 : _a.y;
if (!position) {
const maxRow = cells[maxCellIndex];
position = maxRow.position.h;
}
const movedBlock = Object.assign(Object.assign({}, block), { realStartTime: block.startTime, realEndTime: block.endTime, top: position, height: withHeight
? block.height - (position - block.top)
: block.height });
setBlocks(newBlocks.concat(movedBlock));
handleHistory(newBlocks.concat(movedBlock));
},
//eslint-disable-next-line
[blocks, msTime, cells, maxCellIndex]);
/**
* Moves timeblock to the nearest bottom cell after stopped move or resize it
*/
const setBotPosition = useCallback((block) => {
var _a;
if (block.endTime - block.startTime < msTime)
return;
const newBlocks = blocks.filter((b) => b.id !== block.id);
const endRow = Math.abs(Math.round(block.endTime / msTime));
const needCells = cells.find((cell) => cell.row === endRow);
let position = (_a = needCells === null || needCells === void 0 ? void 0 : needCells.position) === null || _a === void 0 ? void 0 : _a.y;
if (!position) {
const maxRow = cells[maxCellIndex];
position = maxRow.position.h;
}
const movedBlock = Object.assign(Object.assign({}, block), { realStartTime: block.startTime, realEndTime: block.endTime, height: position - block.top });
setBlocks(newBlocks.concat(movedBlock));
handleHistory(newBlocks.concat(movedBlock));
},
//eslint-disable-next-line
[blocks, cells, maxCellIndex, msTime]);
/**
* Block-merger - crossed blocks merge
*/
useEffect(() => {
if (isDrawing || isLocking)
return;
if (blocks.length < 2)
return;
const columns = [...new Set(blocks.map((cb) => cb.column))];
columns.forEach((col) => {
const timeBlocksInColumn = blocks.filter((cb) => cb.column === col);
if (timeBlocksInColumn.length < 2)
return;
const sortedBlocks = timeBlocksInColumn.sort((prev, cur) => {
if (prev.top <= cur.top)
return -1;
return 1;
});
for (let i = 0; i < sortedBlocks.length - 1; i++) {
const current = sortedBlocks[i];
const next = sortedBlocks[i + 1];
const cEnd = current.top + current.height;
const nEnd = next.top + next.height;
const newSetBlocks = blocks.filter((cb) => cb.id !== current.id && cb.id !== next.id);
if (cEnd <= nEnd + 1 && cEnd >= next.top - 1) {
const object = {
id: random(),
startTime: current.startTime,
endTime: next.endTime,
width: current.width,
height: next.top - current.top + next.height,
top: current.top,
left: current.left,
column: col,
isTemp: false,
realStartTime: current.startTime,
realEndTime: next.endTime,
};
setBlocks(newSetBlocks.concat(object));
break;
}
if (next.top <= cEnd && next.top >= current.top) {
const object = Object.assign(Object.assign({}, current), { id: random() });
setBlocks(newSetBlocks.concat(object));
break;
}
}
});
}, [blocks, isDrawing, isLocking]);
/**
* Block-builder - visualize timeblock from selected cells
*/
useEffect(() => {
if (!cells)
return;
const selectedCells = cells.filter((cell) => cell.isSelected);
if (selectedCells.length === 0)
return;
const columns = [...new Set(selectedCells.map((cell) => cell.column))];
const newBlocks = columns.map((column) => {
const rows = selectedCells.filter((cell) => cell.column === column);
const startRow = rows[0];
const endRow = rows[rows.length - 1];
const timeFrameStart = startRow.row * msTime;
const timeFrameEnd = (endRow.row + 1) * msTime;
return {
id: random(),
startTime: timeFrameStart,
endTime: timeFrameEnd,
width: widthStep * 0.75,
height: endRow.position.h - startRow.position.y,
top: startRow.position.y,
left: startRow.position.x + 5,
column: column,
isTemp: isDrawing,
realStartTime: timeFrameStart,
realEndTime: timeFrameEnd,
};
});
if (!isDrawing) {
setBlocks(blocks.concat(newBlocks));
handleHistory(blocks.concat(newBlocks));
setPreview([]);
}
else {
setPreview(blocks.concat(newBlocks));
}
setCells(cells.map((cell) => (Object.assign(Object.assign({}, cell), { isSelected: false }))));
}, [blocks, cells, isDrawing, preview, msTime, widthStep]);
/**
* Input-offseter - sets input schedule groups to required timezone
*/
useEffect(() => {
if (!defaultValue || !widthStep || !helperWidthProp)
return;
const correctedBlocks = inputOffset(inputParser(defaultValue, columns.length, tzOffset), columns.length);
const newBlocks = correctedBlocks.map((ib) => (Object.assign(Object.assign({}, ib), { id: random(), realStartTime: ib.startTime, realEndTime: ib.endTime, top: ib.startTime / millisPerPixel + headerHeightProp, left: widthStep * ib.column + 5 + helperWidthProp, width: widthStep * 0.75, height: (ib.endTime - ib.startTime) / millisPerPixel, isTemp: false })));
setBlocks(newBlocks);
handleHistory(newBlocks);
}, [
columns === null || columns === void 0 ? void 0 : columns.length,
defaultValue,
headerHeightProp,
helperWidthProp,
millisPerPixel,
tzOffset,
widthStep,
]);
/**
* Output-offseter - sets output schedule groups to UTC+0 timezone
*/
useEffect(() => {
if (!onChange || isLocking)
return;
const mergedBlocks = outputMerger(outputOffset(blocks, tzOffset, columns), columns);
const unique = new Array();
mergedBlocks.forEach((b) => {
const find = unique.find((u) => u.startTime === b.startTime && u.endTime === b.endTime);
if (find)
return;
unique.push({ startTime: b.startTime, endTime: b.endTime });
});
const shGroup = unique.map((u) => {
const group = mergedBlocks.filter((b) => b.startTime === u.startTime && b.endTime === u.endTime);
const mask = group.reduce((acc, m) => acc + m.column, 0);
return {
startTime: u.startTime,
endTime: u.endTime,
mask: mask,
};
});
onChange(shGroup);
}, [blocks, columns, isLocking, tzOffset]);
return (_jsx(TimeBlockContext.Provider, Object.assign({ value: {
undoHistory,
blocks,
setTopPosition,
preview,
setBotPosition,
setBlocks,
handleHistory,
} }, { children: children })));
};
export default TimeBlockProvider;