@luckydye/calendar 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/calendar.js +397 -395
- package/package.json +1 -1
- package/src/CalDAVConfig.ts +191 -116
- package/src/CalendarInternal.ts +12 -0
- package/src/CalendarStorage.ts +5 -0
- package/src/CalendarView.ts +83 -86
- package/src/IndexedDBStorage.ts +13 -0
- package/src/InhouseBookingSource.ts +30 -1
- package/src/app.css +17 -1
- package/src/app.ts +168 -183
package/src/CalendarView.ts
CHANGED
|
@@ -55,31 +55,24 @@ export class CalendarViewElement extends LitElement {
|
|
|
55
55
|
align-items: center;
|
|
56
56
|
justify-content: space-between;
|
|
57
57
|
padding: 8px 10px;
|
|
58
|
-
background: var(--bg-
|
|
59
|
-
border: 1px solid var(--grid-color, rgba(255, 255, 255, 0.1));
|
|
58
|
+
background: var(--bg-primary, rgb(30, 30, 30));
|
|
60
59
|
flex-shrink: 0;
|
|
61
60
|
gap: 16px;
|
|
62
61
|
position: absolute;
|
|
63
|
-
bottom:
|
|
64
|
-
left:
|
|
65
|
-
right: 0
|
|
62
|
+
bottom: 0;
|
|
63
|
+
left: 0;
|
|
64
|
+
right: 0;
|
|
66
65
|
z-index: 100;
|
|
67
|
-
border-radius: var(--border-radius-lg, 8px);
|
|
68
|
-
overflow: hidden;
|
|
69
|
-
|
|
70
|
-
--backdrop-padding: 30px;
|
|
71
|
-
--backdrop-size: 42px;
|
|
72
66
|
}
|
|
73
67
|
|
|
74
68
|
.toolbar::before {
|
|
75
69
|
content: '';
|
|
76
|
-
width: 100%;
|
|
77
70
|
position: absolute;
|
|
78
|
-
top: calc(var(--backdrop-padding) * -1);
|
|
79
71
|
left: 0;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
72
|
+
right: 0;
|
|
73
|
+
bottom: 100%;
|
|
74
|
+
height: 48px;
|
|
75
|
+
background: linear-gradient(to bottom, transparent, var(--bg-primary, rgb(30, 30, 30)));
|
|
83
76
|
pointer-events: none;
|
|
84
77
|
}
|
|
85
78
|
|
|
@@ -460,13 +453,14 @@ export class CalendarViewElement extends LitElement {
|
|
|
460
453
|
flex-direction: column;
|
|
461
454
|
min-width: 0;
|
|
462
455
|
max-height: 550px;
|
|
456
|
+
border-radius: var(--border-radius-lg, 8px);
|
|
457
|
+
overflow: hidden;
|
|
463
458
|
}
|
|
464
459
|
|
|
465
460
|
.event-detail-footer {
|
|
466
461
|
border-top: 1px solid var(--grid-color, rgba(255, 255, 255, 0.1));
|
|
467
462
|
padding: 12px 16px;
|
|
468
463
|
background: var(--bg-elevated, rgba(20, 20, 20, 0.95));
|
|
469
|
-
border-radius: 0 0 var(--border-radius-lg, 8px) 0;
|
|
470
464
|
}
|
|
471
465
|
|
|
472
466
|
.invite-response-buttons {
|
|
@@ -5229,16 +5223,20 @@ export class CalendarViewElement extends LitElement {
|
|
|
5229
5223
|
</div>
|
|
5230
5224
|
|
|
5231
5225
|
<div class="event-detail-body">
|
|
5232
|
-
${
|
|
5233
|
-
event.
|
|
5234
|
-
|
|
5226
|
+
${(() => {
|
|
5227
|
+
const teamsMatch = event.description?.match(/https:\/\/teams\.microsoft\.com\/[^\s<>"]+/);
|
|
5228
|
+
const teamsUrl = teamsMatch ? teamsMatch[0] : null;
|
|
5229
|
+
if (!event.location && !teamsUrl) return null;
|
|
5230
|
+
return html`
|
|
5235
5231
|
<div class="event-detail-section">
|
|
5236
5232
|
<div class="event-detail-label">Location</div>
|
|
5237
|
-
<div class="event-detail-value"
|
|
5233
|
+
<div class="event-detail-value">
|
|
5234
|
+
${event.location ? html`<div>${event.location}</div>` : null}
|
|
5235
|
+
${teamsUrl ? html`<a href="${teamsUrl}" class="event-detail-link" target="_blank" rel="noopener">Join Microsoft Teams Meeting</a>` : null}
|
|
5236
|
+
</div>
|
|
5238
5237
|
</div>
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
}
|
|
5238
|
+
`;
|
|
5239
|
+
})()}
|
|
5242
5240
|
|
|
5243
5241
|
${
|
|
5244
5242
|
event.url
|
|
@@ -5477,70 +5475,8 @@ export class CalendarViewElement extends LitElement {
|
|
|
5477
5475
|
render() {
|
|
5478
5476
|
return html`
|
|
5479
5477
|
<div class="container ${this.isDraggingFile ? "dragging-file" : ""}">
|
|
5480
|
-
<div class="toolbar">
|
|
5481
|
-
<div class="toolbar-left">
|
|
5482
|
-
<slot name="toolbar-center"></slot>
|
|
5483
|
-
|
|
5484
|
-
<button class="toolbar-button" title="Month" @click=${
|
|
5485
|
-
this.scrollToMonth
|
|
5486
|
-
}>
|
|
5487
|
-
Month
|
|
5488
|
-
</button>
|
|
5489
|
-
<button class="toolbar-button" title="Today" @click=${
|
|
5490
|
-
this.scrollToToday
|
|
5491
|
-
}>
|
|
5492
|
-
Today
|
|
5493
|
-
</button>
|
|
5494
|
-
<button ?disabled=${
|
|
5495
|
-
this.historyStack.length === 0 ||
|
|
5496
|
-
this.historyIndex >= this.historyStack.length - 1
|
|
5497
|
-
} class="toolbar-button" title="Back" @click=${this.goBack}>
|
|
5498
|
-
←
|
|
5499
|
-
</button>
|
|
5500
|
-
<button ?disabled=${
|
|
5501
|
-
this.historyStack.length === 0 || this.historyIndex === 0
|
|
5502
|
-
} class="toolbar-button" title="Forward" @click=${this.goForward}>
|
|
5503
|
-
→
|
|
5504
|
-
</button>
|
|
5505
|
-
<div class="toolbar-zoom">
|
|
5506
|
-
<input
|
|
5507
|
-
type="range"
|
|
5508
|
-
class="toolbar-zoom-slider"
|
|
5509
|
-
min="${MIN_DAY_HEIGHT}"
|
|
5510
|
-
max="${MAX_DAY_HEIGHT}"
|
|
5511
|
-
.value=${this.dayHeight}
|
|
5512
|
-
@input=${this.onZoomSliderChange}
|
|
5513
|
-
title="Adjust zoom level"
|
|
5514
|
-
/>
|
|
5515
|
-
</div>
|
|
5516
|
-
</div>
|
|
5517
|
-
|
|
5518
|
-
<div class="toolbar-right">
|
|
5519
|
-
<!--<select
|
|
5520
|
-
class="theme-selector"
|
|
5521
|
-
.value=${this.currentTheme}
|
|
5522
|
-
@change=${this.onThemeChange}
|
|
5523
|
-
title="Select theme"
|
|
5524
|
-
>
|
|
5525
|
-
${availableThemes.map(
|
|
5526
|
-
(theme) =>
|
|
5527
|
-
html`<option value="${theme.name}">${theme.label}</option>`,
|
|
5528
|
-
)}
|
|
5529
|
-
</select>-->
|
|
5530
|
-
<div class="toolbar-search">
|
|
5531
|
-
<span class="toolbar-search-icon">🔍</span>
|
|
5532
|
-
<input
|
|
5533
|
-
class="toolbar-search-input"
|
|
5534
|
-
type="text"
|
|
5535
|
-
placeholder="Filter events..."
|
|
5536
|
-
.value=${this.filter}
|
|
5537
|
-
@input=${this.onFilterInput}
|
|
5538
|
-
/>
|
|
5539
|
-
</div>
|
|
5540
|
-
</div>
|
|
5541
|
-
</div>
|
|
5542
|
-
|
|
5543
5478
|
<div class="body">
|
|
5479
|
+
<slot name="sidebar"></slot>
|
|
5544
5480
|
<div class="calendar-area">
|
|
5545
5481
|
<div class="canvas-layer">
|
|
5546
5482
|
<canvas></canvas>
|
|
@@ -5560,6 +5496,67 @@ export class CalendarViewElement extends LitElement {
|
|
|
5560
5496
|
${this.minimapCanvas}
|
|
5561
5497
|
${this.renderEventDetail()}
|
|
5562
5498
|
${this.renderNotificationPopover()}
|
|
5499
|
+
|
|
5500
|
+
<div class="toolbar">
|
|
5501
|
+
<div class="toolbar-left">
|
|
5502
|
+
<button class="toolbar-button" title="Month" @click=${
|
|
5503
|
+
this.scrollToMonth
|
|
5504
|
+
}>
|
|
5505
|
+
Month
|
|
5506
|
+
</button>
|
|
5507
|
+
<button class="toolbar-button" title="Today" @click=${
|
|
5508
|
+
this.scrollToToday
|
|
5509
|
+
}>
|
|
5510
|
+
Today
|
|
5511
|
+
</button>
|
|
5512
|
+
<button ?disabled=${
|
|
5513
|
+
this.historyStack.length === 0 ||
|
|
5514
|
+
this.historyIndex >= this.historyStack.length - 1
|
|
5515
|
+
} class="toolbar-button" title="Back" @click=${this.goBack}>
|
|
5516
|
+
←
|
|
5517
|
+
</button>
|
|
5518
|
+
<button ?disabled=${
|
|
5519
|
+
this.historyStack.length === 0 || this.historyIndex === 0
|
|
5520
|
+
} class="toolbar-button" title="Forward" @click=${this.goForward}>
|
|
5521
|
+
→
|
|
5522
|
+
</button>
|
|
5523
|
+
<div class="toolbar-zoom">
|
|
5524
|
+
<input
|
|
5525
|
+
type="range"
|
|
5526
|
+
class="toolbar-zoom-slider"
|
|
5527
|
+
min="${MIN_DAY_HEIGHT}"
|
|
5528
|
+
max="${MAX_DAY_HEIGHT}"
|
|
5529
|
+
.value=${this.dayHeight}
|
|
5530
|
+
@input=${this.onZoomSliderChange}
|
|
5531
|
+
title="Adjust zoom level"
|
|
5532
|
+
/>
|
|
5533
|
+
</div>
|
|
5534
|
+
</div>
|
|
5535
|
+
|
|
5536
|
+
<div class="toolbar-right">
|
|
5537
|
+
<!--<select
|
|
5538
|
+
class="theme-selector"
|
|
5539
|
+
.value=${this.currentTheme}
|
|
5540
|
+
@change=${this.onThemeChange}
|
|
5541
|
+
title="Select theme"
|
|
5542
|
+
>
|
|
5543
|
+
${availableThemes.map(
|
|
5544
|
+
(theme) =>
|
|
5545
|
+
html`<option value="${theme.name}">${theme.label}</option>`,
|
|
5546
|
+
)}
|
|
5547
|
+
</select>-->
|
|
5548
|
+
<div class="toolbar-search">
|
|
5549
|
+
<span class="toolbar-search-icon">🔍</span>
|
|
5550
|
+
<input
|
|
5551
|
+
class="toolbar-search-input"
|
|
5552
|
+
type="text"
|
|
5553
|
+
placeholder="Filter events..."
|
|
5554
|
+
.value=${this.filter}
|
|
5555
|
+
@input=${this.onFilterInput}
|
|
5556
|
+
/>
|
|
5557
|
+
</div>
|
|
5558
|
+
</div>
|
|
5559
|
+
</div>
|
|
5563
5560
|
</div>
|
|
5564
5561
|
</div>
|
|
5565
5562
|
</div>
|
package/src/IndexedDBStorage.ts
CHANGED
|
@@ -390,4 +390,17 @@ export class IndexedDBStorage implements CalendarStorage {
|
|
|
390
390
|
transaction.onerror = () => reject(transaction.error);
|
|
391
391
|
});
|
|
392
392
|
}
|
|
393
|
+
|
|
394
|
+
async deleteEvent(id: string): Promise<void> {
|
|
395
|
+
const db = await this.open();
|
|
396
|
+
|
|
397
|
+
return new Promise((resolve, reject) => {
|
|
398
|
+
const transaction = db.transaction(this.storeName, "readwrite");
|
|
399
|
+
const store = transaction.objectStore(this.storeName);
|
|
400
|
+
store.delete(id);
|
|
401
|
+
|
|
402
|
+
transaction.oncomplete = () => resolve();
|
|
403
|
+
transaction.onerror = () => reject(transaction.error);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
393
406
|
}
|
|
@@ -484,6 +484,8 @@ export class InhouseBookingSource implements CalendarSource {
|
|
|
484
484
|
|
|
485
485
|
/**
|
|
486
486
|
* Delete a booking from the Inhouse Booking System.
|
|
487
|
+
* - Confirmed timetracks (id > 0): DELETE /timetracks/{id}?id=...&date=...&duration=...etc
|
|
488
|
+
* - Raw bookings (id <= 0): DELETE /timetracks/delete_bookings/{bookingId}
|
|
487
489
|
*/
|
|
488
490
|
async deleteEvent(id: string): Promise<void> {
|
|
489
491
|
if (!this.enabled) {
|
|
@@ -495,7 +497,34 @@ export class InhouseBookingSource implements CalendarSource {
|
|
|
495
497
|
throw new Error(`Invalid event ID format: ${id}`);
|
|
496
498
|
}
|
|
497
499
|
|
|
498
|
-
|
|
500
|
+
if (parsedId.id <= 0) {
|
|
501
|
+
await this.apiQueryRequest<unknown>(`/timetracks/delete_bookings/${parsedId.bookingId}`, 'DELETE', new URLSearchParams());
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const existingBookings = await this.fetchEvents();
|
|
506
|
+
const existing = existingBookings.find(e => e.id === id);
|
|
507
|
+
if (!existing) {
|
|
508
|
+
throw new Error(`Booking not found: ${id}`);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const durationMs = existing.end.getTime() - existing.start.getTime();
|
|
512
|
+
const duration = this.formatDuration(durationMs);
|
|
513
|
+
const date = existing.start.toISOString().split('T')[0];
|
|
514
|
+
const description = existing.description ?? '';
|
|
515
|
+
|
|
516
|
+
const params = new URLSearchParams({
|
|
517
|
+
id: parsedId.id.toString(),
|
|
518
|
+
unit_id: this.credentials.unitId || '',
|
|
519
|
+
employee_id: this.credentials.employeeId,
|
|
520
|
+
booking_id: parsedId.bookingId.toString(),
|
|
521
|
+
date,
|
|
522
|
+
description,
|
|
523
|
+
project_id: parsedId.projectId.toString(),
|
|
524
|
+
duration,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
await this.apiQueryRequest<unknown>(`/timetracks/${parsedId.id}`, 'DELETE', params);
|
|
499
528
|
}
|
|
500
529
|
|
|
501
530
|
/**
|
package/src/app.css
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
|
+
dialog {
|
|
2
|
+
background: var(--bg-tertiary);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.caldav-sidebar {
|
|
6
|
+
width: 250px;
|
|
7
|
+
flex-shrink: 0;
|
|
8
|
+
height: 100%;
|
|
9
|
+
border-right: 1px solid var(--grid-color, rgba(255, 255, 255, 0.1));
|
|
10
|
+
overflow: hidden;
|
|
11
|
+
transition: width 0.15s ease;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.caldav-sidebar.collapsed {
|
|
15
|
+
width: 36px;
|
|
16
|
+
}
|
|
17
|
+
|
|
1
18
|
caldav-config {
|
|
2
19
|
height: 100%;
|
|
3
|
-
box-shadow: 0 0 24px rgba(0, 0, 0, 0.4);
|
|
4
20
|
}
|
|
5
21
|
|
|
6
22
|
.toolbar-button {
|