breaks
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
work-timerer:
|
work-timerer:
|
||||||
image: git.frik.su/Beesquit/work-timerer:latest
|
build: .
|
||||||
container_name: work-timerer
|
container_name: work-timerer
|
||||||
ports:
|
ports:
|
||||||
- 4000:80
|
- 4000:80
|
||||||
|
|||||||
+18
-2
@@ -43,6 +43,11 @@ H1 {
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#currentTime {
|
||||||
|
font-size: 3em;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
.time-blocks {
|
.time-blocks {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
@@ -80,11 +85,22 @@ H1 {
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#currentTime {
|
.next-break {
|
||||||
font-size: 3em;
|
font-size: 2em;
|
||||||
|
text-align: center;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.time-block.break-current {
|
||||||
|
border: 2px solid #d4a017;
|
||||||
|
color: #d4a017;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-block.break-next {
|
||||||
|
border: 2px solid #6b9ac0;
|
||||||
|
color: #6b9ac0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.calendar-container {
|
.calendar-container {
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
|
|||||||
+2
-8
@@ -16,19 +16,13 @@
|
|||||||
|
|
||||||
<div class="vblock">
|
<div class="vblock">
|
||||||
<div class="hblock">
|
<div class="hblock">
|
||||||
<div class="time-blocks jetbrains-mono" id="targetTimes"></div>
|
<div class="time-blocks jetbrains-mono" id="breakTimes"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="vblock">
|
<div class="vblock">
|
||||||
<div class="hblock">
|
<div class="hblock">
|
||||||
<div class="time-blocks jetbrains-mono" id="leaveTimes"></div>
|
<div class="next-break jetbrains-mono" id="nextBreak"></div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="vblock">
|
|
||||||
<div class="hblock">
|
|
||||||
<div class="time-blocks jetbrains-mono" id="timeDifferences"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+152
-131
@@ -1,10 +1,21 @@
|
|||||||
const TRAVEL_TIME_HOURS = 1;
|
const WEEKDAY_BREAKS = [
|
||||||
const FIRST_HOUR = 13;
|
{ start: "10:15", end: "10:30" },
|
||||||
const LAST_HOUR = 22;
|
{ start: "12:00", end: "12:30" },
|
||||||
|
{ start: "14:00", end: "14:15" },
|
||||||
|
{ start: "16:30", end: "17:00" },
|
||||||
|
{ start: "19:45", end: "20:00" },
|
||||||
|
{ start: "21:00", end: "21:15" },
|
||||||
|
];
|
||||||
|
|
||||||
let clockEl, dateEl;
|
const WEEKEND_BREAKS = [
|
||||||
let diffNodes = [];
|
{ start: "11:00", end: "11:15" },
|
||||||
let resizeTimer = null;
|
{ start: "14:30", end: "15:00" },
|
||||||
|
{ start: "17:30", end: "17:45" },
|
||||||
|
{ start: "20:00", end: "20:15" },
|
||||||
|
];
|
||||||
|
|
||||||
|
let clockEl, dateEl, nextBreakEl;
|
||||||
|
let breakNodes = [];
|
||||||
let lastDayKey = "";
|
let lastDayKey = "";
|
||||||
|
|
||||||
function pad2(n) {
|
function pad2(n) {
|
||||||
@@ -19,98 +30,29 @@ function formatDate(d) {
|
|||||||
return pad2(d.getDate()) + "." + pad2(d.getMonth() + 1) + "." + d.getFullYear();
|
return pad2(d.getDate()) + "." + pad2(d.getMonth() + 1) + "." + d.getFullYear();
|
||||||
}
|
}
|
||||||
|
|
||||||
function diffText(targetMs, nowMs) {
|
function breakTimeToMinutes(t) {
|
||||||
const diffMs = targetMs - nowMs;
|
const parts = t.split(":");
|
||||||
if (diffMs < 0) {
|
return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
|
||||||
const diffHours = -1 * Math.ceil(diffMs / 3600000);
|
|
||||||
const diffMinutes = -1 * Math.ceil((diffMs % 3600000) / 60000);
|
|
||||||
return "-" + pad2(diffHours) + ":" + pad2(diffMinutes);
|
|
||||||
}
|
|
||||||
const diffHours = Math.floor(diffMs / 3600000);
|
|
||||||
const diffMinutes = Math.ceil((diffMs % 3600000) / 60000);
|
|
||||||
return pad2(diffHours) + ":" + pad2(diffMinutes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildLabel(text) {
|
function breakMsForToday(t, now) {
|
||||||
const span = document.createElement("span");
|
const d = new Date(now);
|
||||||
span.className = "time-block label-block";
|
const parts = t.split(":");
|
||||||
span.textContent = text;
|
d.setHours(parseInt(parts[0], 10), parseInt(parts[1], 10), 0, 0);
|
||||||
return span;
|
return d.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildTimeBlock(text) {
|
function getBreaksForToday(now) {
|
||||||
const span = document.createElement("span");
|
const day = now.getDay();
|
||||||
span.className = "time-block";
|
return day === 0 || day === 6 ? WEEKEND_BREAKS : WEEKDAY_BREAKS;
|
||||||
span.textContent = text;
|
|
||||||
return span;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function distributeBlocks(container, blocks) {
|
function formatDuration(ms) {
|
||||||
const screenWidth = window.innerWidth;
|
const totalSeconds = Math.floor(ms / 1000);
|
||||||
const blockWidth = 120;
|
const h = Math.floor(totalSeconds / 3600);
|
||||||
const labelWidth = 40;
|
const m = Math.floor((totalSeconds % 3600) / 60);
|
||||||
|
const s = totalSeconds % 60;
|
||||||
const availableWidth = screenWidth - labelWidth;
|
return pad2(h) + ":" + pad2(m) + ":" + pad2(s);
|
||||||
const blocksPerRow = Math.floor(availableWidth / blockWidth);
|
|
||||||
|
|
||||||
while (container.childNodes.length > 1) {
|
|
||||||
container.removeChild(container.lastChild);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blocksPerRow > blocks.length) {
|
|
||||||
blocks.forEach(function (b) { container.appendChild(b); });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalBlocks = blocks.length;
|
|
||||||
const halfBlocks = Math.ceil(totalBlocks / 2);
|
|
||||||
|
|
||||||
const rowsContainer = document.createElement("div");
|
|
||||||
rowsContainer.className = "multi-rows-container";
|
|
||||||
|
|
||||||
const topRow = document.createElement("div");
|
|
||||||
topRow.className = "time-row";
|
|
||||||
for (let i = 0; i < halfBlocks; i++) {
|
|
||||||
topRow.appendChild(blocks[i]);
|
|
||||||
}
|
|
||||||
rowsContainer.appendChild(topRow);
|
|
||||||
|
|
||||||
const bottomRow = document.createElement("div");
|
|
||||||
bottomRow.className = "time-row";
|
|
||||||
for (let i = halfBlocks; i < totalBlocks; i++) {
|
|
||||||
bottomRow.appendChild(blocks[i]);
|
|
||||||
}
|
|
||||||
rowsContainer.appendChild(bottomRow);
|
|
||||||
|
|
||||||
container.appendChild(rowsContainer);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildTargetBlocks() {
|
|
||||||
const targetTimesElement = document.getElementById("targetTimes");
|
|
||||||
const leaveTimesElement = document.getElementById("leaveTimes");
|
|
||||||
const timeDifferencesElement = document.getElementById("timeDifferences");
|
|
||||||
|
|
||||||
targetTimesElement.appendChild(buildLabel("TT"));
|
|
||||||
leaveTimesElement.appendChild(buildLabel("LT"));
|
|
||||||
timeDifferencesElement.appendChild(buildLabel("TD"));
|
|
||||||
|
|
||||||
const targetBlocks = [];
|
|
||||||
const leaveBlocks = [];
|
|
||||||
const diffBlocks = [];
|
|
||||||
|
|
||||||
for (let hour = FIRST_HOUR; hour <= LAST_HOUR; hour++) {
|
|
||||||
const leaveHour = hour - TRAVEL_TIME_HOURS;
|
|
||||||
|
|
||||||
targetBlocks.push(buildTimeBlock(pad2(hour) + ":00"));
|
|
||||||
leaveBlocks.push(buildTimeBlock(pad2(leaveHour) + ":00"));
|
|
||||||
diffBlocks.push(buildTimeBlock(""));
|
|
||||||
}
|
|
||||||
|
|
||||||
distributeBlocks(targetTimesElement, targetBlocks);
|
|
||||||
distributeBlocks(leaveTimesElement, leaveBlocks);
|
|
||||||
distributeBlocks(timeDifferencesElement, diffBlocks);
|
|
||||||
|
|
||||||
diffNodes = diffBlocks;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildCalendar() {
|
function buildCalendar() {
|
||||||
@@ -197,21 +139,123 @@ function scheduleNextClockTick() {
|
|||||||
setTimeout(updateClock, delay);
|
setTimeout(updateClock, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDiffs() {
|
function buildBreakBlocks() {
|
||||||
const now = new Date();
|
const breakTimesElement = document.getElementById("breakTimes");
|
||||||
for (let hour = FIRST_HOUR; hour <= LAST_HOUR; hour++) {
|
breakTimesElement.innerHTML = "";
|
||||||
const target = new Date();
|
breakTimesElement.appendChild(buildLabel("BK"));
|
||||||
target.setHours(hour - TRAVEL_TIME_HOURS, 0, 0, 0);
|
|
||||||
diffNodes[hour - FIRST_HOUR].textContent = diffText(target.getTime(), now.getTime());
|
const blocks = [];
|
||||||
}
|
getBreaksForToday(new Date()).forEach(function (b) {
|
||||||
scheduleNextDiffTick();
|
blocks.push(buildTimeBlock(b.start + "-" + b.end));
|
||||||
|
});
|
||||||
|
|
||||||
|
breakNodes = blocks;
|
||||||
|
distributeBlocks(breakTimesElement, blocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleNextDiffTick() {
|
function buildLabel(text) {
|
||||||
|
const span = document.createElement("span");
|
||||||
|
span.className = "time-block label-block";
|
||||||
|
span.textContent = text;
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTimeBlock(text) {
|
||||||
|
const span = document.createElement("span");
|
||||||
|
span.className = "time-block";
|
||||||
|
span.textContent = text;
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
|
function distributeBlocks(container, blocks) {
|
||||||
|
const screenWidth = window.innerWidth;
|
||||||
|
const blockWidth = 120;
|
||||||
|
const labelWidth = 40;
|
||||||
|
|
||||||
|
const availableWidth = screenWidth - labelWidth;
|
||||||
|
const blocksPerRow = Math.floor(availableWidth / blockWidth);
|
||||||
|
|
||||||
|
while (container.childNodes.length > 1) {
|
||||||
|
container.removeChild(container.lastChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocksPerRow > blocks.length) {
|
||||||
|
blocks.forEach(function (b) { container.appendChild(b); });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalBlocks = blocks.length;
|
||||||
|
const halfBlocks = Math.ceil(totalBlocks / 2);
|
||||||
|
|
||||||
|
const rowsContainer = document.createElement("div");
|
||||||
|
rowsContainer.className = "multi-rows-container";
|
||||||
|
|
||||||
|
const topRow = document.createElement("div");
|
||||||
|
topRow.className = "time-row";
|
||||||
|
for (let i = 0; i < halfBlocks; i++) {
|
||||||
|
topRow.appendChild(blocks[i]);
|
||||||
|
}
|
||||||
|
rowsContainer.appendChild(topRow);
|
||||||
|
|
||||||
|
const bottomRow = document.createElement("div");
|
||||||
|
bottomRow.className = "time-row";
|
||||||
|
for (let i = halfBlocks; i < totalBlocks; i++) {
|
||||||
|
bottomRow.appendChild(blocks[i]);
|
||||||
|
}
|
||||||
|
rowsContainer.appendChild(bottomRow);
|
||||||
|
|
||||||
|
container.appendChild(rowsContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNextBreak() {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const msUntilNextMinute =
|
const nowMinutes = now.getHours() * 60 + now.getMinutes() + now.getSeconds() / 60;
|
||||||
(60 - now.getSeconds()) * 1000 - now.getMilliseconds();
|
const breaks = getBreaksForToday(now);
|
||||||
setTimeout(updateDiffs, msUntilNextMinute);
|
|
||||||
|
let current = null;
|
||||||
|
let next = null;
|
||||||
|
|
||||||
|
breaks.forEach(function (b, index) {
|
||||||
|
const startM = breakTimeToMinutes(b.start);
|
||||||
|
const endM = breakTimeToMinutes(b.end);
|
||||||
|
if (nowMinutes >= startM && nowMinutes < endM) {
|
||||||
|
current = index;
|
||||||
|
} else if (endM > nowMinutes && next === null) {
|
||||||
|
next = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
breakNodes.forEach(function (node, index) {
|
||||||
|
node.classList.remove("break-current", "break-next");
|
||||||
|
if (index === current) {
|
||||||
|
node.classList.add("break-current");
|
||||||
|
} else if (index === next) {
|
||||||
|
node.classList.add("break-next");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (current !== null) {
|
||||||
|
const b = breaks[current];
|
||||||
|
const endMs = breakMsForToday(b.end, now);
|
||||||
|
nextBreakEl.textContent =
|
||||||
|
"Перерыв " + b.start + "-" + b.end +
|
||||||
|
" · до конца " + formatDuration(endMs - now.getTime());
|
||||||
|
} else if (next !== null) {
|
||||||
|
const b = breaks[next];
|
||||||
|
const startMs = breakMsForToday(b.start, now);
|
||||||
|
nextBreakEl.textContent =
|
||||||
|
"До перерыва " + b.start + "-" + b.end +
|
||||||
|
" · " + formatDuration(startMs - now.getTime());
|
||||||
|
} else {
|
||||||
|
nextBreakEl.textContent = "Перерывов больше нет";
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleNextBreakTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleNextBreakTick() {
|
||||||
|
const delay = 1000 - new Date().getMilliseconds();
|
||||||
|
setTimeout(updateNextBreak, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkDayChange() {
|
function checkDayChange() {
|
||||||
@@ -222,14 +266,16 @@ function checkDayChange() {
|
|||||||
lastDayKey = key;
|
lastDayKey = key;
|
||||||
dateEl.textContent = formatDate(now);
|
dateEl.textContent = formatDate(now);
|
||||||
buildCalendar();
|
buildCalendar();
|
||||||
|
buildBreakBlocks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
clockEl = document.getElementById("currentTime");
|
clockEl = document.getElementById("currentTime");
|
||||||
dateEl = document.getElementById("currentDate");
|
dateEl = document.getElementById("currentDate");
|
||||||
|
nextBreakEl = document.getElementById("nextBreak");
|
||||||
|
|
||||||
buildTargetBlocks();
|
buildBreakBlocks();
|
||||||
buildCalendar();
|
buildCalendar();
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
@@ -237,34 +283,9 @@ function init() {
|
|||||||
dateEl.textContent = formatDate(now);
|
dateEl.textContent = formatDate(now);
|
||||||
|
|
||||||
updateClock();
|
updateClock();
|
||||||
updateDiffs();
|
updateNextBreak();
|
||||||
|
|
||||||
setInterval(checkDayChange, 60000);
|
setInterval(checkDayChange, 60000);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onResize() {
|
|
||||||
if (resizeTimer !== null) clearTimeout(resizeTimer);
|
|
||||||
resizeTimer = setTimeout(function () {
|
|
||||||
resizeTimer = null;
|
|
||||||
const targetTimesElement = document.getElementById("targetTimes");
|
|
||||||
const leaveTimesElement = document.getElementById("leaveTimes");
|
|
||||||
const timeDifferencesElement = document.getElementById("timeDifferences");
|
|
||||||
|
|
||||||
const allBlocks = [].slice.call(
|
|
||||||
timeDifferencesElement.querySelectorAll(".time-block:not(.label-block)")
|
|
||||||
);
|
|
||||||
const targetBlocks = [].slice.call(
|
|
||||||
targetTimesElement.querySelectorAll(".time-block:not(.label-block)")
|
|
||||||
);
|
|
||||||
const leaveBlocks = [].slice.call(
|
|
||||||
leaveTimesElement.querySelectorAll(".time-block:not(.label-block)")
|
|
||||||
);
|
|
||||||
|
|
||||||
distributeBlocks(targetTimesElement, targetBlocks);
|
|
||||||
distributeBlocks(leaveTimesElement, leaveBlocks);
|
|
||||||
distributeBlocks(timeDifferencesElement, allBlocks);
|
|
||||||
}, 150);
|
|
||||||
}
|
|
||||||
|
|
||||||
init();
|
init();
|
||||||
window.addEventListener("resize", onResize);
|
|
||||||
|
|||||||
Reference in New Issue
Block a user