@glatam/calendar-ui 1.0.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/components/calendar-modal.d.ts +22 -0
- package/dist/components/calendar-modal.d.ts.map +1 -0
- package/dist/glatam-calendar-mini.d.ts +38 -0
- package/dist/glatam-calendar-mini.d.ts.map +1 -0
- package/dist/glatam-calendar.cjs +830 -0
- package/dist/glatam-calendar.d.ts +58 -0
- package/dist/glatam-calendar.d.ts.map +1 -0
- package/dist/glatam-calendar.js +1518 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/styles/calendar.css.d.ts +2 -0
- package/dist/styles/calendar.css.d.ts.map +1 -0
- package/dist/styles/variables.css.d.ts +2 -0
- package/dist/styles/variables.css.d.ts.map +1 -0
- package/dist/utils/timezone-utils.d.ts +16 -0
- package/dist/utils/timezone-utils.d.ts.map +1 -0
- package/dist/views/day-view.d.ts +27 -0
- package/dist/views/day-view.d.ts.map +1 -0
- package/dist/views/month-view.d.ts +23 -0
- package/dist/views/month-view.d.ts.map +1 -0
- package/dist/views/week-view.d.ts +28 -0
- package/dist/views/week-view.d.ts.map +1 -0
- package/package.json +29 -0
- package/src/components/calendar-modal.ts +297 -0
- package/src/glatam-calendar-mini.ts +147 -0
- package/src/glatam-calendar.ts +197 -0
- package/src/index.ts +9 -0
- package/src/styles/calendar.css.ts +259 -0
- package/src/styles/variables.css.ts +91 -0
- package/src/utils/timezone-utils.ts +87 -0
- package/src/views/day-view.ts +130 -0
- package/src/views/month-view.ts +116 -0
- package/src/views/week-view.ts +158 -0
- package/tsconfig.json +8 -0
- package/vite.config.ts +35 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './glatam-calendar.js';
|
|
2
|
+
export * from './glatam-calendar-mini.js';
|
|
3
|
+
export * from './views/month-view.js';
|
|
4
|
+
export * from './views/week-view.js';
|
|
5
|
+
export * from './views/day-view.js';
|
|
6
|
+
export * from './components/calendar-modal.js';
|
|
7
|
+
export * from './styles/variables.css.js';
|
|
8
|
+
export * from './styles/calendar.css.js';
|
|
9
|
+
export { formatISODate, parseISODate, isTimeBlocked } from '@glatam/calendar-core';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar.css.d.ts","sourceRoot":"","sources":["../../src/styles/calendar.css.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,yBAgQ1B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variables.css.d.ts","sourceRoot":"","sources":["../../src/styles/variables.css.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe,yBAwF3B,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TimeSlot } from '@glatam/calendar-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calculates the difference in minutes between two timezones on a specific date.
|
|
5
|
+
* 'local' represents the user's local timezone.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getTimezoneOffsetDiff(date: Date, fromTz: string, toTz: string): number;
|
|
8
|
+
/**
|
|
9
|
+
* Shifts a single TimeSlot's times by a given offset in minutes and returns the shifted slot and optional day offset.
|
|
10
|
+
*/
|
|
11
|
+
export declare function shiftSlot(slot: TimeSlot, offsetMinutes: number): {
|
|
12
|
+
start: string;
|
|
13
|
+
end: string;
|
|
14
|
+
dayShift: number;
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=timezone-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timezone-utils.d.ts","sourceRoot":"","sources":["../../src/utils/timezone-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAqCtF;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAsCjH"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { CalendarDay, TimeSlot } from '@glatam/calendar-core';
|
|
3
|
+
|
|
4
|
+
export declare class GlatamCalendarDayView extends LitElement {
|
|
5
|
+
static styles: import('lit').CSSResult;
|
|
6
|
+
day: CalendarDay | null;
|
|
7
|
+
slots: TimeSlot[];
|
|
8
|
+
locale: string;
|
|
9
|
+
selectedRange: {
|
|
10
|
+
dateString: string;
|
|
11
|
+
start: string;
|
|
12
|
+
end: string;
|
|
13
|
+
} | null;
|
|
14
|
+
role: string;
|
|
15
|
+
private isDragging;
|
|
16
|
+
private dragStartSlotIndex;
|
|
17
|
+
private dragEndSlotIndex;
|
|
18
|
+
connectedCallback(): void;
|
|
19
|
+
disconnectedCallback(): void;
|
|
20
|
+
private handleMouseDown;
|
|
21
|
+
private handleMouseEnter;
|
|
22
|
+
private handleMouseUp;
|
|
23
|
+
private isSlotSelected;
|
|
24
|
+
private isSlotDragSelecting;
|
|
25
|
+
render(): import('lit-html').TemplateResult<1>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=day-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"day-view.d.ts","sourceRoot":"","sources":["../../src/views/day-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAE9D,qBACa,qBAAsB,SAAQ,UAAU;IACnD,MAAM,CAAC,MAAM,0BA6BX;IAE0B,GAAG,EAAE,WAAW,GAAG,IAAI,CAAQ;IAChC,KAAK,EAAE,QAAQ,EAAE,CAAM;IACtB,MAAM,SAAQ;IACd,aAAa,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAQ;IAChF,IAAI,SAAc;IAErC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,gBAAgB,CAAuB;IAExD,iBAAiB;IACjB,oBAAoB;IAEpB,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,aAAa,CAenB;IAEF,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,mBAAmB;IAM3B,MAAM;CAkCP"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { CalendarDay } from '@glatam/calendar-core';
|
|
3
|
+
|
|
4
|
+
export declare class GlatamCalendarMonthView extends LitElement {
|
|
5
|
+
static styles: import('lit').CSSResult;
|
|
6
|
+
days: CalendarDay[];
|
|
7
|
+
locale: string;
|
|
8
|
+
startOfWeekDay: number;
|
|
9
|
+
selectedDates: string[];
|
|
10
|
+
role: string;
|
|
11
|
+
size: string;
|
|
12
|
+
minDate: string;
|
|
13
|
+
maxDate: string;
|
|
14
|
+
showNeighboringMonth: boolean;
|
|
15
|
+
tileClassName: ((data: {
|
|
16
|
+
date: Date;
|
|
17
|
+
dateString: string;
|
|
18
|
+
}) => string) | null;
|
|
19
|
+
private getWeekdayNames;
|
|
20
|
+
private handleDayClick;
|
|
21
|
+
render(): import('lit-html').TemplateResult<1>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=month-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"month-view.d.ts","sourceRoot":"","sources":["../../src/views/month-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,qBACa,uBAAwB,SAAQ,UAAU;IACrD,MAAM,CAAC,MAAM,0BAgCX;IAEyB,IAAI,EAAE,WAAW,EAAE,CAAM;IACxB,MAAM,SAAQ;IACd,cAAc,SAAK;IACpB,aAAa,EAAE,MAAM,EAAE,CAAM;IAC5B,IAAI,SAAc;IACH,IAAI,SAAY;IAC/B,OAAO,SAAM;IACb,OAAO,SAAM;IACZ,oBAAoB,UAAQ;IACzB,aAAa,EAAE,CAAC,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC,GAAG,IAAI,CAAQ;IAEpH,OAAO,CAAC,eAAe;IAWvB,OAAO,CAAC,cAAc;IAQtB,MAAM;CA4CP"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
import { CalendarDay, TimeSlot } from '@glatam/calendar-core';
|
|
3
|
+
|
|
4
|
+
export declare class GlatamCalendarWeekView extends LitElement {
|
|
5
|
+
static styles: import('lit').CSSResult;
|
|
6
|
+
days: CalendarDay[];
|
|
7
|
+
slots: TimeSlot[];
|
|
8
|
+
locale: string;
|
|
9
|
+
selectedRange: {
|
|
10
|
+
dateString: string;
|
|
11
|
+
start: string;
|
|
12
|
+
end: string;
|
|
13
|
+
} | null;
|
|
14
|
+
role: string;
|
|
15
|
+
private isDragging;
|
|
16
|
+
private dragDayIndex;
|
|
17
|
+
private dragStartSlotIndex;
|
|
18
|
+
private dragEndSlotIndex;
|
|
19
|
+
connectedCallback(): void;
|
|
20
|
+
disconnectedCallback(): void;
|
|
21
|
+
private handleMouseDown;
|
|
22
|
+
private handleMouseEnter;
|
|
23
|
+
private handleMouseUp;
|
|
24
|
+
private isSlotSelected;
|
|
25
|
+
private isSlotDragSelecting;
|
|
26
|
+
render(): import('lit-html').TemplateResult<1>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=week-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"week-view.d.ts","sourceRoot":"","sources":["../../src/views/week-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAG5C,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAE9D,qBACa,sBAAuB,SAAQ,UAAU;IACpD,MAAM,CAAC,MAAM,0BAoDX;IAEyB,IAAI,EAAE,WAAW,EAAE,CAAM;IACzB,KAAK,EAAE,QAAQ,EAAE,CAAM;IACtB,MAAM,SAAQ;IACd,aAAa,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAQ;IAChF,IAAI,SAAc;IAErC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,gBAAgB,CAAuB;IAExD,iBAAiB;IACjB,oBAAoB;IAEpB,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,gBAAgB;IAIxB,OAAO,CAAC,aAAa,CAenB;IAEF,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,mBAAmB;IAM3B,MAAM;CAsCP"}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@glatam/calendar-ui",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Componente web UI para el calendario @glatam/calendar",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"author": "Santiago Hernández Saldarriaga",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"main": "./dist/glatam-calendar.js",
|
|
9
|
+
"module": "./dist/glatam-calendar.js",
|
|
10
|
+
"types": "./dist/glatam-calendar.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/glatam-calendar.d.ts",
|
|
14
|
+
"import": "./dist/glatam-calendar.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "vite build",
|
|
19
|
+
"dev": "vite"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@glatam/calendar-core": "^1.0.0",
|
|
23
|
+
"lit": "^3.1.2"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"vite": "^5.1.4",
|
|
27
|
+
"vite-plugin-dts": "^3.7.3"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
3
|
+
import { classMap } from 'lit/directives/class-map.js';
|
|
4
|
+
import { BlockingRule } from '@glatam/calendar-core';
|
|
5
|
+
|
|
6
|
+
@customElement('glatam-calendar-modal')
|
|
7
|
+
export class GlatamCalendarModal extends LitElement {
|
|
8
|
+
static styles = css`
|
|
9
|
+
.modal-overlay {
|
|
10
|
+
position: fixed;
|
|
11
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
12
|
+
background: rgba(0, 0, 0, 0.35);
|
|
13
|
+
backdrop-filter: blur(10px);
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
z-index: 1000;
|
|
18
|
+
opacity: 0; pointer-events: none;
|
|
19
|
+
transition: opacity var(--glatam-transition-normal);
|
|
20
|
+
}
|
|
21
|
+
.modal-overlay.open {
|
|
22
|
+
opacity: 1; pointer-events: auto;
|
|
23
|
+
}
|
|
24
|
+
.modal-content {
|
|
25
|
+
background: var(--glatam-bg);
|
|
26
|
+
color: var(--glatam-text);
|
|
27
|
+
border-radius: 24px;
|
|
28
|
+
padding: 28px;
|
|
29
|
+
width: 90%;
|
|
30
|
+
max-width: 400px;
|
|
31
|
+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.12);
|
|
32
|
+
border: 1px solid var(--glatam-border);
|
|
33
|
+
transform: scale(0.92) translateY(10px);
|
|
34
|
+
transition: transform var(--glatam-transition-normal), background-color var(--glatam-transition-normal);
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
gap: 18px;
|
|
38
|
+
}
|
|
39
|
+
.modal-overlay.open .modal-content {
|
|
40
|
+
transform: scale(1) translateY(0);
|
|
41
|
+
}
|
|
42
|
+
h3 { margin: 0; font-size: 1.3rem; font-weight: 700; letter-spacing: -0.02em; }
|
|
43
|
+
.form-group { display: flex; flex-direction: column; gap: 6px; }
|
|
44
|
+
label { font-size: 0.8rem; color: var(--glatam-text-secondary); font-weight: 600; }
|
|
45
|
+
input[type="text"] {
|
|
46
|
+
background: var(--glatam-surface);
|
|
47
|
+
border: 1px solid var(--glatam-border);
|
|
48
|
+
border-radius: 10px;
|
|
49
|
+
padding: 10px 14px;
|
|
50
|
+
color: var(--glatam-text);
|
|
51
|
+
font-family: inherit;
|
|
52
|
+
font-size: 0.9rem;
|
|
53
|
+
outline: none;
|
|
54
|
+
transition: border-color var(--glatam-transition-fast);
|
|
55
|
+
}
|
|
56
|
+
input[type="text"]:focus {
|
|
57
|
+
border-color: var(--glatam-primary);
|
|
58
|
+
}
|
|
59
|
+
.switch-row {
|
|
60
|
+
display: flex;
|
|
61
|
+
justify-content: space-between;
|
|
62
|
+
align-items: center;
|
|
63
|
+
padding: 4px 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Apple Switch Style */
|
|
67
|
+
.switch {
|
|
68
|
+
position: relative;
|
|
69
|
+
display: inline-block;
|
|
70
|
+
width: 46px;
|
|
71
|
+
height: 26px;
|
|
72
|
+
}
|
|
73
|
+
.switch input { opacity: 0; width: 0; height: 0; }
|
|
74
|
+
.slider {
|
|
75
|
+
position: absolute;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
78
|
+
background-color: var(--glatam-border);
|
|
79
|
+
transition: .25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
80
|
+
border-radius: 26px;
|
|
81
|
+
}
|
|
82
|
+
.slider:before {
|
|
83
|
+
position: absolute;
|
|
84
|
+
content: "";
|
|
85
|
+
height: 20px;
|
|
86
|
+
width: 20px;
|
|
87
|
+
left: 3px;
|
|
88
|
+
bottom: 3px;
|
|
89
|
+
background-color: white;
|
|
90
|
+
transition: .25s cubic-bezier(0.16, 1, 0.3, 1);
|
|
91
|
+
border-radius: 50%;
|
|
92
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
|
93
|
+
}
|
|
94
|
+
input:checked + .slider {
|
|
95
|
+
background-color: var(--glatam-primary);
|
|
96
|
+
}
|
|
97
|
+
input:checked + .slider:before {
|
|
98
|
+
transform: translateX(20px);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.days-grid {
|
|
102
|
+
display: grid;
|
|
103
|
+
grid-template-columns: repeat(7, 1fr);
|
|
104
|
+
gap: 6px;
|
|
105
|
+
margin-top: 4px;
|
|
106
|
+
}
|
|
107
|
+
.day-btn {
|
|
108
|
+
width: 36px;
|
|
109
|
+
height: 36px;
|
|
110
|
+
margin: 0 auto;
|
|
111
|
+
border-radius: 50%;
|
|
112
|
+
border: 1px solid var(--glatam-border);
|
|
113
|
+
background: var(--glatam-bg);
|
|
114
|
+
color: var(--glatam-text);
|
|
115
|
+
font-size: 0.8rem;
|
|
116
|
+
font-weight: 600;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
display: flex;
|
|
119
|
+
align-items: center;
|
|
120
|
+
justify-content: center;
|
|
121
|
+
transition: background-color var(--glatam-transition-fast), color var(--glatam-transition-fast), border-color var(--glatam-transition-fast);
|
|
122
|
+
}
|
|
123
|
+
.day-btn:hover {
|
|
124
|
+
background-color: var(--glatam-surface);
|
|
125
|
+
}
|
|
126
|
+
.day-btn.selected {
|
|
127
|
+
background: var(--glatam-primary);
|
|
128
|
+
color: var(--glatam-text-light);
|
|
129
|
+
border-color: var(--glatam-primary);
|
|
130
|
+
}
|
|
131
|
+
.btn-actions { display: flex; gap: 10px; margin-top: 10px; justify-content: flex-end; }
|
|
132
|
+
.btn {
|
|
133
|
+
padding: 10px 18px;
|
|
134
|
+
border-radius: 20px;
|
|
135
|
+
font-size: 0.875rem;
|
|
136
|
+
font-weight: 600;
|
|
137
|
+
cursor: pointer;
|
|
138
|
+
border: none;
|
|
139
|
+
transition: opacity var(--glatam-transition-fast), transform var(--glatam-transition-fast);
|
|
140
|
+
display: inline-flex;
|
|
141
|
+
align-items: center;
|
|
142
|
+
justify-content: center;
|
|
143
|
+
}
|
|
144
|
+
.btn:active { transform: scale(0.97); }
|
|
145
|
+
.btn-cancel { background: transparent; color: var(--glatam-text); border: 1px solid var(--glatam-border); }
|
|
146
|
+
.btn-cancel:hover { background-color: var(--glatam-surface); }
|
|
147
|
+
.btn-save { background: var(--glatam-primary); color: var(--glatam-text-light); }
|
|
148
|
+
.btn-save:hover { opacity: 0.95; }
|
|
149
|
+
.btn-danger {
|
|
150
|
+
background: rgba(255, 69, 58, 0.12);
|
|
151
|
+
color: #ff453a;
|
|
152
|
+
margin-right: auto;
|
|
153
|
+
}
|
|
154
|
+
.btn-danger:hover {
|
|
155
|
+
background: rgba(255, 69, 58, 0.18);
|
|
156
|
+
}
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
@property({ type: Boolean }) open = false;
|
|
160
|
+
@property({ type: String }) dateString = '';
|
|
161
|
+
@property({ type: String }) startTime = '';
|
|
162
|
+
@property({ type: String }) endTime = '';
|
|
163
|
+
@property({ type: Boolean }) isRange = false;
|
|
164
|
+
@property({ type: Object }) existingRule: BlockingRule | null = null;
|
|
165
|
+
|
|
166
|
+
@state() private description = '';
|
|
167
|
+
@state() private blockAllDay = true;
|
|
168
|
+
@state() private isRecurring = false;
|
|
169
|
+
@state() private selectedDays: number[] = [];
|
|
170
|
+
|
|
171
|
+
willUpdate(changedProperties: Map<string, any>) {
|
|
172
|
+
if (changedProperties.has('open') && this.open) {
|
|
173
|
+
if (this.existingRule) {
|
|
174
|
+
this.description = 'Bloqueo';
|
|
175
|
+
this.blockAllDay = !this.existingRule.slots || this.existingRule.slots.length === 0;
|
|
176
|
+
this.isRecurring = this.existingRule.type === 'weekly';
|
|
177
|
+
this.selectedDays = this.existingRule.daysOfWeek || [];
|
|
178
|
+
} else {
|
|
179
|
+
this.description = '';
|
|
180
|
+
this.blockAllDay = !this.isRange;
|
|
181
|
+
this.isRecurring = false;
|
|
182
|
+
this.selectedDays = [new Date(this.dateString + 'T00:00:00').getDay()];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private toggleDay(dayNum: number) {
|
|
188
|
+
const idx = this.selectedDays.indexOf(dayNum);
|
|
189
|
+
if (idx > -1) {
|
|
190
|
+
this.selectedDays = this.selectedDays.filter(d => d !== dayNum);
|
|
191
|
+
} else {
|
|
192
|
+
this.selectedDays = [...this.selectedDays, dayNum];
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
private handleSave() {
|
|
197
|
+
const detail = {
|
|
198
|
+
title: this.description || 'Bloqueo',
|
|
199
|
+
blockAllDay: this.blockAllDay,
|
|
200
|
+
isRecurring: this.isRecurring,
|
|
201
|
+
selectedDays: this.selectedDays,
|
|
202
|
+
dateString: this.dateString,
|
|
203
|
+
startTime: this.startTime,
|
|
204
|
+
endTime: this.endTime
|
|
205
|
+
};
|
|
206
|
+
this.dispatchEvent(new CustomEvent('save-rule', { detail, bubbles: true, composed: true }));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private handleDelete() {
|
|
210
|
+
if (this.existingRule) {
|
|
211
|
+
this.dispatchEvent(new CustomEvent('delete-rule', {
|
|
212
|
+
detail: { id: this.existingRule.id },
|
|
213
|
+
bubbles: true,
|
|
214
|
+
composed: true
|
|
215
|
+
}));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
render() {
|
|
220
|
+
const weekLabels = ['D', 'L', 'M', 'M', 'J', 'V', 'S'];
|
|
221
|
+
const formattedSelection = this.isRange
|
|
222
|
+
? `${this.dateString} (${this.startTime} - ${this.endTime})`
|
|
223
|
+
: this.dateString;
|
|
224
|
+
|
|
225
|
+
return html`
|
|
226
|
+
<div class=${classMap({ 'modal-overlay': true, 'open': this.open })}>
|
|
227
|
+
<div class="modal-content">
|
|
228
|
+
<h3>${this.existingRule ? 'Gestionar Bloqueo' : 'Crear Bloqueo'}</h3>
|
|
229
|
+
|
|
230
|
+
<div style="font-size: 0.85rem; color: var(--glatam-text-secondary);">
|
|
231
|
+
Selección: <strong>${formattedSelection}</strong>
|
|
232
|
+
</div>
|
|
233
|
+
|
|
234
|
+
<div class="form-group">
|
|
235
|
+
<label>Descripción / Nota</label>
|
|
236
|
+
<input
|
|
237
|
+
type="text"
|
|
238
|
+
.value=${this.description}
|
|
239
|
+
@input=${(e: any) => this.description = e.target.value}
|
|
240
|
+
placeholder="Ej. Reunión de equipo, Vacaciones"
|
|
241
|
+
/>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<div class="switch-row">
|
|
245
|
+
<label>Bloquear todo el día</label>
|
|
246
|
+
<label class="switch">
|
|
247
|
+
<input
|
|
248
|
+
type="checkbox"
|
|
249
|
+
.checked=${this.blockAllDay}
|
|
250
|
+
@change=${(e: any) => this.blockAllDay = e.target.checked}
|
|
251
|
+
/>
|
|
252
|
+
<span class="slider"></span>
|
|
253
|
+
</label>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<div class="switch-row">
|
|
257
|
+
<label>Repetir semanalmente</label>
|
|
258
|
+
<label class="switch">
|
|
259
|
+
<input
|
|
260
|
+
type="checkbox"
|
|
261
|
+
.checked=${this.isRecurring}
|
|
262
|
+
@change=${(e: any) => this.isRecurring = e.target.checked}
|
|
263
|
+
/>
|
|
264
|
+
<span class="slider"></span>
|
|
265
|
+
</label>
|
|
266
|
+
</div>
|
|
267
|
+
|
|
268
|
+
${this.isRecurring
|
|
269
|
+
? html`
|
|
270
|
+
<div class="form-group">
|
|
271
|
+
<label>Repetir los días</label>
|
|
272
|
+
<div class="days-grid">
|
|
273
|
+
${[1, 2, 3, 4, 5, 6, 0].map(day => html`
|
|
274
|
+
<button
|
|
275
|
+
class="day-btn ${this.selectedDays.includes(day) ? 'selected' : ''}"
|
|
276
|
+
@click=${() => this.toggleDay(day)}
|
|
277
|
+
>
|
|
278
|
+
${weekLabels[day]}
|
|
279
|
+
</button>
|
|
280
|
+
`)}
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
`
|
|
284
|
+
: ''}
|
|
285
|
+
|
|
286
|
+
<div class="btn-actions">
|
|
287
|
+
${this.existingRule
|
|
288
|
+
? html`<button class="btn btn-danger" @click=${this.handleDelete}>Eliminar</button>`
|
|
289
|
+
: ''}
|
|
290
|
+
<button class="btn btn-cancel" @click=${() => this.dispatchEvent(new CustomEvent('close'))}>Cancelar</button>
|
|
291
|
+
<button class="btn btn-save" @click=${this.handleSave}>Guardar</button>
|
|
292
|
+
</div>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
`;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { LitElement, html, css } from 'lit';
|
|
2
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
3
|
+
import {
|
|
4
|
+
generateMonthDays,
|
|
5
|
+
isTimeBlocked,
|
|
6
|
+
BlockingRule,
|
|
7
|
+
TimeSlot
|
|
8
|
+
} from '@glatam/calendar-core';
|
|
9
|
+
import { variablesStyles } from './styles/variables.css.js';
|
|
10
|
+
import { calendarStyles } from './styles/calendar.css.js';
|
|
11
|
+
import { getTimezoneOffsetDiff, shiftSlot } from './utils/timezone-utils.js';
|
|
12
|
+
|
|
13
|
+
import './views/month-view.js';
|
|
14
|
+
|
|
15
|
+
@customElement('glatam-calendar-mini')
|
|
16
|
+
export class GlatamCalendarMini extends LitElement {
|
|
17
|
+
static styles = [
|
|
18
|
+
variablesStyles,
|
|
19
|
+
calendarStyles,
|
|
20
|
+
css`
|
|
21
|
+
:host {
|
|
22
|
+
display: inline-block;
|
|
23
|
+
background: transparent;
|
|
24
|
+
border: none;
|
|
25
|
+
padding: 0;
|
|
26
|
+
box-shadow: none;
|
|
27
|
+
width: 100%;
|
|
28
|
+
}
|
|
29
|
+
`
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
@property({ type: String }) role = 'buyer'; // Default to buyer for checkout
|
|
33
|
+
@property({ type: String }) locale = 'es';
|
|
34
|
+
@property({ type: Number }) startOfWeekDay = 0;
|
|
35
|
+
@property({ type: Array }) rules: BlockingRule[] = [];
|
|
36
|
+
@property({ type: Object }) selectedRange: { dateString: string; start: string; end: string } | null = null;
|
|
37
|
+
@property({ type: String, reflect: true }) size = 'medium';
|
|
38
|
+
@property({ type: String }) hostTimezone = 'America/Bogota'; @property({ type: String }) activeTimezone = 'local';
|
|
39
|
+
@property({ type: String }) minDate = ''; @property({ type: String }) maxDate = '';
|
|
40
|
+
@property({ type: Boolean }) showNeighboringMonth = true;
|
|
41
|
+
@property({ attribute: false }) tileClassName: ((data: { date: Date; dateString: string }) => string) | null = null;
|
|
42
|
+
@property({ type: Array }) slots: TimeSlot[] = [
|
|
43
|
+
{ start: '09:00', end: '10:00' }, { start: '10:00', end: '11:00' },
|
|
44
|
+
{ start: '11:00', end: '12:00' }, { start: '12:00', end: '13:00' },
|
|
45
|
+
{ start: '13:00', end: '14:00' }, { start: '14:00', end: '15:00' },
|
|
46
|
+
{ start: '15:00', end: '16:00' }, { start: '16:00', end: '17:00' },
|
|
47
|
+
{ start: '17:00', end: '18:00' },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
@state() private activeDate = new Date();
|
|
51
|
+
@state() private dropdownOpen = false;
|
|
52
|
+
@state() private dropdownSelectedDateString = '';
|
|
53
|
+
|
|
54
|
+
private handleDropdownDaySelect(e: CustomEvent<{ dateString: string }>) {
|
|
55
|
+
this.dropdownSelectedDateString = e.detail.dateString;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private selectDropdownSlot(slot: any) {
|
|
59
|
+
const detail = {
|
|
60
|
+
dateString: this.dropdownSelectedDateString,
|
|
61
|
+
start: slot.displayStart,
|
|
62
|
+
end: slot.displayEnd,
|
|
63
|
+
hostStart: slot.start,
|
|
64
|
+
hostEnd: slot.end
|
|
65
|
+
};
|
|
66
|
+
this.selectedRange = { dateString: this.dropdownSelectedDateString, start: slot.displayStart, end: slot.displayEnd };
|
|
67
|
+
this.dispatchEvent(new CustomEvent('booking-selected', { detail, bubbles: true, composed: true }));
|
|
68
|
+
this.dropdownOpen = false;
|
|
69
|
+
this.dropdownSelectedDateString = '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
render() {
|
|
73
|
+
const label = this.selectedRange
|
|
74
|
+
? `Reserva: ${this.selectedRange.dateString} (${this.selectedRange.start} - ${this.selectedRange.end})`
|
|
75
|
+
: 'Seleccionar Fecha y Hora';
|
|
76
|
+
|
|
77
|
+
const y = this.activeDate.getFullYear(), m = this.activeDate.getMonth();
|
|
78
|
+
const dropdownDate = this.dropdownSelectedDateString ? new Date(this.dropdownSelectedDateString + 'T00:00:00') : null;
|
|
79
|
+
const offset = dropdownDate && this.activeTimezone === 'local' ? getTimezoneOffsetDiff(dropdownDate, this.hostTimezone, 'local') : 0;
|
|
80
|
+
|
|
81
|
+
const dropdownSlots = dropdownDate
|
|
82
|
+
? this.slots.map(slot => {
|
|
83
|
+
const isBlocked = isTimeBlocked(dropdownDate, slot, this.rules);
|
|
84
|
+
const shifted = shiftSlot(slot, offset);
|
|
85
|
+
const suffix = shifted.dayShift > 0 ? ' (+1d)' : shifted.dayShift < 0 ? ' (-1d)' : '';
|
|
86
|
+
return {
|
|
87
|
+
...slot,
|
|
88
|
+
displayStart: shifted.start + suffix,
|
|
89
|
+
displayEnd: shifted.end + suffix,
|
|
90
|
+
isBlocked
|
|
91
|
+
};
|
|
92
|
+
})
|
|
93
|
+
: [];
|
|
94
|
+
|
|
95
|
+
return html`
|
|
96
|
+
<div class="dropdown-container">
|
|
97
|
+
<button class="btn btn-primary dropdown-toggle" @click=${() => this.dropdownOpen = !this.dropdownOpen}>
|
|
98
|
+
<span>${label}</span> <span>${this.dropdownOpen ? '▲' : '▼'}</span>
|
|
99
|
+
</button>
|
|
100
|
+
|
|
101
|
+
${this.dropdownOpen
|
|
102
|
+
? html`
|
|
103
|
+
<div class="dropdown-card" style="--glatam-day-min-height: 38px;">
|
|
104
|
+
${!this.dropdownSelectedDateString
|
|
105
|
+
? html`
|
|
106
|
+
<div style="font-weight:600; font-size:0.9rem; text-align:center; color: var(--glatam-text);">Selecciona un Día</div>
|
|
107
|
+
<glatam-calendar-month-view
|
|
108
|
+
.days=${generateMonthDays(y, m, this.rules, this.slots, this.startOfWeekDay)}
|
|
109
|
+
.locale=${this.locale}
|
|
110
|
+
.startOfWeekDay=${this.startOfWeekDay}
|
|
111
|
+
.role=${this.role}
|
|
112
|
+
size="small"
|
|
113
|
+
.minDate=${this.minDate}
|
|
114
|
+
.maxDate=${this.maxDate}
|
|
115
|
+
.showNeighboringMonth=${this.showNeighboringMonth}
|
|
116
|
+
.tileClassName=${this.tileClassName}
|
|
117
|
+
@day-select=${this.handleDropdownDaySelect}
|
|
118
|
+
></glatam-calendar-month-view>
|
|
119
|
+
`
|
|
120
|
+
: html`
|
|
121
|
+
<div style="display:flex; justify-content:space-between; align-items:center; border-bottom: 1px solid var(--glatam-border); padding-bottom:8px;">
|
|
122
|
+
<button class="btn" style="height:28px; padding:0 8px; font-size:0.75rem;" @click=${() => this.dropdownSelectedDateString = ''}>< Volver</button>
|
|
123
|
+
<span style="font-size:0.8rem; font-weight:600; color: var(--glatam-text);">${this.dropdownSelectedDateString}</span>
|
|
124
|
+
</div>
|
|
125
|
+
<div class="slot-list">
|
|
126
|
+
${dropdownSlots.map(s => html`
|
|
127
|
+
<button
|
|
128
|
+
class="slot-btn ${s.isBlocked ? 'blocked' : ''}"
|
|
129
|
+
?disabled=${s.isBlocked}
|
|
130
|
+
@click=${() => this.selectDropdownSlot(s)}
|
|
131
|
+
>
|
|
132
|
+
${s.displayStart} - ${s.displayEnd} ${s.isBlocked ? '(Ocupado)' : ''}
|
|
133
|
+
</button>
|
|
134
|
+
`)}
|
|
135
|
+
</div>
|
|
136
|
+
`}
|
|
137
|
+
</div>
|
|
138
|
+
`
|
|
139
|
+
: ''}
|
|
140
|
+
</div>
|
|
141
|
+
`;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
declare global {
|
|
146
|
+
interface HTMLElementTagNameMap { 'glatam-calendar-mini': GlatamCalendarMini; }
|
|
147
|
+
}
|