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:
work-timerer:
image: git.frik.su/Beesquit/work-timerer:latest
build: .
container_name: work-timerer
ports:
- 4000:80
+18 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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);