ngx-resource-scheduler 0.1.4 → 1.0.1
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/README.md +19 -5
- package/ng-package.json +7 -0
- package/package.json +3 -14
- package/src/lib/internal/types-internal.ts +16 -0
- package/src/lib/ngx-resource-scheduler.component.html +131 -0
- package/src/lib/ngx-resource-scheduler.component.scss +443 -0
- package/src/lib/ngx-resource-scheduler.component.ts +655 -0
- package/src/lib/ngx-resource-scheduler.module.ts +10 -0
- package/src/lib/ngx-resource-scheduler.service.ts +9 -0
- package/src/lib/types.ts +103 -0
- package/src/public-api.ts +7 -0
- package/tsconfig.lib.json +15 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +15 -0
- package/fesm2022/ngx-resource-scheduler.mjs +0 -596
- package/fesm2022/ngx-resource-scheduler.mjs.map +0 -1
- package/types/ngx-resource-scheduler.d.ts +0 -244
package/README.md
CHANGED
|
@@ -36,12 +36,12 @@ npm install ngx-resource-scheduler
|
|
|
36
36
|
```ts
|
|
37
37
|
startDate = new Date();
|
|
38
38
|
|
|
39
|
-
resources = [
|
|
39
|
+
resources: SchedulerResource[] = [
|
|
40
40
|
{ id: 'r1', title: 'Room A' },
|
|
41
41
|
{ id: 'r2', title: 'Room B' },
|
|
42
42
|
];
|
|
43
43
|
|
|
44
|
-
events = [
|
|
44
|
+
events: SchedulerEvent[] = [
|
|
45
45
|
{
|
|
46
46
|
id: 'e1',
|
|
47
47
|
title: 'Meeting',
|
|
@@ -57,9 +57,9 @@ events = [
|
|
|
57
57
|
### Required
|
|
58
58
|
| Input | Type | Type |
|
|
59
59
|
| ------------- | ------------- | ------------- |
|
|
60
|
-
| startDate | Date | First visible day (recommended at 00:00) |
|
|
61
|
-
| resources | SchedulerResource[] | List of schedulable resources
|
|
62
|
-
| events | SchedulerEvent[] | Events (UTC instants recommended)
|
|
60
|
+
| `startDate` | Date | First visible day (recommended at 00:00) |
|
|
61
|
+
| `resources` | SchedulerResource[] | List of schedulable resources
|
|
62
|
+
| `events` | SchedulerEvent[] | Events (UTC instants recommended)
|
|
63
63
|
|
|
64
64
|
### Layout & Range
|
|
65
65
|
|
|
@@ -81,11 +81,25 @@ events = [
|
|
|
81
81
|
| `slotLineStyle` | `slot` | `slot`, `hour`, or `both` |
|
|
82
82
|
| `readonly` | `false` | Disable interactions |
|
|
83
83
|
| `timezone` | `local` | `local`, `UTC`, or IANA zone (e.g. `Europe/Kiev`) |
|
|
84
|
+
| `weekStartsOn` | `1` | First day of week. 0 = Sunday |
|
|
84
85
|
|
|
85
86
|
> **Important**
|
|
86
87
|
>
|
|
87
88
|
> Events should be provided as UTC instants. The scheduler converts them for display using timezone.
|
|
88
89
|
|
|
90
|
+
## i18n
|
|
91
|
+
|
|
92
|
+
| Input | Default | Description |
|
|
93
|
+
| ------------- | ------------- | ------------- |
|
|
94
|
+
| `showDaysResourcesLabel` | `true` | If the number of days/resources should be shown |
|
|
95
|
+
| `todayLabel` | `Today` | Your translation for "Today" |
|
|
96
|
+
| `daysLabel` | `days` | Your translation for "days" |
|
|
97
|
+
| `resourcesLabel` | `resources` | Your translation for "resources" |
|
|
98
|
+
| `prevLabel` | `<` | Your translation for "<" |
|
|
99
|
+
| `nextLabel` | `>` | Your translation for ">" |
|
|
100
|
+
| `locale` | `null` | Locale to be used in the dates header |
|
|
101
|
+
|
|
102
|
+
|
|
89
103
|
## 🎯 Outputs
|
|
90
104
|
|
|
91
105
|
| Output | Payload | Description |
|
package/ng-package.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ngx-resource-scheduler",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Angular scheduler with date columns and nested resource columns or inverted",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -22,16 +22,5 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"tslib": "^2.3.0"
|
|
24
24
|
},
|
|
25
|
-
"sideEffects": false
|
|
26
|
-
|
|
27
|
-
"typings": "types/ngx-resource-scheduler.d.ts",
|
|
28
|
-
"exports": {
|
|
29
|
-
"./package.json": {
|
|
30
|
-
"default": "./package.json"
|
|
31
|
-
},
|
|
32
|
-
".": {
|
|
33
|
-
"types": "./types/ngx-resource-scheduler.d.ts",
|
|
34
|
-
"default": "./fesm2022/ngx-resource-scheduler.mjs"
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
25
|
+
"sideEffects": false
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SchedulerEvent } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* INTERNAL type
|
|
5
|
+
* Represents a unique scheduler grid cell.
|
|
6
|
+
* Not exported in public-api.ts.
|
|
7
|
+
*/
|
|
8
|
+
export interface CellKey {
|
|
9
|
+
day: Date;
|
|
10
|
+
resourceId: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type PositionedEvent = SchedulerEvent & {
|
|
14
|
+
_col: number;
|
|
15
|
+
_cols: number; // total columns in its overlap group
|
|
16
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
<div class="ngx-scheduler">
|
|
2
|
+
|
|
3
|
+
<!-- Toolbar -->
|
|
4
|
+
@if (showToolbar) {
|
|
5
|
+
<div class="ngx-toolbar">
|
|
6
|
+
<div class="ngx-toolbar-left">
|
|
7
|
+
<button type="button" class="ngx-btn" (click)="navigatePrev()">{{ prevLabel }}</button>
|
|
8
|
+
<button type="button" class="ngx-btn ngx-btn--ghost" (click)="navigateToday()">{{ todayLabel }}</button>
|
|
9
|
+
<button type="button" class="ngx-btn" (click)="navigateNext()">{{ nextLabel }}</button>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="ngx-toolbar-title">
|
|
12
|
+
{{ rangeTitle }}
|
|
13
|
+
</div>
|
|
14
|
+
<div class="ngx-toolbar-right">
|
|
15
|
+
@if (showDaysResourcesLabel) {
|
|
16
|
+
<span class="ngx-toolbar-meta">
|
|
17
|
+
{{ primaryAxis === 'days' ? (visibleDays.length + ' ' + daysLabel) : (resources.length + ' ' + resourcesLabel ) }}
|
|
18
|
+
</span>
|
|
19
|
+
}
|
|
20
|
+
@if (!showDaysResourcesLabel) {
|
|
21
|
+
<span class="ngx-toolbar-meta"> </span>
|
|
22
|
+
}
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
<!-- Header row -->
|
|
28
|
+
<div class="ngx-header">
|
|
29
|
+
<div class="ngx-time-gutter"></div>
|
|
30
|
+
|
|
31
|
+
<div class="ngx-primary-headers" [style.gridTemplateColumns]="'repeat(' + primaryColumns.length + ', minmax(0, 1fr))'">
|
|
32
|
+
@for (p of primaryColumns; track trackPrimary($index, p)) {
|
|
33
|
+
<div class="ngx-primary-header">
|
|
34
|
+
<div class="ngx-primary-title-row">
|
|
35
|
+
<div class="ngx-primary-title">{{ p.title }}</div>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="ngx-secondary-header-row" [style.gridTemplateColumns]="'repeat(' + secondaryColumns.length + ', minmax(0, 1fr))'">
|
|
38
|
+
@for (s of secondaryColumns; track trackSecondary($index, s)) {
|
|
39
|
+
<div class="ngx-secondary-header">
|
|
40
|
+
<span class="ngx-secondary-header-title">{{ s.title }}</span>
|
|
41
|
+
</div>
|
|
42
|
+
}
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<!-- Body -->
|
|
50
|
+
<div class="ngx-body">
|
|
51
|
+
<!-- time gutter -->
|
|
52
|
+
<div class="ngx-time-gutter" [style.height.px]="timelineHeightPx">
|
|
53
|
+
@for (h of hourLabels; track h) {
|
|
54
|
+
<div
|
|
55
|
+
class="ngx-hour-label"
|
|
56
|
+
[style.top.px]="hourTopPx(h)">
|
|
57
|
+
{{ formatHour(h) }}
|
|
58
|
+
</div>
|
|
59
|
+
}
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
<!-- grid -->
|
|
64
|
+
<div class="ngx-grid"
|
|
65
|
+
[style.gridTemplateColumns]="'repeat(' + primaryColumns.length + ', minmax(0, 1fr))'"
|
|
66
|
+
[style.height.px]="timelineHeightPx">
|
|
67
|
+
|
|
68
|
+
@for (p of primaryColumns; track trackPrimary($index, p)) {
|
|
69
|
+
<div class="ngx-primary-col">
|
|
70
|
+
<div class="ngx-secondary-cols"
|
|
71
|
+
[style.gridTemplateColumns]="'repeat(' + secondaryColumns.length + ', minmax(0, 1fr))'"
|
|
72
|
+
[style.height.px]="timelineHeightPx">
|
|
73
|
+
<!-- Each cell is (day, resource) regardless of axis order -->
|
|
74
|
+
@for (s of secondaryColumns; track trackSecondary($index, s)) {
|
|
75
|
+
<div class="ngx-cell"
|
|
76
|
+
#cellEl
|
|
77
|
+
[style.height.px]="timelineHeightPx"
|
|
78
|
+
(click)="cellClick(p, s, $event)"
|
|
79
|
+
[attr.data-day]="cellDayKey(p,s)"
|
|
80
|
+
[attr.data-resource]="cellResourceId(p,s)">
|
|
81
|
+
<!-- grid lines -->
|
|
82
|
+
<div class="ngx-lines" aria-hidden="true">
|
|
83
|
+
<!-- slot lines -->
|
|
84
|
+
@for (line of slotLines; track line) {
|
|
85
|
+
<div
|
|
86
|
+
class="ngx-line ngx-line--slot"
|
|
87
|
+
[style.top.px]="line.top"
|
|
88
|
+
[class.ngx-line--half]="line.isHalfHour"
|
|
89
|
+
[class.is-hidden]="slotLineStyle === 'hour'">
|
|
90
|
+
</div>
|
|
91
|
+
}
|
|
92
|
+
<!-- hour lines -->
|
|
93
|
+
@for (top of hourLineOffsetsPx; track top) {
|
|
94
|
+
<div
|
|
95
|
+
class="ngx-line ngx-line--hour"
|
|
96
|
+
[style.top.px]="top"
|
|
97
|
+
[class.is-hidden]="slotLineStyle === 'slot'">
|
|
98
|
+
</div>
|
|
99
|
+
}
|
|
100
|
+
</div>
|
|
101
|
+
<!-- events -->
|
|
102
|
+
<!-- ngClass and 2nd ngStyle are only used when user passes custom class/style -->
|
|
103
|
+
@for (e of cellEvents(p, s); track trackEvent($index, e)) {
|
|
104
|
+
<div class="ngx-event"
|
|
105
|
+
[ngStyle]="getEventLayoutStyle(e, p, s)"
|
|
106
|
+
(click)="onEventClick(e, $event); $event.stopPropagation()"
|
|
107
|
+
[ngClass]="eventClass ? eventClass(e) : null"
|
|
108
|
+
[attr.title]="eventTooltip(e)">
|
|
109
|
+
@if (eventTemplate) {
|
|
110
|
+
<ng-container
|
|
111
|
+
[ngTemplateOutlet]="eventTemplate"
|
|
112
|
+
[ngTemplateOutletContext]="eventTemplateCtx(e, p, s)">
|
|
113
|
+
</ng-container>
|
|
114
|
+
} @else {
|
|
115
|
+
<div class="ngx-event-title">{{ e.title }}</div>
|
|
116
|
+
<div class="ngx-event-time">
|
|
117
|
+
{{ toDisplayZone(e.start) | date:'HH:mm' }}–{{ toDisplayZone(e.end) | date:'HH:mm' }}
|
|
118
|
+
</div>
|
|
119
|
+
}
|
|
120
|
+
</div>
|
|
121
|
+
}
|
|
122
|
+
</div>
|
|
123
|
+
}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
</div>
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
display: block;
|
|
3
|
+
|
|
4
|
+
/* Theme tokens */
|
|
5
|
+
--ngx-bg: #ffffff;
|
|
6
|
+
--ngx-surface: #ffffff;
|
|
7
|
+
--ngx-muted: #f6f7f9;
|
|
8
|
+
--ngx-border: rgba(16, 24, 40, 0.10);
|
|
9
|
+
--ngx-border-soft: rgba(16, 24, 40, 0.06);
|
|
10
|
+
--ngx-text: #101828;
|
|
11
|
+
--ngx-text-muted: rgba(16, 24, 40, 0.60);
|
|
12
|
+
|
|
13
|
+
--ngx-radius: 6px;
|
|
14
|
+
|
|
15
|
+
--ngx-shadow: 0 10px 25px rgba(16, 24, 40, 0.06);
|
|
16
|
+
--ngx-shadow-soft: 0 8px 18px rgba(16, 24, 40, 0.05);
|
|
17
|
+
|
|
18
|
+
/* Events */
|
|
19
|
+
--ngx-event-bg: #eef4ff;
|
|
20
|
+
--ngx-event-border: rgba(53, 122, 246, 0.25);
|
|
21
|
+
--ngx-event-text: #0b1f44;
|
|
22
|
+
|
|
23
|
+
/* Sizing */
|
|
24
|
+
--ngx-time-gutter-width: 72px;
|
|
25
|
+
--ngx-header-height: 78px; // includes primary + secondary headers
|
|
26
|
+
--ngx-primary-title-height: 40px;
|
|
27
|
+
--ngx-secondary-title-height: 45px;
|
|
28
|
+
|
|
29
|
+
/* Layout */
|
|
30
|
+
background: var(--ngx-bg);
|
|
31
|
+
color: var(--ngx-text);
|
|
32
|
+
--ngx-grid-gap: 5px;
|
|
33
|
+
|
|
34
|
+
/* Better font rendering */
|
|
35
|
+
-webkit-font-smoothing: antialiased;
|
|
36
|
+
-moz-osx-font-smoothing: grayscale;
|
|
37
|
+
|
|
38
|
+
font-weight: 400;
|
|
39
|
+
font-family:
|
|
40
|
+
-apple-system,
|
|
41
|
+
BlinkMacSystemFont,
|
|
42
|
+
"Segoe UI",
|
|
43
|
+
Roboto,
|
|
44
|
+
Inter,
|
|
45
|
+
Helvetica,
|
|
46
|
+
Arial,
|
|
47
|
+
sans-serif,
|
|
48
|
+
"Apple Color Emoji",
|
|
49
|
+
"Segoe UI Emoji";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.ngx-scheduler {
|
|
53
|
+
display: grid;
|
|
54
|
+
grid-template-rows: auto 1fr;
|
|
55
|
+
gap: var(--ngx-grid-gap);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ===== Toolbar ===== */
|
|
59
|
+
.ngx-toolbar {
|
|
60
|
+
display: grid;
|
|
61
|
+
grid-template-columns: auto 1fr auto;
|
|
62
|
+
align-items: center;
|
|
63
|
+
gap: 12px;
|
|
64
|
+
|
|
65
|
+
padding: 10px 10px 2px 10px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ngx-toolbar-title {
|
|
69
|
+
text-align: center;
|
|
70
|
+
font-weight: 650;
|
|
71
|
+
font-size: 14px;
|
|
72
|
+
color: var(--ngx-text);
|
|
73
|
+
letter-spacing: 0.2px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.ngx-toolbar-meta {
|
|
77
|
+
font-size: 12px;
|
|
78
|
+
color: var(--ngx-text-muted);
|
|
79
|
+
padding-right: 6px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.ngx-toolbar-left {
|
|
83
|
+
display: flex;
|
|
84
|
+
gap: 8px;
|
|
85
|
+
align-items: center;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.ngx-btn {
|
|
89
|
+
height: 34px;
|
|
90
|
+
padding: 0 10px;
|
|
91
|
+
border-radius: 12px;
|
|
92
|
+
|
|
93
|
+
border: 1px solid var(--ngx-border);
|
|
94
|
+
background: #fff;
|
|
95
|
+
color: var(--ngx-text);
|
|
96
|
+
|
|
97
|
+
font-weight: 600;
|
|
98
|
+
font-size: 12px;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
|
|
101
|
+
box-shadow: 0 8px 14px rgba(16, 24, 40, 0.06);
|
|
102
|
+
transition: transform 120ms ease, box-shadow 120ms ease, background 120ms ease;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.ngx-btn:hover {
|
|
106
|
+
transform: translateY(-1px);
|
|
107
|
+
box-shadow: 0 12px 18px rgba(16, 24, 40, 0.10);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.ngx-btn:active {
|
|
111
|
+
transform: translateY(0);
|
|
112
|
+
box-shadow: 0 8px 14px rgba(16, 24, 40, 0.08);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.ngx-btn--ghost {
|
|
116
|
+
background: var(--ngx-muted);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* ===== Header ===== */
|
|
120
|
+
.ngx-header {
|
|
121
|
+
position: sticky;
|
|
122
|
+
top: 0;
|
|
123
|
+
z-index: 5;
|
|
124
|
+
backdrop-filter: blur(8px);
|
|
125
|
+
gap: var(--ngx-grid-gap);
|
|
126
|
+
|
|
127
|
+
display: grid;
|
|
128
|
+
grid-template-columns: var(--ngx-time-gutter-width) 1fr;
|
|
129
|
+
align-items: stretch;
|
|
130
|
+
|
|
131
|
+
background: var(--ngx-bg);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* empty corner */
|
|
135
|
+
.ngx-header .ngx-time-gutter {
|
|
136
|
+
background: transparent;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
/* Primary column headers (days or resources) */
|
|
142
|
+
.ngx-primary-headers {
|
|
143
|
+
display: grid;
|
|
144
|
+
gap: var(--ngx-grid-gap);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.ngx-primary-header {
|
|
148
|
+
border: 1px solid var(--ngx-border);
|
|
149
|
+
border-radius: var(--ngx-radius);
|
|
150
|
+
overflow: hidden;
|
|
151
|
+
background: var(--ngx-surface);
|
|
152
|
+
box-shadow: var(--ngx-shadow-soft);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* Primary title line */
|
|
156
|
+
.ngx-primary-title-row {
|
|
157
|
+
height: var(--ngx-primary-title-height);
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
padding: 0 12px;
|
|
161
|
+
|
|
162
|
+
font-weight: 650;
|
|
163
|
+
font-size: 13px;
|
|
164
|
+
letter-spacing: 0.2px;
|
|
165
|
+
|
|
166
|
+
border-bottom: 1px solid var(--ngx-border-soft);
|
|
167
|
+
background: linear-gradient(to bottom, #ffffff, #fbfbfc);
|
|
168
|
+
|
|
169
|
+
.ngx-primary-title {
|
|
170
|
+
margin: auto;
|
|
171
|
+
text-align: center;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
/* Secondary header row (resources or days) */
|
|
177
|
+
.ngx-secondary-header-row {
|
|
178
|
+
height: var(--ngx-secondary-title-height);
|
|
179
|
+
display: grid;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.ngx-secondary-header {
|
|
183
|
+
display: flex;
|
|
184
|
+
align-items: center;
|
|
185
|
+
|
|
186
|
+
padding: 0 6px;
|
|
187
|
+
|
|
188
|
+
font-size: 12px;
|
|
189
|
+
text-align: center;
|
|
190
|
+
color: var(--ngx-text-muted);
|
|
191
|
+
|
|
192
|
+
border-left: 1px solid var(--ngx-border-soft);
|
|
193
|
+
background: #ffffff;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.ngx-secondary-header-title {
|
|
197
|
+
margin: auto;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.ngx-secondary-header:first-child {
|
|
201
|
+
border-left: none;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/* ===== Body ===== */
|
|
205
|
+
.ngx-body {
|
|
206
|
+
display: grid;
|
|
207
|
+
grid-template-columns: var(--ngx-time-gutter-width) 1fr;
|
|
208
|
+
gap: 10px;
|
|
209
|
+
|
|
210
|
+
/* scrolling area */
|
|
211
|
+
min-height: 320px;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.ngx-time-gutter {
|
|
215
|
+
position: relative;
|
|
216
|
+
user-select: none;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.ngx-hour-label {
|
|
220
|
+
position: absolute;
|
|
221
|
+
left: 0;
|
|
222
|
+
right: 8px;
|
|
223
|
+
|
|
224
|
+
transform: translateY(-50%);
|
|
225
|
+
text-align: right;
|
|
226
|
+
|
|
227
|
+
font-size: 12px;
|
|
228
|
+
color: var(--ngx-text-muted);
|
|
229
|
+
pointer-events: none;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* Special cases so first and last labels are visible and not clipped */
|
|
233
|
+
.ngx-hour-label:first-child {
|
|
234
|
+
transform: translateY(0);
|
|
235
|
+
top: 0 !important;
|
|
236
|
+
}
|
|
237
|
+
.ngx-hour-label:last-child {
|
|
238
|
+
transform: translateY(-100%);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/* Grid with primary columns */
|
|
242
|
+
.ngx-grid {
|
|
243
|
+
position: relative;
|
|
244
|
+
display: grid;
|
|
245
|
+
gap: var(--ngx-grid-gap);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/* Each primary column looks like a card */
|
|
249
|
+
.ngx-primary-col {
|
|
250
|
+
border: 1px solid var(--ngx-border);
|
|
251
|
+
border-radius: var(--ngx-radius);
|
|
252
|
+
overflow: hidden;
|
|
253
|
+
background: var(--ngx-surface);
|
|
254
|
+
box-shadow: var(--ngx-shadow);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/* Secondary columns container inside each primary column */
|
|
258
|
+
.ngx-secondary-cols {
|
|
259
|
+
display: grid;
|
|
260
|
+
height: 100%;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/* Individual day/resource cell */
|
|
264
|
+
.ngx-cell {
|
|
265
|
+
position: relative;
|
|
266
|
+
border-left: 1px solid var(--ngx-border-soft);
|
|
267
|
+
background: #ffffff;
|
|
268
|
+
|
|
269
|
+
/* Click affordance */
|
|
270
|
+
cursor: pointer;
|
|
271
|
+
transition: background 120ms ease;
|
|
272
|
+
}
|
|
273
|
+
.ngx-cell .ngx-event {
|
|
274
|
+
box-sizing: border-box;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.ngx-cell:first-child {
|
|
278
|
+
border-left: none;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.ngx-cell:hover {
|
|
282
|
+
background: rgba(16, 24, 40, 0.02);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* Grid lines inside each cell */
|
|
286
|
+
.ngx-lines {
|
|
287
|
+
position: absolute;
|
|
288
|
+
inset: 0;
|
|
289
|
+
pointer-events: none;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.ngx-line {
|
|
293
|
+
position: absolute;
|
|
294
|
+
left: 0;
|
|
295
|
+
right: 0;
|
|
296
|
+
height: 0;
|
|
297
|
+
border-top: 1px solid var(--ngx-border-soft);
|
|
298
|
+
opacity: 0.75;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* Slot lines are subtler */
|
|
302
|
+
.ngx-line--slot {
|
|
303
|
+
opacity: 0.5;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* Slightly stronger at hh:30 */
|
|
307
|
+
.ngx-line--half {
|
|
308
|
+
opacity: 0.7;
|
|
309
|
+
border-top-color: rgba(16, 24, 40, 0.09);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* Hour lines are stronger */
|
|
313
|
+
.ngx-line--hour {
|
|
314
|
+
opacity: 0.9;
|
|
315
|
+
border-top-color: rgba(16, 24, 40, 0.10);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.is-hidden {
|
|
319
|
+
display: none;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/* ===== Events ===== */
|
|
323
|
+
.ngx-event {
|
|
324
|
+
display: flex;
|
|
325
|
+
flex-direction: column;
|
|
326
|
+
gap: 2px;
|
|
327
|
+
touch-action: none; /* for pointer events */
|
|
328
|
+
user-select: none;
|
|
329
|
+
|
|
330
|
+
min-width: 0; /* critical so children can shrink */
|
|
331
|
+
overflow: hidden; /* keep content inside rounded card */
|
|
332
|
+
|
|
333
|
+
position: absolute;
|
|
334
|
+
/* left/width are computed in TS for overlaps */
|
|
335
|
+
min-width: 0;
|
|
336
|
+
margin: 0;
|
|
337
|
+
|
|
338
|
+
border-radius: 4px;
|
|
339
|
+
background: var(--ngx-event-bg);
|
|
340
|
+
border: 1px solid var(--ngx-event-border);
|
|
341
|
+
color: var(--ngx-event-text);
|
|
342
|
+
|
|
343
|
+
padding: 2px 4px;
|
|
344
|
+
box-sizing: border-box;
|
|
345
|
+
|
|
346
|
+
box-shadow: 0 10px 18px rgba(16, 24, 40, 0.08);
|
|
347
|
+
cursor: pointer;
|
|
348
|
+
|
|
349
|
+
transition: transform 120ms ease, box-shadow 120ms ease, filter 120ms ease;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
.ngx-event:hover {
|
|
354
|
+
transform: translateY(-1px);
|
|
355
|
+
box-shadow: 0 14px 22px rgba(16, 24, 40, 0.12);
|
|
356
|
+
filter: saturate(1.05);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.ngx-event:hover .ngx-resize {
|
|
360
|
+
opacity: 1;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.ngx-event:active {
|
|
364
|
+
transform: translateY(0px);
|
|
365
|
+
box-shadow: 0 10px 18px rgba(16, 24, 40, 0.10);
|
|
366
|
+
cursor: grabbing;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* Text inside event */
|
|
370
|
+
.ngx-event-title {
|
|
371
|
+
font-weight: 650;
|
|
372
|
+
font-size: 11px;
|
|
373
|
+
line-height: 1.2;
|
|
374
|
+
|
|
375
|
+
white-space: nowrap;
|
|
376
|
+
overflow: hidden;
|
|
377
|
+
text-overflow: ellipsis;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.ngx-event-time {
|
|
381
|
+
min-width: 0; /* critical */
|
|
382
|
+
overflow: hidden;
|
|
383
|
+
text-overflow: ellipsis;
|
|
384
|
+
white-space: nowrap;
|
|
385
|
+
margin-top: 3px;
|
|
386
|
+
font-size: 10px;
|
|
387
|
+
color: rgba(11, 31, 68, 0.72);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* FONT SETUP */
|
|
391
|
+
.ngx-event-time,
|
|
392
|
+
.ngx-toolbar-meta,
|
|
393
|
+
.ngx-secondary-header {
|
|
394
|
+
letter-spacing: 0.01em;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.ngx-hour,
|
|
398
|
+
.ngx-event-time {
|
|
399
|
+
font-variant-numeric: tabular-nums;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/* ===== Smooth scrollbars (optional) ===== */
|
|
403
|
+
.ngx-body {
|
|
404
|
+
overflow: auto;
|
|
405
|
+
padding-bottom: 4px;
|
|
406
|
+
gap: var(--ngx-grid-gap);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* Webkit scrollbar styling (Chrome/Safari/Edge) */
|
|
410
|
+
.ngx-body::-webkit-scrollbar {
|
|
411
|
+
width: 10px;
|
|
412
|
+
height: 10px;
|
|
413
|
+
}
|
|
414
|
+
.ngx-body::-webkit-scrollbar-thumb {
|
|
415
|
+
background: rgba(16, 24, 40, 0.14);
|
|
416
|
+
border-radius: 10px;
|
|
417
|
+
border: 2px solid rgba(255, 255, 255, 0.9);
|
|
418
|
+
}
|
|
419
|
+
.ngx-body::-webkit-scrollbar-track {
|
|
420
|
+
background: rgba(16, 24, 40, 0.04);
|
|
421
|
+
border-radius: 10px;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/* ===== Responsive tweaks ===== */
|
|
425
|
+
@media (max-width: 900px) {
|
|
426
|
+
:host {
|
|
427
|
+
--ngx-time-gutter-width: 58px;
|
|
428
|
+
}
|
|
429
|
+
.ngx-primary-headers,
|
|
430
|
+
.ngx-grid {
|
|
431
|
+
display: grid;
|
|
432
|
+
gap: 10px;
|
|
433
|
+
}
|
|
434
|
+
.ngx-primary-headers {
|
|
435
|
+
padding-left: calc(var(--ngx-grid-gap) / 2);
|
|
436
|
+
padding-right: calc(var(--ngx-grid-gap) / 2);
|
|
437
|
+
}
|
|
438
|
+
.ngx-event {
|
|
439
|
+
left: 6px;
|
|
440
|
+
right: 6px;
|
|
441
|
+
border-radius: 10px;
|
|
442
|
+
}
|
|
443
|
+
}
|