This commit is contained in:
2026-07-31 19:03:37 +03:00
parent ac2696d387
commit 1998f9ab87
4 changed files with 173 additions and 142 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);