Skip to content

Commit ae3da03

Browse files
authored
Add files via upload
1 parent 53ca52c commit ae3da03

12 files changed

Lines changed: 1779 additions & 0 deletions

FieldTrak.code-workspace

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"folders": [
3+
{
4+
"path": "."
5+
}
6+
],
7+
"settings": {}
8+
}

calendar.html

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
<!doctype html>
2+
<html lang="el">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>FieldTrak — Ημερολόγιο</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1" />
7+
<meta name="theme-color" content="#0A66FF" />
8+
<link rel="manifest" href="manifest.webmanifest" />
9+
<link rel="stylesheet" href="style.css" />
10+
</head>
11+
<body>
12+
<!-- Κοινό navbar -->
13+
<header class="app-bar no-print">
14+
<div class="app-bar-inner">
15+
<div class="brand">
16+
<img src="fieldtrak1.png" alt="FieldTrak" onerror="this.onerror=null;this.src='/mnt/data/icon.png';">
17+
<span>FieldTrak</span>
18+
</div>
19+
<nav class="app-nav">
20+
<a href="index.html" data-i18n="nav.home">Αρχική</a>
21+
<a href="work_order.html" data-i18n="nav.workorder">Εντολή Εργασίας</a>
22+
<a href="station.html" data-i18n="nav.station">Έλεγχος Εγκαταστάσεων</a>
23+
<a class="active" href="calendar.html" data-i18n="nav.calendar">Ημερολόγιο</a>
24+
<a href="history.html" data-i18n="nav.history">Ιστορικό</a>
25+
</nav>
26+
<div class="lang-segment" role="group" aria-label="Language">
27+
<button type="button" class="lang-btn" data-lang="el">EL</button>
28+
<button type="button" class="lang-btn" data-lang="en">EN</button>
29+
<button type="button" class="lang-btn" data-lang="it">IT</button>
30+
</div>
31+
</div>
32+
</header>
33+
34+
<main class="container">
35+
<h1 class="page-title" data-i18n="calendar.title">Ημερολόγιο</h1>
36+
37+
<section class="card">
38+
<div class="btn-row no-print" style="justify-content:space-between; margin-bottom:10px;">
39+
<div class="btn-row">
40+
<button class="btn btn-secondary" id="prevBtn" data-i18n="calendar.prev">‹ Προηγ.</button>
41+
<button class="btn btn-secondary" id="todayBtn" data-i18n="calendar.today">Σήμερα</button>
42+
<button class="btn btn-secondary" id="nextBtn" data-i18n="calendar.next">Επόμ. ›</button>
43+
</div>
44+
<div class="btn-row">
45+
<button class="btn" id="printBtn" data-i18n="calendar.print">Εκτύπωση / PDF</button>
46+
<button class="btn btn-primary" id="emailBtn" data-i18n="calendar.email">Αποστολή Email</button>
47+
</div>
48+
</div>
49+
50+
<h2 id="monthLabel" class="subtle" style="margin:0 0 8px 2px;"></h2>
51+
52+
<div class="calendar">
53+
<div class="weekdays">
54+
<div data-i18n="calendar.weekdayMon">Δευ</div><div data-i18n="calendar.weekdayTue">Τρ</div><div data-i18n="calendar.weekdayWed">Τετ</div><div data-i18n="calendar.weekdayThu">Πεμ</div><div data-i18n="calendar.weekdayFri">Παρ</div><div data-i18n="calendar.weekdaySat">Σαβ</div><div data-i18n="calendar.weekdaySun">Κυρ</div>
55+
</div>
56+
<div class="grid" id="grid"></div>
57+
</div>
58+
</section>
59+
60+
<!-- PRINT-ONLY notes -->
61+
<section id="printNotes" class="card print-block" style="display:none">
62+
<h2 style="margin:0 0 8px 0;" data-i18n="calendar.printNotesTitle">Σημειώσεις Ημερολογίου</h2>
63+
<div class="subtle" id="printRange" style="margin-bottom:6px;"></div>
64+
<ol id="notesList" style="padding-left:18px;"></ol>
65+
</section>
66+
</main>
67+
68+
<footer class="footer no-print" data-i18n="app.footer">© 2025 Theocharis Anastopoulos — FieldTrak</footer>
69+
70+
<script src="i18n.js"></script>
71+
<script>
72+
(function(){ try { if (navigator && 'serviceWorker' in navigator) navigator.serviceWorker.register('sw.js'); } catch (e) {} })();
73+
document.addEventListener('DOMContentLoaded', function() { FieldTrakI18n.initLangControl(); });
74+
</script>
75+
<dialog id="noteDialog">
76+
<div class="dialog-head">
77+
<span id="dialogTitle" data-i18n="calendar.dialogTitle">Σημείωση</span>
78+
<button class="btn btn-outline" id="closeDialog"></button>
79+
</div>
80+
<div class="dialog-body">
81+
<textarea id="noteText" data-i18n-placeholder="calendar.notePlaceholder" placeholder="Γράψτε σημείωση για την ημέρα..."></textarea>
82+
</div>
83+
<div class="dialog-actions">
84+
<button class="btn btn-danger" id="deleteNote" data-i18n="calendar.delete">Διαγραφή</button>
85+
<div class="btn-row">
86+
<button class="btn" id="cancelNote" data-i18n="calendar.cancel">Άκυρο</button>
87+
<button class="btn btn-primary" id="saveNote" data-i18n="calendar.save">Αποθήκευση</button>
88+
</div>
89+
</div>
90+
</dialog>
91+
92+
<script>
93+
function getMonthNames() { var lang = FieldTrakI18n.getLang(); return FieldTrakI18n.LANG[lang] && FieldTrakI18n.LANG[lang].calendarMonths ? FieldTrakI18n.LANG[lang].calendarMonths : FieldTrakI18n.LANG.el.calendarMonths; }
94+
// ---- Utilities ----
95+
const grid = document.getElementById('grid');
96+
const monthLabel = document.getElementById('monthLabel');
97+
const noteDialog = document.getElementById('noteDialog');
98+
const noteText = document.getElementById('noteText');
99+
const dialogTitle = document.getElementById('dialogTitle');
100+
const printRange = document.getElementById('printRange');
101+
const notesList = document.getElementById('notesList');
102+
103+
let view = (() => { const now = new Date(); return { y: now.getFullYear(), m: now.getMonth() }; })();
104+
const LS_KEY = 'fieldtrak_calendar_notes_v1';
105+
const loadNotes = () => { try { return JSON.parse(localStorage.getItem(LS_KEY) || '{}'); } catch(e) { return {}; } };
106+
const saveNotes = (m) => localStorage.setItem(LS_KEY, JSON.stringify(m || {}));
107+
const fmtDateKey = (dt) => `${dt.getFullYear()}-${String(dt.getMonth()+1).padStart(2,'0')}-${String(dt.getDate()).padStart(2,'0')}`;
108+
109+
function isSameDate(a,b){return a.getFullYear()===b.getFullYear() && a.getMonth()===b.getMonth() && a.getDate()===b.getDate();}
110+
111+
// ---- Render Calendar ----
112+
function render() {
113+
grid.innerHTML = '';
114+
const first = new Date(view.y, view.m, 1);
115+
const startWeekday = (first.getDay() + 6) % 7; // 0=Mon
116+
const daysInMonth = new Date(view.y, view.m + 1, 0).getDate();
117+
const totalCells = 42;
118+
const notes = loadNotes();
119+
const today = new Date();
120+
121+
monthLabel.textContent = getMonthNames()[view.m] + ' ' + view.y;
122+
123+
for (let i = 0; i < totalCells; i++) {
124+
const cell = document.createElement('div');
125+
cell.className = 'cell';
126+
const dayNum = i - startWeekday + 1;
127+
let dateObj;
128+
129+
if (dayNum < 1) {
130+
const prevMonthLastDay = new Date(view.y, view.m, 0).getDate();
131+
dateObj = new Date(view.y, view.m - 1, prevMonthLastDay + dayNum);
132+
cell.classList.add('outside');
133+
} else if (dayNum > daysInMonth) {
134+
dateObj = new Date(view.y, view.m + 1, dayNum - daysInMonth);
135+
cell.classList.add('outside');
136+
} else {
137+
dateObj = new Date(view.y, view.m, dayNum);
138+
}
139+
140+
const key = fmtDateKey(dateObj);
141+
const hasNote = (notes[key] && notes[key].trim().length > 0);
142+
143+
const badge = document.createElement('div');
144+
badge.className = 'date-badge';
145+
badge.textContent = dateObj.getDate();
146+
cell.appendChild(badge);
147+
148+
if (isSameDate(dateObj, today)) cell.classList.add('today');
149+
150+
if (hasNote) {
151+
const dot = document.createElement('div');
152+
dot.className = 'note-dot';
153+
cell.appendChild(dot);
154+
cell.title = notes[key].split('\n')[0];
155+
}
156+
157+
cell.addEventListener('click', () => openNote(dateObj));
158+
grid.appendChild(cell);
159+
}
160+
}
161+
162+
// ---- Note Modal ----
163+
let currentDateKey = null;
164+
function openNote(dateObj) {
165+
currentDateKey = fmtDateKey(dateObj);
166+
const notes = loadNotes();
167+
noteText.value = notes[currentDateKey] || '';
168+
dialogTitle.textContent = FieldTrakI18n.t('calendar.dialogTitle') + ' — ' + currentDateKey;
169+
if (typeof noteDialog.showModal === 'function') noteDialog.showModal();
170+
else {
171+
const val = prompt(`Σημείωση για ${currentDateKey}:`, noteText.value);
172+
if (val !== null) { notes[currentDateKey] = val.trim(); saveNotes(notes); render(); }
173+
}
174+
}
175+
176+
document.getElementById('saveNote').addEventListener('click', () => {
177+
const notes = loadNotes();
178+
notes[currentDateKey] = (noteText.value || '').trim();
179+
saveNotes(notes);
180+
noteDialog.close();
181+
render();
182+
});
183+
document.getElementById('deleteNote').addEventListener('click', () => {
184+
const notes = loadNotes(); delete notes[currentDateKey]; saveNotes(notes);
185+
noteDialog.close(); render();
186+
});
187+
document.getElementById('cancelNote').addEventListener('click', () => noteDialog.close());
188+
document.getElementById('closeDialog').addEventListener('click', () => noteDialog.close());
189+
190+
// ---- Navigation ----
191+
document.getElementById('prevBtn').addEventListener('click', () => { if (view.m===0){view.m=11;view.y--;} else view.m--; render(); });
192+
document.getElementById('nextBtn').addEventListener('click', () => { if (view.m===11){view.m=0;view.y++;} else view.m++; render(); });
193+
document.getElementById('todayBtn').addEventListener('click', () => { const n=new Date(); view.y=n.getFullYear(); view.m=n.getMonth(); render(); });
194+
195+
// ---- Printable Notes ----
196+
function buildPrintableNotes() {
197+
notesList.innerHTML = '';
198+
const notes = loadNotes();
199+
const end = new Date(view.y, view.m + 1, 0);
200+
printRange.textContent = getMonthNames()[view.m] + ' ' + view.y;
201+
202+
for (let day = 1; day <= end.getDate(); day++) {
203+
const dt = new Date(view.y, view.m, day);
204+
const key = fmtDateKey(dt);
205+
const val = (notes[key] || '').trim();
206+
if (!val) continue;
207+
208+
const li = document.createElement('li');
209+
li.innerHTML = `<span style="font-weight:700">${key}:</span> ${val}`;
210+
notesList.appendChild(li);
211+
}
212+
213+
if (!notesList.children.length) {
214+
const li = document.createElement('li');
215+
li.textContent = FieldTrakI18n.t('calendar.emptyNotes');
216+
notesList.appendChild(li);
217+
}
218+
}
219+
220+
document.getElementById('printBtn').addEventListener('click', () => {
221+
buildPrintableNotes();
222+
setTimeout(() => window.print(), 50);
223+
});
224+
225+
document.getElementById('emailBtn').addEventListener('click', () => {
226+
const notes = loadNotes();
227+
const end = new Date(view.y, view.m + 1, 0);
228+
const out = [];
229+
for (let d = 1; d <= end.getDate(); d++) {
230+
const key = fmtDateKey(new Date(view.y, view.m, d));
231+
if (notes[key]) out.push(`${key}\n${notes[key]}\n`);
232+
}
233+
const subject = FieldTrakI18n.t('calendar.emailSubject') + ' — ' + getMonthNames()[view.m] + ' ' + view.y;
234+
const body = out.length ? out.join('\n') : FieldTrakI18n.t('calendar.emailEmpty');
235+
const mailto = `mailto:?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
236+
window.location.href = mailto;
237+
});
238+
239+
window.onLanguageApplied = function() { render(); };
240+
render();
241+
</script>
242+
</body>
243+
</html>

0 commit comments

Comments
 (0)