breaks
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
work-timerer:
|
||||
image: git.frik.su/Beesquit/work-timerer:latest
|
||||
build: .
|
||||
container_name: work-timerer
|
||||
ports:
|
||||
- 4000:80
|
||||
|
||||
+18
-2
@@ -43,6 +43,11 @@ H1 {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
#currentTime {
|
||||
font-size: 3em;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.time-blocks {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
@@ -80,11 +85,22 @@ H1 {
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
#currentTime {
|
||||
font-size: 3em;
|
||||
.next-break {
|
||||
font-size: 2em;
|
||||
text-align: center;
|
||||
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 {
|
||||
margin-top: 30px;
|
||||
|
||||
+2
-8
@@ -16,19 +16,13 @@
|
||||
|
||||
<div class="vblock">
|
||||
<div class="hblock">
|
||||
<div class="time-blocks jetbrains-mono" id="targetTimes"></div>
|
||||
<div class="time-blocks jetbrains-mono" id="breakTimes"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vblock">
|
||||
<div class="hblock">
|
||||
<div class="time-blocks jetbrains-mono" id="leaveTimes"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="vblock">
|
||||
<div class="hblock">
|
||||
<div class="time-blocks jetbrains-mono" id="timeDifferences"></div>
|
||||
<div class="next-break jetbrains-mono" id="nextBreak"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+152
-131
@@ -1,10 +1,21 @@
|
||||
const TRAVEL_TIME_HOURS = 1;
|
||||
const FIRST_HOUR = 13;
|
||||
const LAST_HOUR = 22;
|
||||
const WEEKDAY_BREAKS = [
|
||||
{ start: "10:15", end: "10:30" },
|
||||
{ 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;
|
||||
let diffNodes = [];
|
||||
let resizeTimer = null;
|
||||
const WEEKEND_BREAKS = [
|
||||
{ start: "11:00", end: "11:15" },
|
||||
{ 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 = "";
|
||||
|
||||
function pad2(n) {
|
||||
@@ -19,98 +30,29 @@ function formatDate(d) {
|
||||
return pad2(d.getDate()) + "." + pad2(d.getMonth() + 1) + "." + d.getFullYear();
|
||||
}
|
||||
|
||||
function diffText(targetMs, nowMs) {
|
||||
const diffMs = targetMs - nowMs;
|
||||
if (diffMs < 0) {
|
||||
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 breakTimeToMinutes(t) {
|
||||
const parts = t.split(":");
|
||||
return parseInt(parts[0], 10) * 60 + parseInt(parts[1], 10);
|
||||
}
|
||||
|
||||
function buildLabel(text) {
|
||||
const span = document.createElement("span");
|
||||
span.className = "time-block label-block";
|
||||
span.textContent = text;
|
||||
return span;
|
||||
function breakMsForToday(t, now) {
|
||||
const d = new Date(now);
|
||||
const parts = t.split(":");
|
||||
d.setHours(parseInt(parts[0], 10), parseInt(parts[1], 10), 0, 0);
|
||||
return d.getTime();
|
||||
}
|
||||
|
||||
function buildTimeBlock(text) {
|
||||
const span = document.createElement("span");
|
||||
span.className = "time-block";
|
||||
span.textContent = text;
|
||||
return span;
|
||||
function getBreaksForToday(now) {
|
||||
const day = now.getDay();
|
||||
return day === 0 || day === 6 ? WEEKEND_BREAKS : WEEKDAY_BREAKS;
|
||||
}
|
||||
|
||||
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 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 formatDuration(ms) {
|
||||
const totalSeconds = Math.floor(ms / 1000);
|
||||
const h = Math.floor(totalSeconds / 3600);
|
||||
const m = Math.floor((totalSeconds % 3600) / 60);
|
||||
const s = totalSeconds % 60;
|
||||
return pad2(h) + ":" + pad2(m) + ":" + pad2(s);
|
||||
}
|
||||
|
||||
function buildCalendar() {
|
||||
@@ -197,21 +139,123 @@ function scheduleNextClockTick() {
|
||||
setTimeout(updateClock, delay);
|
||||
}
|
||||
|
||||
function updateDiffs() {
|
||||
const now = new Date();
|
||||
for (let hour = FIRST_HOUR; hour <= LAST_HOUR; hour++) {
|
||||
const target = new Date();
|
||||
target.setHours(hour - TRAVEL_TIME_HOURS, 0, 0, 0);
|
||||
diffNodes[hour - FIRST_HOUR].textContent = diffText(target.getTime(), now.getTime());
|
||||
}
|
||||
scheduleNextDiffTick();
|
||||
function buildBreakBlocks() {
|
||||
const breakTimesElement = document.getElementById("breakTimes");
|
||||
breakTimesElement.innerHTML = "";
|
||||
breakTimesElement.appendChild(buildLabel("BK"));
|
||||
|
||||
const blocks = [];
|
||||
getBreaksForToday(new Date()).forEach(function (b) {
|
||||
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 msUntilNextMinute =
|
||||
(60 - now.getSeconds()) * 1000 - now.getMilliseconds();
|
||||
setTimeout(updateDiffs, msUntilNextMinute);
|
||||
const nowMinutes = now.getHours() * 60 + now.getMinutes() + now.getSeconds() / 60;
|
||||
const breaks = getBreaksForToday(now);
|
||||
|
||||
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() {
|
||||
@@ -222,14 +266,16 @@ function checkDayChange() {
|
||||
lastDayKey = key;
|
||||
dateEl.textContent = formatDate(now);
|
||||
buildCalendar();
|
||||
buildBreakBlocks();
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
clockEl = document.getElementById("currentTime");
|
||||
dateEl = document.getElementById("currentDate");
|
||||
nextBreakEl = document.getElementById("nextBreak");
|
||||
|
||||
buildTargetBlocks();
|
||||
buildBreakBlocks();
|
||||
buildCalendar();
|
||||
|
||||
const now = new Date();
|
||||
@@ -237,34 +283,9 @@ function init() {
|
||||
dateEl.textContent = formatDate(now);
|
||||
|
||||
updateClock();
|
||||
updateDiffs();
|
||||
updateNextBreak();
|
||||
|
||||
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();
|
||||
window.addEventListener("resize", onResize);
|
||||
|
||||
Reference in New Issue
Block a user