resourcenest 1.2.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.
@@ -0,0 +1,304 @@
1
+ (function(m,w){typeof exports=="object"&&typeof module<"u"?w(exports):typeof define=="function"&&define.amd?define(["exports"],w):(m=typeof globalThis<"u"?globalThis:m||self,w(m.ResourceNest={}))})(this,(function(m){"use strict";function w(s){const t=s.getHours().toString().padStart(2,"0"),e=s.getMinutes().toString().padStart(2,"0");return`${t}:${e}`}function k(s,t="DD.MM.YYYY"){const e=s.getDate().toString().padStart(2,"0"),r=(s.getMonth()+1).toString().padStart(2,"0"),o=s.getFullYear();return t.replace("DD",e).replace("MM",r).replace("YYYY",o)}function C(s){return new Date(s.getFullYear(),s.getMonth(),s.getDate(),0,0,0,0)}function H(s,t){const e=new Date(s);return e.setDate(e.getDate()+t),e}function v(s,t){return s.getFullYear()===t.getFullYear()&&s.getMonth()===t.getMonth()&&s.getDate()===t.getDate()}function E(s,t=5){const e=6e4*t;return new Date(Math.round(s.getTime()/e)*e)}const M={startHour:8,endHour:22,events:[],resources:[],locale:"de-DE",dateFormat:"DD.MM.YYYY",todayLabel:"Today",loadingLabel:"Loading...",showNavigation:!0,showNowIndicator:!0,showViewSwitcher:!0,eventClickable:!0,timeClickable:!0,view:"day",viewLabels:{day:"Day","3days":"3 Days",week:"Week"},onEventClick:null,onTimeClick:null,onDateChange:null,onViewChange:null},T=`
2
+ .rn-timeline-calendar {
3
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
4
+ background: #fff;
5
+ border-radius: 8px;
6
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
7
+ padding: 1rem;
8
+ box-sizing: border-box;
9
+ }
10
+ .rn-timeline-calendar *, .rn-timeline-calendar *::before, .rn-timeline-calendar *::after {
11
+ box-sizing: border-box;
12
+ }
13
+ .rn-calendar-header {
14
+ display: flex;
15
+ justify-content: space-between;
16
+ align-items: center;
17
+ margin-bottom: 1rem;
18
+ flex-wrap: wrap;
19
+ gap: 0.5rem;
20
+ }
21
+ .rn-date-display {
22
+ font-size: 1.25rem;
23
+ font-weight: 700;
24
+ color: #111827;
25
+ }
26
+ .rn-nav-controls {
27
+ display: flex;
28
+ gap: 0.5rem;
29
+ align-items: center;
30
+ }
31
+ .rn-btn {
32
+ display: inline-flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ padding: 8px 16px;
36
+ border-radius: 6px;
37
+ font-size: 0.875rem;
38
+ font-weight: 500;
39
+ cursor: pointer;
40
+ border: 1px solid #d1d5db;
41
+ background: #fff;
42
+ color: #374151;
43
+ transition: background 0.15s, border-color 0.15s;
44
+ }
45
+ .rn-btn:hover {
46
+ background: #f9fafb;
47
+ border-color: #9ca3af;
48
+ }
49
+ .rn-btn:disabled {
50
+ opacity: 0.5;
51
+ cursor: not-allowed;
52
+ }
53
+ .rn-btn-icon {
54
+ padding: 8px;
55
+ min-width: 36px;
56
+ }
57
+ .rn-calendar-body {
58
+ display: flex;
59
+ position: relative;
60
+ border: 1px solid #e5e7eb;
61
+ border-radius: 4px;
62
+ min-height: 200px;
63
+ overflow: hidden;
64
+ }
65
+ .rn-loading-overlay {
66
+ position: absolute;
67
+ inset: 0;
68
+ background: rgba(255,255,255,0.8);
69
+ display: flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ z-index: 100;
73
+ font-weight: 600;
74
+ color: #6b7280;
75
+ }
76
+ .rn-resource-sidebar {
77
+ width: 140px;
78
+ flex-shrink: 0;
79
+ background: #f9fafb;
80
+ border-right: 1px solid #e5e7eb;
81
+ margin-top: 40px;
82
+ }
83
+ .rn-resource-row {
84
+ display: flex;
85
+ align-items: center;
86
+ height: 60px;
87
+ padding: 0 1rem;
88
+ border-bottom: 1px solid #e5e7eb;
89
+ font-size: 0.875rem;
90
+ font-weight: 500;
91
+ white-space: nowrap;
92
+ overflow: hidden;
93
+ text-overflow: ellipsis;
94
+ }
95
+ .rn-chart {
96
+ flex: 1;
97
+ overflow-x: auto;
98
+ }
99
+ .rn-hour-labels {
100
+ display: flex;
101
+ height: 40px;
102
+ background: #f3f4f6;
103
+ border-bottom: 1px solid #e5e7eb;
104
+ }
105
+ .rn-hour-cell {
106
+ flex: 1;
107
+ min-width: 80px;
108
+ display: flex;
109
+ align-items: center;
110
+ justify-content: center;
111
+ font-size: 0.75rem;
112
+ color: #4b5563;
113
+ border-right: 1px solid #e5e7eb;
114
+ }
115
+ .rn-hour-cell.is-current {
116
+ background: #dbeafe;
117
+ font-weight: 600;
118
+ color: #1d4ed8;
119
+ }
120
+ .rn-timeline {
121
+ position: relative;
122
+ }
123
+ .rn-timeline-row {
124
+ display: flex;
125
+ height: 60px;
126
+ position: relative;
127
+ }
128
+ .rn-timeline-cell {
129
+ flex: 1;
130
+ min-width: 80px;
131
+ border-right: 1px solid #e5e7eb;
132
+ border-bottom: 1px solid #e5e7eb;
133
+ }
134
+ .rn-timeline-cell.is-current {
135
+ background: #eff6ff;
136
+ }
137
+ .rn-event {
138
+ position: absolute;
139
+ top: 6px;
140
+ height: calc(100% - 12px);
141
+ background: #3b82f6;
142
+ color: #fff;
143
+ padding: 4px 8px;
144
+ border-radius: 4px;
145
+ font-size: 0.75rem;
146
+ overflow: hidden;
147
+ cursor: pointer;
148
+ z-index: 10;
149
+ box-shadow: 0 1px 2px rgb(0 0 0 / 0.1);
150
+ transition: transform 0.1s;
151
+ }
152
+ .rn-event:hover {
153
+ transform: scale(1.02);
154
+ z-index: 20;
155
+ }
156
+ .rn-event.status-cancelled {
157
+ background: #ef4444;
158
+ }
159
+ .rn-event.status-confirmed {
160
+ background: #10b981;
161
+ }
162
+ .rn-event-time {
163
+ font-weight: 700;
164
+ }
165
+ .rn-event-label {
166
+ white-space: nowrap;
167
+ overflow: hidden;
168
+ text-overflow: ellipsis;
169
+ }
170
+ .rn-event-guests {
171
+ font-size: 0.7rem;
172
+ opacity: 0.9;
173
+ }
174
+ .rn-now-indicator {
175
+ position: absolute;
176
+ top: 0;
177
+ bottom: 0;
178
+ width: 2px;
179
+ background: #ef4444;
180
+ z-index: 30;
181
+ pointer-events: none;
182
+ }
183
+ .rn-now-indicator::before {
184
+ content: '';
185
+ position: absolute;
186
+ top: 0;
187
+ left: -4px;
188
+ width: 10px;
189
+ height: 10px;
190
+ background: #ef4444;
191
+ border-radius: 50%;
192
+ }
193
+ .rn-view-switcher {
194
+ display: flex;
195
+ gap: 0;
196
+ border: 1px solid #d1d5db;
197
+ border-radius: 6px;
198
+ overflow: hidden;
199
+ }
200
+ .rn-view-btn {
201
+ padding: 6px 12px;
202
+ font-size: 0.8rem;
203
+ font-weight: 500;
204
+ cursor: pointer;
205
+ border: none;
206
+ background: #fff;
207
+ color: #374151;
208
+ transition: background 0.15s;
209
+ border-right: 1px solid #d1d5db;
210
+ }
211
+ .rn-view-btn:last-child {
212
+ border-right: none;
213
+ }
214
+ .rn-view-btn:hover {
215
+ background: #f3f4f6;
216
+ }
217
+ .rn-view-btn.is-active {
218
+ background: #3b82f6;
219
+ color: #fff;
220
+ }
221
+ .rn-day-header {
222
+ display: flex;
223
+ background: #f3f4f6;
224
+ border-bottom: 1px solid #e5e7eb;
225
+ }
226
+ .rn-day-column-header {
227
+ flex: 1;
228
+ text-align: center;
229
+ padding: 8px 4px;
230
+ font-size: 0.75rem;
231
+ font-weight: 600;
232
+ color: #374151;
233
+ border-right: 1px solid #e5e7eb;
234
+ }
235
+ .rn-day-column-header:last-child {
236
+ border-right: none;
237
+ }
238
+ .rn-day-column-header.is-today {
239
+ background: #dbeafe;
240
+ color: #1d4ed8;
241
+ }
242
+ .rn-multi-day-timeline {
243
+ display: flex;
244
+ }
245
+ .rn-day-column {
246
+ flex: 1;
247
+ border-right: 1px solid #e5e7eb;
248
+ }
249
+ .rn-day-column:last-child {
250
+ border-right: none;
251
+ }
252
+ `;function L(){if(document.getElementById("rn-timeline-calendar-styles"))return;const s=document.createElement("style");s.id="rn-timeline-calendar-styles",s.textContent=T,document.head.appendChild(s)}class _{constructor(t,e={}){if(this.root=typeof t=="string"?document.querySelector(t):t,!this.root)throw new Error(`ResourceNest: Element "${t}" not found`);this.options={...M,...e},this.currentDate=C(new Date),this.events=this._normalizeEvents(this.options.events),this.resources=this.options.resources||[],this.loading=!1,this.view=this.options.view||"day",L(),this._render()}setEvents(t){return this.events=this._normalizeEvents(t),this._renderTimeline(),this}setResources(t){return this.resources=t||[],this._render(),this}setDate(t){return this.currentDate=C(new Date(t)),this._render(),this._emitDateChange(),this}goToToday(){return this.currentDate=C(new Date),this._render(),this._emitDateChange(),this}nextDay(){const t=this._getViewDays();return this.currentDate=H(this.currentDate,t),this._render(),this._emitDateChange(),this}prevDay(){const t=this._getViewDays();return this.currentDate=H(this.currentDate,-t),this._render(),this._emitDateChange(),this}setView(t){return["day","3days","week"].includes(t)&&(this.view=t,this._render(),this._emitViewChange()),this}getView(){return this.view}setLoading(t){this.loading=t;const e=this.root.querySelector(".rn-loading-overlay");return e&&(e.style.display=t?"flex":"none"),this}refresh(){return this._render(),this}scrollToNow(){const t=this.root.querySelector(".rn-chart"),e=new Date;if(!t)return this;const r=this._getVisibleDates(),o=r.findIndex(n=>v(n,e));if(o===-1)return this;const{startHour:i,endHour:c}=this.options,l=c-i,a=l*r.length,d=e.getHours()-i,g=(o*l+d)/a;return t.scrollLeft=t.scrollWidth*Math.max(0,Math.min(1,g))-t.clientWidth/2,this}getDate(){return new Date(this.currentDate)}destroy(){this.root.innerHTML="",this.root.classList.remove("rn-timeline-calendar")}onEventClick(t){return this.options.onEventClick=t,this}onTimeClick(t){return this.options.onTimeClick=t,this}onDateChange(t){return this.options.onDateChange=t,this}onViewChange(t){return this.options.onViewChange=t,this}_getViewDays(){switch(this.view){case"3days":return 3;case"week":return 7;default:return 1}}_getVisibleDates(){const t=this._getViewDays(),e=[];for(let r=0;r<t;r++)e.push(H(this.currentDate,r));return e}_normalizeEvents(t){return(t||[]).map(e=>({id:e.id,resourceId:e.resourceId||e.serviceResourceId,from:new Date(e.from),to:new Date(e.to),label:e.label||e.title||e.code||"",guestCount:e.guestCount||e.guests||0,status:e.status||null,data:e}))}_getHours(){const{startHour:t,endHour:e}=this.options,r=[],o=new Date;for(let i=t;i<e;i++)r.push({hour:i,label:`${i.toString().padStart(2,"0")}:00`,isCurrent:v(o,this.currentDate)&&o.getHours()===i});return r}_getDayRange(){const{startHour:t,endHour:e}=this.options,r=this._getViewDays(),o=new Date(this.currentDate);o.setHours(t,0,0,0);const i=new Date(this.currentDate);return i.setDate(i.getDate()+r-1),i.setHours(e,0,0,0),{from:o,to:i,rangeMs:i-o}}_getEventsForResource(t){const{from:e,to:r}=this._getDayRange();return this.events.filter(o=>o.resourceId!==t?!1:!(o.to<=e||o.from>=r))}_render(){const{showNavigation:t,showViewSwitcher:e,todayLabel:r,loadingLabel:o,dateFormat:i,viewLabels:c}=this.options,l=this._getHours(),a=this._getVisibleDates(),d=a.length>1;let f;if(d){const n=a[0],h=a[a.length-1];f=`${k(n,i)} - ${k(h,i)}`}else f=k(this.currentDate,i);const g=a.flatMap((n,h)=>l.map(b=>({...b,dayIndex:h,date:n,isCurrent:v(n,new Date)&&new Date().getHours()===b.hour})));this.root.classList.add("rn-timeline-calendar"),this.root.innerHTML=`
253
+ <div class="rn-calendar-header">
254
+ <div class="rn-date-display">${f}</div>
255
+ <div class="rn-nav-controls">
256
+ ${e?`
257
+ <div class="rn-view-switcher">
258
+ <button class="rn-view-btn ${this.view==="day"?"is-active":""}" data-view="day">${c.day}</button>
259
+ <button class="rn-view-btn ${this.view==="3days"?"is-active":""}" data-view="3days">${c["3days"]}</button>
260
+ <button class="rn-view-btn ${this.view==="week"?"is-active":""}" data-view="week">${c.week}</button>
261
+ </div>
262
+ `:""}
263
+ ${t?`
264
+ <button class="rn-btn" data-action="today">${r}</button>
265
+ <button class="rn-btn rn-btn-icon" data-action="prev">&#8249;</button>
266
+ <button class="rn-btn rn-btn-icon" data-action="next">&#8250;</button>
267
+ `:""}
268
+ </div>
269
+ </div>
270
+ <div class="rn-calendar-body">
271
+ <div class="rn-loading-overlay" style="display:${this.loading?"flex":"none"}">${o}</div>
272
+ <div class="rn-resource-sidebar">
273
+ ${d?'<div class="rn-resource-row" style="height:40px;"></div>':""}
274
+ ${this.resources.map(n=>`
275
+ <div class="rn-resource-row" data-resource-id="${n.id||n.Id}">${n.name||n.Name||n.title||"—"}</div>
276
+ `).join("")}
277
+ </div>
278
+ <div class="rn-chart">
279
+ ${d?this._renderMultiDayHeader(a,l.length):""}
280
+ <div class="rn-hour-labels">
281
+ ${g.map(n=>`
282
+ <div class="rn-hour-cell ${n.isCurrent?"is-current":""}">${n.label}</div>
283
+ `).join("")}
284
+ </div>
285
+ <div class="rn-timeline" data-days="${a.length}" data-hours="${l.length}">
286
+ ${this.resources.map(n=>`
287
+ <div class="rn-timeline-row" data-resource-id="${n.id||n.Id}">
288
+ ${g.map(h=>`
289
+ <div class="rn-timeline-cell ${h.isCurrent?"is-current":""}" data-day="${h.dayIndex}"></div>
290
+ `).join("")}
291
+ </div>
292
+ `).join("")}
293
+ </div>
294
+ </div>
295
+ </div>
296
+ `,this._bindEvents(),this._renderTimeline(),this.scrollToNow()}_renderMultiDayHeader(t,e){const r=new Date,o=["So","Mo","Di","Mi","Do","Fr","Sa"];return`
297
+ <div class="rn-day-header">
298
+ ${t.map(i=>{const c=v(i,r),l=o[i.getDay()],a=i.getDate();return`<div class="rn-day-column-header ${c?"is-today":""}" style="flex: ${e};">${l} ${a}</div>`}).join("")}
299
+ </div>
300
+ `}_renderTimeline(){const t=this.root.querySelector(".rn-timeline");if(!t)return;t.querySelectorAll(".rn-event, .rn-now-indicator").forEach(a=>a.remove());const e=this._getVisibleDates(),{startHour:r,endHour:o}=this.options,i=o-r,c=i*e.length,l=t.scrollWidth;if(this.resources.forEach(a=>{const d=a.id||a.Id,f=t.querySelector(`.rn-timeline-row[data-resource-id="${d}"]`);if(!f)return;this._getEventsForResource(d).forEach(n=>{e.forEach((h,b)=>{const y=new Date(h);y.setHours(r,0,0,0);const u=new Date(h);if(u.setHours(o,0,0,0),n.to<=y||n.from>=u)return;const D=n.from<y?y:n.from,$=n.to>u?u:n.to,x=D.getHours()-r+D.getMinutes()/60,I=b*i+x,V=($-D)/(1e3*60*60),Y=l/c*I,N=Math.max(40,l/c*V),p=document.createElement("div");p.className="rn-event",(n.status==="Cancelled"||n.status==="cancelled")&&p.classList.add("status-cancelled"),(n.status==="Confirmed"||n.status==="confirmed")&&p.classList.add("status-confirmed"),p.style.left=`${Y}px`,p.style.width=`${N}px`,p.title=n.label,p.dataset.eventId=n.id,p.innerHTML=`
301
+ <div class="rn-event-time">${w(n.from)}</div>
302
+ <div class="rn-event-label">${n.label}</div>
303
+ ${n.guestCount?`<div class="rn-event-guests">${n.guestCount} people</div>`:""}
304
+ `,this.options.eventClickable&&p.addEventListener("click",z=>{z.stopPropagation(),typeof this.options.onEventClick=="function"&&this.options.onEventClick(n.data,n)}),f.appendChild(p)})})}),this.options.showNowIndicator){const a=new Date,d=this._getVisibleDates(),f=d.findIndex(g=>v(g,a));if(f!==-1&&a.getHours()>=this.options.startHour&&a.getHours()<this.options.endHour){const g=this.options.endHour-this.options.startHour,n=g*d.length,h=a.getHours()-this.options.startHour+a.getMinutes()/60,b=f*g+h,y=l/n*b,u=document.createElement("div");u.className="rn-now-indicator",u.style.left=`${y}px`,t.appendChild(u)}}}_bindEvents(){const t=this.root.querySelector('[data-action="today"]'),e=this.root.querySelector('[data-action="prev"]'),r=this.root.querySelector('[data-action="next"]');if(t&&t.addEventListener("click",()=>this.goToToday()),e&&e.addEventListener("click",()=>this.prevDay()),r&&r.addEventListener("click",()=>this.nextDay()),this.root.querySelectorAll("[data-view]").forEach(o=>{o.addEventListener("click",()=>{const i=o.dataset.view;this.setView(i)})}),this.options.timeClickable){const o=this.root.querySelector(".rn-timeline");o&&o.addEventListener("click",i=>this._handleTimelineClick(i))}}_handleTimelineClick(t){if(t.target.closest(".rn-event"))return;const e=t.currentTarget,r=e.getBoundingClientRect(),o=t.clientX-r.left+e.scrollLeft,i=t.clientY-r.top,c=this._getVisibleDates(),{startHour:l,endHour:a}=this.options,d=a-l,f=d*c.length,g=e.scrollWidth,n=o/g*f,h=Math.floor(n/d),b=n%d,y=c[h]||c[0];let u=new Date(y);u.setHours(l+Math.floor(b),b%1*60,0,0),u=E(u,5);const $=Math.floor(i/60),x=this.resources[$];x&&typeof this.options.onTimeClick=="function"&&this.options.onTimeClick({time:u.toISOString(),date:u,resource:x})}_emitDateChange(){if(typeof this.options.onDateChange=="function"){const t=this.currentDate.getFullYear(),e=String(this.currentDate.getMonth()+1).padStart(2,"0"),r=String(this.currentDate.getDate()).padStart(2,"0"),o=`${t}-${e}-${r}`;this.options.onDateChange(o,this.currentDate)}}_emitViewChange(){typeof this.options.onViewChange=="function"&&this.options.onViewChange(this.view)}}function S(s,t={}){return new _(s,t)}m.ResourceNest=_,m.createResourceNest=S,m.default=S,Object.defineProperties(m,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}));
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "resourcenest",
3
+ "version": "1.2.0",
4
+ "description": "ResourceNest - Professional Resource Calendar UI. Beautiful timeline interface for displaying and managing room bookings, staff schedules, equipment allocation & appointments. Vanilla JavaScript, framework-agnostic.",
5
+ "type": "module",
6
+ "private": false,
7
+ "main": "./dist/resourcenest.umd.cjs",
8
+ "module": "./dist/resourcenest.js",
9
+ "browser": "./dist/resourcenest.js",
10
+ "unpkg": "./dist/resourcenest.umd.cjs",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/resourcenest.js",
14
+ "require": "./dist/resourcenest.umd.cjs"
15
+ },
16
+ "./src": {
17
+ "import": "./src/index.js"
18
+ }
19
+ },
20
+ "files": [
21
+ "dist",
22
+ "src"
23
+ ],
24
+ "scripts": {
25
+ "dev": "vite",
26
+ "build": "vite build",
27
+ "preview": "vite preview",
28
+ "prepare": "yarn build",
29
+ "changeset": "changeset",
30
+ "changeset:version": "changeset version",
31
+ "changeset:publish": "changeset publish"
32
+ },
33
+ "keywords": [
34
+ "resourcenest",
35
+ "resource-calendar",
36
+ "resource-scheduling",
37
+ "resource-management",
38
+ "room-booking",
39
+ "appointment-calendar",
40
+ "timeline-calendar",
41
+ "scheduler",
42
+ "scheduling",
43
+ "booking",
44
+ "team-scheduling",
45
+ "staff-scheduling",
46
+ "equipment-scheduling",
47
+ "meeting-room",
48
+ "resource-allocation",
49
+ "time-slot",
50
+ "calendar",
51
+ "vanilla-js",
52
+ "javascript",
53
+ "vue",
54
+ "react",
55
+ "angular",
56
+ "framework-agnostic"
57
+ ],
58
+ "author": "Dennis Thelosen <dennisthelosen20@gmail.com>",
59
+ "license": "MIT",
60
+ "repository": {
61
+ "type": "git",
62
+ "url": "https://github.com/dennisthln/resourcenest"
63
+ },
64
+ "devDependencies": {
65
+ "@changesets/changelog-github": "0.5.2",
66
+ "@changesets/cli": "2.29.8",
67
+ "vite": "^6.0.11"
68
+ }
69
+ }