@fayz-ai/plugin-agenda 0.8.0 → 0.8.3
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/{chunk-LFUELUIT.cjs → chunk-3KWNHV5A.cjs} +60 -2
- package/dist/chunk-3KWNHV5A.cjs.map +1 -0
- package/dist/{chunk-DRZF6CV2.js → chunk-YQO6B4G3.js} +60 -3
- package/dist/chunk-YQO6B4G3.js.map +1 -0
- package/dist/components/AgendaGeneralSettings.d.ts.map +1 -1
- package/dist/components/AppointmentModal.d.ts.map +1 -1
- package/dist/components/CalendarToolbar.d.ts.map +1 -1
- package/dist/data/supabase.d.ts +5 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/index.cjs +1071 -766
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +914 -609
- package/dist/index.js.map +1 -1
- package/dist/integrations/google-calendar/components/CalendarColorDot.d.ts +6 -0
- package/dist/integrations/google-calendar/components/CalendarColorDot.d.ts.map +1 -0
- package/dist/integrations/google-calendar/components/ChannelMappingList.d.ts +9 -0
- package/dist/integrations/google-calendar/components/ChannelMappingList.d.ts.map +1 -0
- package/dist/integrations/google-calendar/components/ImportEventsWizard.d.ts +12 -0
- package/dist/integrations/google-calendar/components/ImportEventsWizard.d.ts.map +1 -0
- package/dist/integrations/google-calendar/components/useAgendaLookups.d.ts +26 -0
- package/dist/integrations/google-calendar/components/useAgendaLookups.d.ts.map +1 -0
- package/dist/integrations/google-calendar/connectorDef.d.ts.map +1 -1
- package/dist/integrations/google-calendar/data/supabase.d.ts +33 -1
- package/dist/integrations/google-calendar/data/supabase.d.ts.map +1 -1
- package/dist/integrations/google-calendar/data/tables.d.ts +6 -0
- package/dist/integrations/google-calendar/data/tables.d.ts.map +1 -0
- package/dist/integrations/google-calendar/index.d.ts +1 -1
- package/dist/integrations/google-calendar/index.d.ts.map +1 -1
- package/dist/integrations/google-calendar/mapping.d.ts +56 -4
- package/dist/integrations/google-calendar/mapping.d.ts.map +1 -1
- package/dist/integrations/google-calendar/types.d.ts +101 -0
- package/dist/integrations/google-calendar/types.d.ts.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +11 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/public/BookingWidget.d.ts.map +1 -1
- package/dist/public/context.d.ts +3 -1
- package/dist/public/context.d.ts.map +1 -1
- package/dist/public/createPublicBookingPlugin.d.ts +7 -1
- package/dist/public/createPublicBookingPlugin.d.ts.map +1 -1
- package/dist/public/data.d.ts +6 -0
- package/dist/public/data.d.ts.map +1 -1
- package/dist/public/data.supabase.d.ts.map +1 -1
- package/dist/public/format.d.ts +22 -1
- package/dist/public/format.d.ts.map +1 -1
- package/dist/public/hooks.d.ts.map +1 -1
- package/dist/public/index.cjs +31 -63
- package/dist/public/index.cjs.map +1 -1
- package/dist/public/index.d.ts +1 -1
- package/dist/public/index.d.ts.map +1 -1
- package/dist/public/index.js +27 -61
- package/dist/public/index.js.map +1 -1
- package/dist/public/steps/ContactStep.d.ts +4 -2
- package/dist/public/steps/ContactStep.d.ts.map +1 -1
- package/dist/public/types.d.ts +16 -0
- package/dist/public/types.d.ts.map +1 -1
- package/dist/views/CalendarView.d.ts.map +1 -1
- package/package.json +6 -5
- package/src/components/AgendaGeneralSettings.tsx +38 -16
- package/src/components/AppointmentModal.tsx +50 -108
- package/src/components/CalendarToolbar.tsx +10 -7
- package/src/data/supabase.ts +67 -13
- package/src/index.ts +56 -8
- package/src/integrations/google-calendar/components/CalendarColorDot.tsx +23 -0
- package/src/integrations/google-calendar/components/ChannelMappingList.tsx +329 -0
- package/src/integrations/google-calendar/components/ImportEventsWizard.tsx +215 -0
- package/src/integrations/google-calendar/components/useAgendaLookups.ts +100 -0
- package/src/integrations/google-calendar/connectorDef.tsx +123 -40
- package/src/integrations/google-calendar/data/supabase.ts +109 -13
- package/src/integrations/google-calendar/data/tables.ts +12 -0
- package/src/integrations/google-calendar/index.ts +10 -1
- package/src/integrations/google-calendar/integration.test.ts +126 -1
- package/src/integrations/google-calendar/mapping.ts +149 -9
- package/src/integrations/google-calendar/migrations/001_google_calendar.sql +9 -6
- package/src/integrations/google-calendar/migrations/002_smart_sync.sql +517 -0
- package/src/integrations/google-calendar/migrations/003_plg_rename.sql +273 -0
- package/src/integrations/google-calendar/types.ts +111 -0
- package/src/locales/en.ts +1 -0
- package/src/locales/pt-BR.ts +1 -0
- package/src/migrations/003_public_client_kind.sql +175 -0
- package/src/migrations/004_public_booking_assignee.sql +176 -0
- package/src/migrations/005_agent_rpcs.sql +168 -0
- package/src/migrations/006_agent_rpc_localtime.sql +181 -0
- package/src/migrations/007_schedule_exception_precedence.sql +290 -0
- package/src/migrations/008_agent_rpc_exception_precedence.sql +172 -0
- package/src/migrations/009_public_booking_location.sql +162 -0
- package/src/migrations/010_public_service_curation.sql +31 -0
- package/src/migrations/011_default_assignee.sql +167 -0
- package/src/migrations/012_drop_legacy_booking_overload.sql +29 -0
- package/src/migrations/013_normalize_v_appointments.sql +161 -0
- package/src/migrations/index.ts +1746 -1
- package/src/public/BookingWidget.tsx +10 -1
- package/src/public/context.tsx +3 -0
- package/src/public/createPublicBookingPlugin.ts +14 -1
- package/src/public/data.supabase.ts +3 -0
- package/src/public/data.ts +6 -0
- package/src/public/format.ts +36 -1
- package/src/public/hooks.ts +7 -2
- package/src/public/index.ts +1 -0
- package/src/public/steps/ContactStep.tsx +10 -5
- package/src/public/types.ts +21 -0
- package/src/views/CalendarView.tsx +15 -1
- package/dist/chunk-3LJKZQK4.js +0 -12
- package/dist/chunk-3LJKZQK4.js.map +0 -1
- package/dist/chunk-DRZF6CV2.js.map +0 -1
- package/dist/chunk-LFUELUIT.cjs.map +0 -1
- package/dist/chunk-SYDBDI3Q.cjs +0 -15
- package/dist/chunk-SYDBDI3Q.cjs.map +0 -1
- package/dist/components/SearchCombobox.d.ts +0 -38
- package/dist/components/SearchCombobox.d.ts.map +0 -1
- package/dist/tenant-222EHYUC.cjs +0 -16
- package/dist/tenant-222EHYUC.cjs.map +0 -1
- package/dist/tenant-LAUWVUYB.js +0 -3
- package/dist/tenant-LAUWVUYB.js.map +0 -1
- package/src/components/SearchCombobox.tsx +0 -216
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useRef, useCallback } from 'react'
|
|
2
|
-
import { createPortal } from 'react-dom'
|
|
3
|
-
import { Search, Plus } from 'lucide-react'
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// Reusable search combobox with keyboard navigation + optional create
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
export interface ComboboxOption {
|
|
10
|
-
id: string
|
|
11
|
-
label: string
|
|
12
|
-
subtitle?: string
|
|
13
|
-
price?: number
|
|
14
|
-
data?: Record<string, unknown>
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface SearchComboboxProps {
|
|
18
|
-
value: string
|
|
19
|
-
onChange: (value: string) => void
|
|
20
|
-
onSelect: (option: ComboboxOption) => void
|
|
21
|
-
options: ComboboxOption[]
|
|
22
|
-
placeholder?: string
|
|
23
|
-
autoFocus?: boolean
|
|
24
|
-
onBlurEmpty?: () => void
|
|
25
|
-
renderRight?: (option: ComboboxOption) => React.ReactNode
|
|
26
|
-
/** Show a "+ Create ..." option when search has no exact match */
|
|
27
|
-
allowCreate?: boolean
|
|
28
|
-
/** Called when user clicks the create option. Receives the current search text. */
|
|
29
|
-
onCreateNew?: (searchText: string) => void
|
|
30
|
-
/** Label for the create option (default: 'Create') */
|
|
31
|
-
createLabel?: string
|
|
32
|
-
className?: string
|
|
33
|
-
/** Minimal style — no border/bg, just underline on focus. For inline use. */
|
|
34
|
-
minimal?: boolean
|
|
35
|
-
/** Inline label shown when collapsed. Click reveals the search input. */
|
|
36
|
-
inlineLabel?: string
|
|
37
|
-
/** Icon for the inline label: 'search' (default) or 'plus' (for "add" actions) */
|
|
38
|
-
inlineIcon?: 'search' | 'plus'
|
|
39
|
-
/** Show a loading skeleton in the dropdown while results are being fetched */
|
|
40
|
-
loading?: boolean
|
|
41
|
-
/** Called when the input gains focus */
|
|
42
|
-
onFocus?: () => void
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function SearchCombobox({
|
|
46
|
-
value, onChange, onSelect, options, placeholder, autoFocus, onBlurEmpty,
|
|
47
|
-
renderRight, allowCreate, onCreateNew, createLabel = 'Create', className, minimal, inlineLabel, inlineIcon = 'search',
|
|
48
|
-
loading, onFocus: onFocusProp,
|
|
49
|
-
}: SearchComboboxProps) {
|
|
50
|
-
const [open, setOpen] = useState(false)
|
|
51
|
-
const [activeIndex, setActiveIndex] = useState(-1)
|
|
52
|
-
const [expanded, setExpanded] = useState(!inlineLabel)
|
|
53
|
-
const [dropRect, setDropRect] = useState<DOMRect | null>(null)
|
|
54
|
-
const inputRef = useRef<HTMLInputElement>(null)
|
|
55
|
-
const wrapperRef = useRef<HTMLDivElement>(null)
|
|
56
|
-
const listRef = useRef<HTMLDivElement>(null)
|
|
57
|
-
|
|
58
|
-
// When value is set externally (e.g. edit mode), auto-expand; collapse when cleared
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
if (value && inlineLabel) setExpanded(true)
|
|
61
|
-
if (!value && inlineLabel && !open) setExpanded(false)
|
|
62
|
-
}, [value, open])
|
|
63
|
-
|
|
64
|
-
function updateRect() {
|
|
65
|
-
if (wrapperRef.current) setDropRect(wrapperRef.current.getBoundingClientRect())
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Show create option only when dropdown is open, has text, and no exact match
|
|
69
|
-
const showCreate = open && allowCreate && onCreateNew && value.trim().length > 0 &&
|
|
70
|
-
!options.some((o) => o.label.toLowerCase() === value.trim().toLowerCase())
|
|
71
|
-
|
|
72
|
-
const totalItems = options.length + (showCreate ? 1 : 0)
|
|
73
|
-
|
|
74
|
-
useEffect(() => { setActiveIndex(-1) }, [options])
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
if ((options.length > 0 || showCreate || loading) && document.activeElement === inputRef.current) setOpen(true)
|
|
77
|
-
}, [options, showCreate, loading])
|
|
78
|
-
|
|
79
|
-
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
80
|
-
if (!open || totalItems === 0) {
|
|
81
|
-
if (e.key === 'ArrowDown' && totalItems > 0) {
|
|
82
|
-
e.preventDefault()
|
|
83
|
-
setOpen(true)
|
|
84
|
-
setActiveIndex(0)
|
|
85
|
-
}
|
|
86
|
-
return
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
switch (e.key) {
|
|
90
|
-
case 'ArrowDown':
|
|
91
|
-
e.preventDefault()
|
|
92
|
-
setActiveIndex((prev) => (prev + 1) % totalItems)
|
|
93
|
-
break
|
|
94
|
-
case 'ArrowUp':
|
|
95
|
-
e.preventDefault()
|
|
96
|
-
setActiveIndex((prev) => (prev <= 0 ? totalItems - 1 : prev - 1))
|
|
97
|
-
break
|
|
98
|
-
case 'Enter':
|
|
99
|
-
e.preventDefault()
|
|
100
|
-
if (activeIndex >= 0 && activeIndex < options.length) {
|
|
101
|
-
onSelect(options[activeIndex])
|
|
102
|
-
setOpen(false)
|
|
103
|
-
setActiveIndex(-1)
|
|
104
|
-
} else if (showCreate && activeIndex === options.length) {
|
|
105
|
-
onCreateNew?.(value.trim())
|
|
106
|
-
setOpen(false)
|
|
107
|
-
setActiveIndex(-1)
|
|
108
|
-
}
|
|
109
|
-
break
|
|
110
|
-
case 'Escape':
|
|
111
|
-
e.preventDefault()
|
|
112
|
-
setOpen(false)
|
|
113
|
-
setActiveIndex(-1)
|
|
114
|
-
break
|
|
115
|
-
}
|
|
116
|
-
}, [open, options, totalItems, activeIndex, onSelect, showCreate, onCreateNew, value])
|
|
117
|
-
|
|
118
|
-
useEffect(() => {
|
|
119
|
-
if (activeIndex < 0 || !listRef.current) return
|
|
120
|
-
const items = listRef.current.children
|
|
121
|
-
if (items[activeIndex]) (items[activeIndex] as HTMLElement).scrollIntoView({ block: 'nearest' })
|
|
122
|
-
}, [activeIndex])
|
|
123
|
-
|
|
124
|
-
function handleBlur() {
|
|
125
|
-
setTimeout(() => {
|
|
126
|
-
setOpen(false)
|
|
127
|
-
setActiveIndex(-1)
|
|
128
|
-
if (!value && onBlurEmpty) onBlurEmpty()
|
|
129
|
-
if (!value && inlineLabel) setExpanded(false)
|
|
130
|
-
}, 200)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const hasDropdown = open && (options.length > 0 || showCreate || loading)
|
|
134
|
-
|
|
135
|
-
// Collapsed inline label — click to expand
|
|
136
|
-
if (!expanded && inlineLabel) {
|
|
137
|
-
const isPlus = inlineIcon === 'plus'
|
|
138
|
-
return (
|
|
139
|
-
<button type="button" onClick={() => { setExpanded(true); setTimeout(() => inputRef.current?.focus(), 50) }}
|
|
140
|
-
className={`flex items-center gap-1.5 w-full text-left text-sm py-1.5 pl-2 transition-colors ${
|
|
141
|
-
isPlus ? 'text-primary hover:text-primary/80' : 'text-muted-foreground hover:text-foreground'
|
|
142
|
-
} ${className ?? ''}`}>
|
|
143
|
-
{isPlus && <Plus className="h-3.5 w-3.5 shrink-0" />}
|
|
144
|
-
{inlineLabel}
|
|
145
|
-
</button>
|
|
146
|
-
)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<div ref={wrapperRef} className={`relative ${className ?? ''}`}>
|
|
151
|
-
{!minimal && (
|
|
152
|
-
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground pointer-events-none" />
|
|
153
|
-
)}
|
|
154
|
-
<input
|
|
155
|
-
ref={inputRef}
|
|
156
|
-
type="text"
|
|
157
|
-
value={value}
|
|
158
|
-
onChange={(e) => { onChange(e.target.value); updateRect(); setOpen(true) }}
|
|
159
|
-
onFocus={() => { updateRect(); onFocusProp?.(); if (options.length > 0 || showCreate || loading) setOpen(true) }}
|
|
160
|
-
onBlur={handleBlur}
|
|
161
|
-
onKeyDown={handleKeyDown}
|
|
162
|
-
placeholder={placeholder}
|
|
163
|
-
autoFocus={autoFocus}
|
|
164
|
-
className={minimal
|
|
165
|
-
? 'w-full bg-transparent pl-2 pr-3 py-1.5 text-sm placeholder:text-muted-foreground focus:outline-none border-b border-transparent hover:border-border focus:border-foreground transition-colors'
|
|
166
|
-
: 'w-full rounded-lg border bg-background pl-8 pr-3 py-2 text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring'
|
|
167
|
-
}
|
|
168
|
-
role="combobox"
|
|
169
|
-
aria-expanded={hasDropdown}
|
|
170
|
-
aria-autocomplete="list"
|
|
171
|
-
aria-activedescendant={activeIndex >= 0 ? `combobox-opt-${activeIndex}` : undefined}
|
|
172
|
-
/>
|
|
173
|
-
{hasDropdown && dropRect && createPortal(
|
|
174
|
-
<div ref={listRef} role="listbox" data-modal-passthrough
|
|
175
|
-
style={{ position: 'fixed', left: dropRect.left, top: dropRect.bottom + 4, width: dropRect.width, zIndex: 9999, pointerEvents: 'auto' }}
|
|
176
|
-
className="fayz-glass-surface max-h-52 overflow-y-auto rounded-lg border bg-popover shadow-lg py-0.5 animate-in fade-in zoom-in-95 duration-100">
|
|
177
|
-
{loading && options.length === 0 && (
|
|
178
|
-
<div className="flex items-center gap-2 px-3 py-2 text-sm text-muted-foreground">
|
|
179
|
-
<svg className="h-3.5 w-3.5 animate-spin shrink-0" viewBox="0 0 16 16" fill="none">
|
|
180
|
-
<circle cx="8" cy="8" r="6.5" stroke="currentColor" strokeWidth="1.5" opacity="0.25" />
|
|
181
|
-
<path d="M14.5 8a6.5 6.5 0 00-6.5-6.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
182
|
-
</svg>
|
|
183
|
-
<span>Buscando…</span>
|
|
184
|
-
</div>
|
|
185
|
-
)}
|
|
186
|
-
{options.map((opt, i) => (
|
|
187
|
-
<div key={opt.id} id={`combobox-opt-${i}`} role="option" aria-selected={i === activeIndex}
|
|
188
|
-
onMouseDown={(e) => { e.preventDefault(); onSelect(opt); setOpen(false) }}
|
|
189
|
-
onMouseEnter={() => setActiveIndex(i)}
|
|
190
|
-
className={`flex w-full items-center gap-2 px-3 py-2 text-sm cursor-pointer transition-colors ${
|
|
191
|
-
i === activeIndex ? 'bg-primary/10 text-primary' : 'hover:bg-muted/50'
|
|
192
|
-
}`}>
|
|
193
|
-
<div className="flex-1 min-w-0">
|
|
194
|
-
<p className="truncate font-medium">{opt.label}</p>
|
|
195
|
-
{opt.subtitle && <p className="truncate text-[11px] text-muted-foreground">{opt.subtitle}</p>}
|
|
196
|
-
</div>
|
|
197
|
-
{renderRight && <div className="shrink-0">{renderRight(opt)}</div>}
|
|
198
|
-
</div>
|
|
199
|
-
))}
|
|
200
|
-
{showCreate && (
|
|
201
|
-
<div id={`combobox-opt-${options.length}`} role="option" aria-selected={activeIndex === options.length}
|
|
202
|
-
onMouseDown={(e) => { e.preventDefault(); onCreateNew?.(value.trim()); setOpen(false) }}
|
|
203
|
-
onMouseEnter={() => setActiveIndex(options.length)}
|
|
204
|
-
className={`flex w-full items-center gap-2 px-3 py-2 text-sm cursor-pointer transition-colors border-t ${
|
|
205
|
-
activeIndex === options.length ? 'bg-primary/10 text-primary' : 'hover:bg-muted/50'
|
|
206
|
-
}`}>
|
|
207
|
-
<Plus className="h-3.5 w-3.5 shrink-0" />
|
|
208
|
-
<span>{createLabel} “<span className="font-medium">{value.trim()}</span>”</span>
|
|
209
|
-
</div>
|
|
210
|
-
)}
|
|
211
|
-
</div>,
|
|
212
|
-
document.body,
|
|
213
|
-
)}
|
|
214
|
-
</div>
|
|
215
|
-
)
|
|
216
|
-
}
|