@peak-ai/canvas 1.4.21 → 1.4.22

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.
Files changed (213) hide show
  1. package/.babelrc +14 -0
  2. package/.eslintcache +1 -0
  3. package/.eslintignore +5 -0
  4. package/.eslintrc.js +29 -0
  5. package/{GrapesjsCanvas.js → dist/GrapesjsCanvas.js} +3 -2
  6. package/dist/GrapesjsCanvas.js.map +1 -0
  7. package/dist/package.json +62 -0
  8. package/{plugins → dist/plugins}/helpers/render-components.js +1 -1
  9. package/dist/plugins/helpers/render-components.js.map +1 -0
  10. package/dist/shadcn/components/ui/error-wrapper.js +2 -0
  11. package/dist/shadcn/components/ui/error-wrapper.js.map +1 -0
  12. package/package.json +45 -7
  13. package/scripts/build.ts +120 -0
  14. package/src/GrapesjsCanvas.tsx +494 -0
  15. package/src/constants/index.ts +25 -0
  16. package/src/declaration.d.ts +1 -0
  17. package/src/helpers/compiled-table.css +2429 -0
  18. package/src/helpers/css.ts +2667 -0
  19. package/src/helpers/date-picker.ts +807 -0
  20. package/src/helpers/filter-placeholder.ts +18 -0
  21. package/src/helpers/index.ts +13 -0
  22. package/src/helpers/merge-json.ts +106 -0
  23. package/src/index.styles.ts +58 -0
  24. package/src/index.ts +9 -0
  25. package/src/plugins/grapejs-plugin.tsx +196 -0
  26. package/src/plugins/helpers/custom-modal.tsx +123 -0
  27. package/src/plugins/helpers/data-table.tsx +300 -0
  28. package/src/plugins/helpers/extra.tsx +164 -0
  29. package/src/plugins/helpers/query-cache-context.tsx +154 -0
  30. package/src/plugins/helpers/query-cache-singleton.ts +176 -0
  31. package/src/plugins/helpers/query-cache-utils.ts +226 -0
  32. package/src/plugins/helpers/query-details-modal.tsx +400 -0
  33. package/src/plugins/helpers/query-heading-formatter.ts +24 -0
  34. package/src/plugins/helpers/query-loading-modal.tsx +94 -0
  35. package/src/plugins/helpers/render-components.tsx +1450 -0
  36. package/src/plugins/helpers/styled-info-button.tsx +504 -0
  37. package/src/public/canvas.css +42 -0
  38. package/src/public/components-css/table/table-output.css +2436 -0
  39. package/src/public/components-css/table/table.css +30 -0
  40. package/src/public/output.css +2465 -0
  41. package/src/public/table.css +135 -0
  42. package/src/shadcn/components/icons/AiAvatarIcon.tsx +47 -0
  43. package/src/shadcn/components/icons/Co_driver Expanding button copy.svg +21 -0
  44. package/src/shadcn/components/icons/ai-avatar.svg +7 -0
  45. package/src/shadcn/components/icons/thinking.gif +0 -0
  46. package/src/shadcn/components/ui/button.tsx +132 -0
  47. package/src/shadcn/components/ui/card.tsx +92 -0
  48. package/src/shadcn/components/ui/chart.tsx +324 -0
  49. package/src/shadcn/components/ui/checkbox.tsx +27 -0
  50. package/src/shadcn/components/ui/component-wrapper.tsx +61 -0
  51. package/src/shadcn/components/ui/date-filter.tsx +816 -0
  52. package/src/shadcn/components/ui/error-container.tsx +125 -0
  53. package/src/shadcn/components/ui/error-wrapper.tsx +99 -0
  54. package/src/shadcn/components/ui/filter.tsx +368 -0
  55. package/src/shadcn/components/ui/hover-card.tsx +36 -0
  56. package/src/shadcn/components/ui/input.tsx +20 -0
  57. package/src/shadcn/components/ui/label.tsx +24 -0
  58. package/src/shadcn/components/ui/pagination.tsx +213 -0
  59. package/src/shadcn/components/ui/scroll-area.tsx +59 -0
  60. package/src/shadcn/components/ui/search.tsx +150 -0
  61. package/src/shadcn/components/ui/separator.tsx +26 -0
  62. package/src/shadcn/components/ui/skeleton.tsx +69 -0
  63. package/src/shadcn/components/ui/table.tsx +196 -0
  64. package/src/shadcn/components/ui/tabs.tsx +55 -0
  65. package/src/shadcn/components/ui/textarea.tsx +18 -0
  66. package/src/shadcn/components/ui/tooltip.tsx +87 -0
  67. package/src/shadcn/utils.ts +6 -0
  68. package/src/types/grapesjs-tailwind.d.ts +61 -0
  69. package/src/types/images.d.ts +1 -0
  70. package/tailwind.config.js +5 -0
  71. package/tooling/tailwind-compiler/index.js +99 -0
  72. package/tooling/tailwind-compiler/package.json +11 -0
  73. package/tooling/tailwind-compiler/yarn.lock +123 -0
  74. package/tsconfig.build.json +15 -0
  75. package/tsconfig.json +8 -0
  76. package/GrapesjsCanvas.js.map +0 -1
  77. package/plugins/helpers/render-components.js.map +0 -1
  78. package/shadcn/components/ui/error-wrapper.js +0 -2
  79. package/shadcn/components/ui/error-wrapper.js.map +0 -1
  80. /package/{GrapesjsCanvas.d.ts → dist/GrapesjsCanvas.d.ts} +0 -0
  81. /package/{constants → dist/constants}/index.d.ts +0 -0
  82. /package/{constants → dist/constants}/index.js +0 -0
  83. /package/{constants → dist/constants}/index.js.map +0 -0
  84. /package/{declaration.d.js → dist/declaration.d.js} +0 -0
  85. /package/{declaration.d.js.map → dist/declaration.d.js.map} +0 -0
  86. /package/{helpers → dist/helpers}/compiled-table.css +0 -0
  87. /package/{helpers → dist/helpers}/css.d.ts +0 -0
  88. /package/{helpers → dist/helpers}/css.js +0 -0
  89. /package/{helpers → dist/helpers}/css.js.map +0 -0
  90. /package/{helpers → dist/helpers}/date-picker.d.ts +0 -0
  91. /package/{helpers → dist/helpers}/date-picker.js +0 -0
  92. /package/{helpers → dist/helpers}/date-picker.js.map +0 -0
  93. /package/{helpers → dist/helpers}/filter-placeholder.d.ts +0 -0
  94. /package/{helpers → dist/helpers}/filter-placeholder.js +0 -0
  95. /package/{helpers → dist/helpers}/filter-placeholder.js.map +0 -0
  96. /package/{helpers → dist/helpers}/index.d.ts +0 -0
  97. /package/{helpers → dist/helpers}/index.js +0 -0
  98. /package/{helpers → dist/helpers}/index.js.map +0 -0
  99. /package/{helpers → dist/helpers}/merge-json.d.ts +0 -0
  100. /package/{helpers → dist/helpers}/merge-json.js +0 -0
  101. /package/{helpers → dist/helpers}/merge-json.js.map +0 -0
  102. /package/{index.d.ts → dist/index.d.ts} +0 -0
  103. /package/{index.js → dist/index.js} +0 -0
  104. /package/{index.js.map → dist/index.js.map} +0 -0
  105. /package/{index.styles.d.ts → dist/index.styles.d.ts} +0 -0
  106. /package/{index.styles.js → dist/index.styles.js} +0 -0
  107. /package/{index.styles.js.map → dist/index.styles.js.map} +0 -0
  108. /package/{plugins → dist/plugins}/grapejs-plugin.d.ts +0 -0
  109. /package/{plugins → dist/plugins}/grapejs-plugin.js +0 -0
  110. /package/{plugins → dist/plugins}/grapejs-plugin.js.map +0 -0
  111. /package/{plugins → dist/plugins}/helpers/custom-modal.d.ts +0 -0
  112. /package/{plugins → dist/plugins}/helpers/custom-modal.js +0 -0
  113. /package/{plugins → dist/plugins}/helpers/custom-modal.js.map +0 -0
  114. /package/{plugins → dist/plugins}/helpers/data-table.d.ts +0 -0
  115. /package/{plugins → dist/plugins}/helpers/data-table.js +0 -0
  116. /package/{plugins → dist/plugins}/helpers/data-table.js.map +0 -0
  117. /package/{plugins → dist/plugins}/helpers/extra.d.ts +0 -0
  118. /package/{plugins → dist/plugins}/helpers/extra.js +0 -0
  119. /package/{plugins → dist/plugins}/helpers/extra.js.map +0 -0
  120. /package/{plugins → dist/plugins}/helpers/query-cache-context.d.ts +0 -0
  121. /package/{plugins → dist/plugins}/helpers/query-cache-context.js +0 -0
  122. /package/{plugins → dist/plugins}/helpers/query-cache-context.js.map +0 -0
  123. /package/{plugins → dist/plugins}/helpers/query-cache-singleton.d.ts +0 -0
  124. /package/{plugins → dist/plugins}/helpers/query-cache-singleton.js +0 -0
  125. /package/{plugins → dist/plugins}/helpers/query-cache-singleton.js.map +0 -0
  126. /package/{plugins → dist/plugins}/helpers/query-cache-utils.d.ts +0 -0
  127. /package/{plugins → dist/plugins}/helpers/query-cache-utils.js +0 -0
  128. /package/{plugins → dist/plugins}/helpers/query-cache-utils.js.map +0 -0
  129. /package/{plugins → dist/plugins}/helpers/query-details-modal.d.ts +0 -0
  130. /package/{plugins → dist/plugins}/helpers/query-details-modal.js +0 -0
  131. /package/{plugins → dist/plugins}/helpers/query-details-modal.js.map +0 -0
  132. /package/{plugins → dist/plugins}/helpers/query-heading-formatter.d.ts +0 -0
  133. /package/{plugins → dist/plugins}/helpers/query-heading-formatter.js +0 -0
  134. /package/{plugins → dist/plugins}/helpers/query-heading-formatter.js.map +0 -0
  135. /package/{plugins → dist/plugins}/helpers/query-loading-modal.d.ts +0 -0
  136. /package/{plugins → dist/plugins}/helpers/query-loading-modal.js +0 -0
  137. /package/{plugins → dist/plugins}/helpers/query-loading-modal.js.map +0 -0
  138. /package/{plugins → dist/plugins}/helpers/render-components.d.ts +0 -0
  139. /package/{plugins → dist/plugins}/helpers/styled-info-button.d.ts +0 -0
  140. /package/{plugins → dist/plugins}/helpers/styled-info-button.js +0 -0
  141. /package/{plugins → dist/plugins}/helpers/styled-info-button.js.map +0 -0
  142. /package/{shadcn → dist/shadcn}/components/icons/AiAvatarIcon.d.ts +0 -0
  143. /package/{shadcn → dist/shadcn}/components/icons/AiAvatarIcon.js +0 -0
  144. /package/{shadcn → dist/shadcn}/components/icons/AiAvatarIcon.js.map +0 -0
  145. /package/{shadcn → dist/shadcn}/components/icons/thinking.gif +0 -0
  146. /package/{shadcn → dist/shadcn}/components/ui/button.d.ts +0 -0
  147. /package/{shadcn → dist/shadcn}/components/ui/button.js +0 -0
  148. /package/{shadcn → dist/shadcn}/components/ui/button.js.map +0 -0
  149. /package/{shadcn → dist/shadcn}/components/ui/card.d.ts +0 -0
  150. /package/{shadcn → dist/shadcn}/components/ui/card.js +0 -0
  151. /package/{shadcn → dist/shadcn}/components/ui/card.js.map +0 -0
  152. /package/{shadcn → dist/shadcn}/components/ui/chart.d.ts +0 -0
  153. /package/{shadcn → dist/shadcn}/components/ui/chart.js +0 -0
  154. /package/{shadcn → dist/shadcn}/components/ui/chart.js.map +0 -0
  155. /package/{shadcn → dist/shadcn}/components/ui/checkbox.d.ts +0 -0
  156. /package/{shadcn → dist/shadcn}/components/ui/checkbox.js +0 -0
  157. /package/{shadcn → dist/shadcn}/components/ui/checkbox.js.map +0 -0
  158. /package/{shadcn → dist/shadcn}/components/ui/component-wrapper.d.ts +0 -0
  159. /package/{shadcn → dist/shadcn}/components/ui/component-wrapper.js +0 -0
  160. /package/{shadcn → dist/shadcn}/components/ui/component-wrapper.js.map +0 -0
  161. /package/{shadcn → dist/shadcn}/components/ui/date-filter.d.ts +0 -0
  162. /package/{shadcn → dist/shadcn}/components/ui/date-filter.js +0 -0
  163. /package/{shadcn → dist/shadcn}/components/ui/date-filter.js.map +0 -0
  164. /package/{shadcn → dist/shadcn}/components/ui/error-container.d.ts +0 -0
  165. /package/{shadcn → dist/shadcn}/components/ui/error-container.js +0 -0
  166. /package/{shadcn → dist/shadcn}/components/ui/error-container.js.map +0 -0
  167. /package/{shadcn → dist/shadcn}/components/ui/error-wrapper.d.ts +0 -0
  168. /package/{shadcn → dist/shadcn}/components/ui/filter.d.ts +0 -0
  169. /package/{shadcn → dist/shadcn}/components/ui/filter.js +0 -0
  170. /package/{shadcn → dist/shadcn}/components/ui/filter.js.map +0 -0
  171. /package/{shadcn → dist/shadcn}/components/ui/hover-card.d.ts +0 -0
  172. /package/{shadcn → dist/shadcn}/components/ui/hover-card.js +0 -0
  173. /package/{shadcn → dist/shadcn}/components/ui/hover-card.js.map +0 -0
  174. /package/{shadcn → dist/shadcn}/components/ui/input.d.ts +0 -0
  175. /package/{shadcn → dist/shadcn}/components/ui/input.js +0 -0
  176. /package/{shadcn → dist/shadcn}/components/ui/input.js.map +0 -0
  177. /package/{shadcn → dist/shadcn}/components/ui/label.d.ts +0 -0
  178. /package/{shadcn → dist/shadcn}/components/ui/label.js +0 -0
  179. /package/{shadcn → dist/shadcn}/components/ui/label.js.map +0 -0
  180. /package/{shadcn → dist/shadcn}/components/ui/pagination.d.ts +0 -0
  181. /package/{shadcn → dist/shadcn}/components/ui/pagination.js +0 -0
  182. /package/{shadcn → dist/shadcn}/components/ui/pagination.js.map +0 -0
  183. /package/{shadcn → dist/shadcn}/components/ui/scroll-area.d.ts +0 -0
  184. /package/{shadcn → dist/shadcn}/components/ui/scroll-area.js +0 -0
  185. /package/{shadcn → dist/shadcn}/components/ui/scroll-area.js.map +0 -0
  186. /package/{shadcn → dist/shadcn}/components/ui/search.d.ts +0 -0
  187. /package/{shadcn → dist/shadcn}/components/ui/search.js +0 -0
  188. /package/{shadcn → dist/shadcn}/components/ui/search.js.map +0 -0
  189. /package/{shadcn → dist/shadcn}/components/ui/separator.d.ts +0 -0
  190. /package/{shadcn → dist/shadcn}/components/ui/separator.js +0 -0
  191. /package/{shadcn → dist/shadcn}/components/ui/separator.js.map +0 -0
  192. /package/{shadcn → dist/shadcn}/components/ui/skeleton.d.ts +0 -0
  193. /package/{shadcn → dist/shadcn}/components/ui/skeleton.js +0 -0
  194. /package/{shadcn → dist/shadcn}/components/ui/skeleton.js.map +0 -0
  195. /package/{shadcn → dist/shadcn}/components/ui/table.d.ts +0 -0
  196. /package/{shadcn → dist/shadcn}/components/ui/table.js +0 -0
  197. /package/{shadcn → dist/shadcn}/components/ui/table.js.map +0 -0
  198. /package/{shadcn → dist/shadcn}/components/ui/tabs.d.ts +0 -0
  199. /package/{shadcn → dist/shadcn}/components/ui/tabs.js +0 -0
  200. /package/{shadcn → dist/shadcn}/components/ui/tabs.js.map +0 -0
  201. /package/{shadcn → dist/shadcn}/components/ui/textarea.d.ts +0 -0
  202. /package/{shadcn → dist/shadcn}/components/ui/textarea.js +0 -0
  203. /package/{shadcn → dist/shadcn}/components/ui/textarea.js.map +0 -0
  204. /package/{shadcn → dist/shadcn}/components/ui/tooltip.d.ts +0 -0
  205. /package/{shadcn → dist/shadcn}/components/ui/tooltip.js +0 -0
  206. /package/{shadcn → dist/shadcn}/components/ui/tooltip.js.map +0 -0
  207. /package/{shadcn → dist/shadcn}/utils.d.ts +0 -0
  208. /package/{shadcn → dist/shadcn}/utils.js +0 -0
  209. /package/{shadcn → dist/shadcn}/utils.js.map +0 -0
  210. /package/{types → dist/types}/grapesjs-tailwind.d.js +0 -0
  211. /package/{types → dist/types}/grapesjs-tailwind.d.js.map +0 -0
  212. /package/{types → dist/types}/images.d.js +0 -0
  213. /package/{types → dist/types}/images.d.js.map +0 -0
@@ -0,0 +1,816 @@
1
+ /* eslint-disable func-style */
2
+ /* eslint-disable camelcase */
3
+ /* eslint-disable @typescript-eslint/no-explicit-any */
4
+ import { useEffect, useRef, useState } from 'react';
5
+ import { Calendar, Loader2Icon, X } from 'lucide-react';
6
+ import ReactDatePicker from 'react-datepicker';
7
+ import { zonedTimeToUtc, utcToZonedTime } from 'date-fns-tz';
8
+ import classnames from 'classnames';
9
+
10
+ import { getAffectedComponentsWithLoader } from '../../../helpers';
11
+ import { Button } from './button';
12
+ import sherlock from 'sherlockjs';
13
+
14
+ export type DatePickerProps = {
15
+ className?: string;
16
+ dateFormat?: string;
17
+ disabled?: boolean;
18
+ enableTime?: boolean;
19
+ isInputEditable?: boolean;
20
+ isClearable?: boolean;
21
+ label?: string;
22
+ maxDate?: Date | undefined;
23
+ minDate?: Date | undefined;
24
+ onChange(date: Date | null): void;
25
+ value: Date | null;
26
+ open?: boolean;
27
+ onClickOutside?: () => void;
28
+ showTimeSelect?: boolean;
29
+ timeFormat?: string;
30
+ timeIntervals?: number;
31
+ inline?: boolean;
32
+ };
33
+
34
+ type DateFilterProps = {
35
+ gjsModel: any;
36
+ performInteraction: (payload: Record<string, any>) => Promise<void>;
37
+ };
38
+
39
+ const TIMEZONE = 'TIMEZONE';
40
+
41
+ function getTimezone() {
42
+ return window.localStorage.getItem(TIMEZONE) || 'UTC';
43
+ }
44
+
45
+ const formatDateWithTimezone = (date: Date | null, timezone: string, showTime = false): string => {
46
+ if (!date) {
47
+ return '';
48
+ }
49
+
50
+ const formatOptions: Intl.DateTimeFormatOptions = {
51
+ day: '2-digit',
52
+ month: '2-digit',
53
+ year: 'numeric',
54
+ ...(showTime && {
55
+ hour: '2-digit',
56
+ minute: '2-digit',
57
+ hour12: false,
58
+ }),
59
+ timeZone: timezone,
60
+ };
61
+
62
+ return date.toLocaleString('en-GB', formatOptions);
63
+ };
64
+
65
+ const convertToTargetTimezone = (date: Date | null): Date | null => {
66
+ if (!date) {return null;}
67
+
68
+ const targetTimezone = getTimezone();
69
+
70
+ return zonedTimeToUtc(date, targetTimezone);
71
+ };
72
+
73
+ const convertFromStoredDate = (date: Date | null): Date | null => {
74
+ if (!date) {return null;}
75
+
76
+ const targetTimezone = getTimezone();
77
+
78
+ return utcToZonedTime(date, targetTimezone);
79
+ };
80
+
81
+ const inputClasses =
82
+ 'w-full px-3 py-2 border border-gray-300 rounded-md mb-1 focus:outline-none focus:ring-2 focus:ring-blue-500';
83
+ const disabledClasses = 'opacity-60 cursor-not-allowed bg-gray-50';
84
+ const enabledClasses = 'cursor-pointer';
85
+
86
+ const buttonClasses =
87
+ 'flex items-center gap-2 px-3 py-2 border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 transition-colors';
88
+ const closeButtonClasses =
89
+ 'p-1 rounded-md hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500';
90
+
91
+ const dropdownClasses =
92
+ 'absolute top-full mt-1 bg-white rounded-lg shadow-lg border border-gray-200 p-4 w-[380px] z-[9999]';
93
+
94
+ const parseRelativeDate = (dateString: string | Date | null): Date | null => {
95
+ if (!dateString) {
96
+ return null;
97
+ }
98
+
99
+ if (dateString instanceof Date) {
100
+ return dateString;
101
+ }
102
+
103
+ const result = sherlock.parse(dateString as string);
104
+
105
+ if (result.startDate) {
106
+ return result.startDate;
107
+ }
108
+
109
+ return null;
110
+ };
111
+
112
+ const getDefaultDates = () => {
113
+ const now = new Date();
114
+ const oneYearAgo = new Date(now);
115
+ oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
116
+ const oneYearFromNow = new Date(now);
117
+ oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1);
118
+
119
+ return {
120
+ defaultStartDate: new Date(
121
+ oneYearAgo.getFullYear(),
122
+ oneYearAgo.getMonth(),
123
+ oneYearAgo.getDate(),
124
+ 0,
125
+ 0,
126
+ 0,
127
+ ),
128
+ defaultEndDate: new Date(
129
+ oneYearFromNow.getFullYear(),
130
+ oneYearFromNow.getMonth(),
131
+ oneYearFromNow.getDate(),
132
+ 23,
133
+ 59,
134
+ 59,
135
+ ),
136
+ };
137
+ };
138
+
139
+ const DateInput = ({
140
+ label,
141
+ value,
142
+ onClick,
143
+ disabled,
144
+ placeholder,
145
+ isOpen,
146
+ showTime = false,
147
+ onChange,
148
+ minDate,
149
+ maxDate,
150
+ }: {
151
+ label: string;
152
+ value: Date | null;
153
+ onClick: () => void;
154
+ disabled?: boolean;
155
+ placeholder?: string;
156
+ isOpen?: boolean;
157
+ showTime?: boolean;
158
+ onChange?: (date: Date | null) => void;
159
+ minDate?: Date | null;
160
+ maxDate?: Date | null;
161
+ }) => {
162
+ const [inputValue, setInputValue] = useState('');
163
+ const [isEditing, setIsEditing] = useState(false);
164
+ const [validationError, setValidationError] = useState<string>('');
165
+
166
+ const formatDate = (date: Date | null) => {
167
+ const timezone = getTimezone();
168
+
169
+ return formatDateWithTimezone(date, timezone, showTime);
170
+ };
171
+
172
+ useEffect(() => {
173
+ if (!isEditing) {
174
+ setInputValue(formatDate(value));
175
+ }
176
+ }, [value, showTime, isEditing]);
177
+
178
+ const handleClick = (e: React.MouseEvent) => {
179
+ e.preventDefault();
180
+ e.stopPropagation();
181
+
182
+ if (!disabled) {
183
+ onClick();
184
+ }
185
+ };
186
+
187
+ const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
188
+ setInputValue(e.target.value);
189
+ };
190
+
191
+ const handleInputFocus = () => {
192
+ setIsEditing(true);
193
+ setValidationError('');
194
+ };
195
+
196
+ const parseInputDate = (inputText: string): Date | null => {
197
+ if (!inputText.trim()) {
198
+ return null;
199
+ }
200
+
201
+ const commonFormats = [
202
+ // DD/MM/YYYY formats
203
+ /^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:\s*,?\s*(\d{1,2}):(\d{2}))?$/,
204
+ // DD-MM-YYYY formats
205
+ /^(\d{1,2})-(\d{1,2})-(\d{4})(?:\s*,?\s*(\d{1,2}):(\d{2}))?$/,
206
+ // YYYY-MM-DD formats
207
+ /^(\d{4})-(\d{1,2})-(\d{1,2})(?:\s*,?\s*(\d{1,2}):(\d{2}))?$/,
208
+ // YYYY/MM/DD formats
209
+ /^(\d{4})\/(\d{1,2})\/(\d{1,2})(?:\s*,?\s*(\d{1,2}):(\d{2}))?$/,
210
+ ];
211
+
212
+ for (const format of commonFormats) {
213
+ const match = inputText.match(format);
214
+
215
+ if (match) {
216
+ let day: string; let month: string; let year: string; let hours = '0'; let minutes = '0';
217
+
218
+ if (format.source.startsWith('^(\\d{4})')) {
219
+ [, year, month, day, hours = '0', minutes = '0'] = match;
220
+ } else {
221
+ [, day, month, year, hours = '0', minutes = '0'] = match;
222
+ }
223
+
224
+ const yearNum = parseInt(year, 10);
225
+ const monthNum = parseInt(month, 10);
226
+ const dayNum = parseInt(day, 10);
227
+ const hoursNum = parseInt(hours, 10);
228
+ const minutesNum = parseInt(minutes, 10);
229
+
230
+ const parsedDate = new Date(
231
+ yearNum,
232
+ monthNum - 1,
233
+ dayNum,
234
+ hoursNum,
235
+ minutesNum
236
+ );
237
+
238
+ if (
239
+ !isNaN(parsedDate.getTime()) &&
240
+ parsedDate.getFullYear() === yearNum &&
241
+ parsedDate.getMonth() === monthNum - 1 &&
242
+ parsedDate.getDate() === dayNum
243
+ ) {
244
+ return parsedDate;
245
+ }
246
+ }
247
+ }
248
+
249
+ try {
250
+ const result = sherlock.parse(inputText);
251
+
252
+ if (result.startDate) {
253
+ return result.startDate;
254
+ }
255
+ } catch (error) {
256
+
257
+ return null;
258
+ }
259
+
260
+ return null;
261
+ };
262
+
263
+ const validateDateRange = (date: Date | null): string => {
264
+ if (!date) {
265
+ return '';
266
+ }
267
+
268
+ if (minDate && date < minDate) {
269
+ return `Minimum allowed date is ${formatDateWithTimezone(minDate, getTimezone(), false)}`;
270
+ }
271
+
272
+ if (maxDate && date > maxDate) {
273
+ return `Maximum allowed date is ${formatDateWithTimezone(maxDate, getTimezone(), false)}`;
274
+ }
275
+
276
+ return '';
277
+ };
278
+
279
+ const handleInputBlur = () => {
280
+ setIsEditing(false);
281
+
282
+ if (onChange) {
283
+ const parsedDate = parseInputDate(inputValue);
284
+
285
+ if (parsedDate) {
286
+ const errorMessage = validateDateRange(parsedDate);
287
+ setValidationError(errorMessage);
288
+
289
+ if (!errorMessage) {
290
+ onChange(parsedDate);
291
+ }
292
+ } else if (inputValue.trim() === '') {
293
+ setValidationError('');
294
+ onChange(null);
295
+ } else {
296
+ setValidationError('');
297
+ setInputValue(formatDate(value));
298
+ }
299
+ }
300
+ };
301
+
302
+ const handleKeyDown = (e: React.KeyboardEvent) => {
303
+ if (e.key === 'Enter') {
304
+ e.preventDefault();
305
+ handleInputBlur();
306
+ } else if (e.key === 'Escape') {
307
+ e.preventDefault();
308
+ setInputValue(formatDate(value));
309
+ setValidationError('');
310
+ setIsEditing(false);
311
+ }
312
+ };
313
+
314
+ return (
315
+ <div className="relative">
316
+ <input
317
+ value={inputValue}
318
+ onChange={handleInputChange}
319
+ onFocus={handleInputFocus}
320
+ onBlur={handleInputBlur}
321
+ onKeyDown={handleKeyDown}
322
+ onClick={handleClick}
323
+ placeholder={placeholder || label}
324
+ disabled={disabled}
325
+ className={`${inputClasses} ${disabled ? disabledClasses : enabledClasses} ${
326
+ isOpen ? 'ring-2 ring-blue-500' : ''
327
+ }`}
328
+ title={showTime ?
329
+ "Enter date and time" :
330
+ "Enter date"
331
+ }
332
+ />
333
+ <div className="absolute right-3 top-7 transform -translate-y-1/2 flex items-center gap-1">
334
+ <Calendar size={16} className="text-gray-400 cursor-pointer" onClick={handleClick} />
335
+ </div>
336
+ {validationError && (
337
+ <div className="absolute top-full left-0 text-xs text-red-600 px-2 mb-2 z-10">
338
+ {validationError}
339
+ </div>
340
+ )}
341
+ </div>
342
+ );
343
+ };
344
+
345
+ export function DatePicker({
346
+ className = '',
347
+ disabled = false,
348
+ enableTime = false,
349
+ isClearable = true,
350
+ maxDate,
351
+ minDate,
352
+ onChange,
353
+ value = null,
354
+ open,
355
+ onClickOutside,
356
+ showTimeSelect = false,
357
+ timeFormat = 'HH:mm',
358
+ timeIntervals = 15,
359
+ inline = true,
360
+ }: DatePickerProps) {
361
+ const dateFormat = showTimeSelect || enableTime ? 'yyyy-MM-dd HH:mm' : 'yyyy-MM-dd';
362
+
363
+ const date = typeof value === 'string' && value ? new Date(value) : value;
364
+
365
+ const shouldShowTime = showTimeSelect || enableTime;
366
+
367
+ const datePickerProps = {
368
+ selected: date,
369
+ minDate,
370
+ maxDate,
371
+ disabled,
372
+ isClearable,
373
+ onChange,
374
+ dateFormat,
375
+ showTimeSelect: shouldShowTime,
376
+ timeFormat,
377
+ timeIntervals,
378
+ timeCaption: 'Time',
379
+ portalId: 'date-picker-portal',
380
+ popperClassName: 'date-picker-popper !z-[9999]',
381
+ popperPlacement: 'bottom-end',
382
+ inline,
383
+ open,
384
+ onClickOutside,
385
+ className: classnames(className, inputClasses),
386
+ } as any;
387
+
388
+ return (
389
+ <div className={`relative inline-block ${className}`}>
390
+ {
391
+ <div className="absolute z-[9999] mt-1 border rounded-md shadow-lg bg-white">
392
+ <ReactDatePicker {...datePickerProps} />
393
+ </div>
394
+ }
395
+ </div>
396
+ );
397
+ }
398
+
399
+ export function renderDateFilter({ gjsModel, performInteraction }: DateFilterProps) {
400
+ const wrapperRef = useRef<HTMLDivElement>(null);
401
+ const buttonRef = useRef<HTMLButtonElement>(null);
402
+ const startDatePickerRef = useRef<HTMLDivElement>(null);
403
+ const endDatePickerRef = useRef<HTMLDivElement>(null);
404
+ const dropdownRef = useRef<HTMLDivElement>(null);
405
+
406
+ const [componentData, setComponentData] = useState({ ...gjsModel.get('componentProps') });
407
+ const [attributes, setAttributes] = useState({ ...gjsModel.get('attributes') });
408
+ const [isApplying, setIsApplying] = useState(false);
409
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
410
+ const [isStartDateOpen, setIsStartDateOpen] = useState(false);
411
+ const [isEndDateOpen, setIsEndDateOpen] = useState(false);
412
+ const [dropdownPosition, setDropdownPosition] = useState({ left: 'auto', right: '0' });
413
+
414
+ const {
415
+ className = '',
416
+ affectedComponents = [],
417
+ startLabel = 'Start date',
418
+ endLabel = 'End date',
419
+ isClearable = true,
420
+ dateFormat = 'yyyy-MM-dd',
421
+ minDate,
422
+ maxDate,
423
+ showTimeSelect = true,
424
+ timeFormat = 'HH:mm',
425
+ timeIntervals = 15,
426
+ value,
427
+ default: defaultValue,
428
+ } = componentData;
429
+
430
+ const { defaultStartDate, defaultEndDate } = getDefaultDates();
431
+
432
+ const globalMin = minDate ? parseRelativeDate(minDate) : undefined;
433
+ const globalMax = maxDate ? parseRelativeDate(maxDate) : undefined;
434
+
435
+ const getInitialStartDate = (): Date | null => {
436
+ if (value?.start) {
437
+ return new Date(value.start);
438
+ }
439
+
440
+ if (defaultValue?.start) {
441
+ return parseRelativeDate(defaultValue.start);
442
+ }
443
+
444
+ return defaultStartDate;
445
+ };
446
+
447
+ const getInitialEndDate = (): Date | null => {
448
+ if (value?.end) {
449
+ return new Date(value.end);
450
+ }
451
+
452
+ if (defaultValue?.end) {
453
+ return parseRelativeDate(defaultValue.end);
454
+ }
455
+
456
+ return defaultEndDate;
457
+ };
458
+
459
+ const [startDate, setStartDate] = useState<Date | null>(getInitialStartDate());
460
+ const [endDate, setEndDate] = useState<Date | null>(getInitialEndDate());
461
+ const [tempStartDate, setTempStartDate] = useState<Date | null>(null);
462
+ const [tempEndDate, setTempEndDate] = useState<Date | null>(null);
463
+
464
+ const isBusy = attributes.interactionApiInProgress || isApplying;
465
+
466
+ const getMaxStartDate = () => {
467
+ return globalMax || tempEndDate;
468
+ };
469
+
470
+ const getMinEndDate = () => {
471
+ return globalMin || tempStartDate;
472
+ };
473
+
474
+ const closeAllPickers = () => {
475
+ setIsStartDateOpen(false);
476
+ setIsEndDateOpen(false);
477
+ };
478
+
479
+ const closeDropdown = () => {
480
+ setIsDropdownOpen(false);
481
+ closeAllPickers();
482
+ };
483
+
484
+ const toggleDropdown = () => {
485
+ if (!isDropdownOpen) {
486
+ closeAllPickers();
487
+ }
488
+
489
+ setIsDropdownOpen(!isDropdownOpen);
490
+ };
491
+
492
+ const handleStartDateClick = () => {
493
+ if (!isApplying && !isBusy) {
494
+ setIsEndDateOpen(false);
495
+ setIsStartDateOpen(!isStartDateOpen);
496
+ }
497
+ };
498
+
499
+ const handleEndDateClick = () => {
500
+ if (!isApplying && !isBusy) {
501
+ setIsStartDateOpen(false);
502
+ setIsEndDateOpen(!isEndDateOpen);
503
+ }
504
+ };
505
+
506
+ useEffect(() => {
507
+ const doc = wrapperRef.current?.ownerDocument ?? document;
508
+
509
+ function handleOutsideClick(e: PointerEvent) {
510
+ if (isDropdownOpen && wrapperRef.current && !wrapperRef.current.contains(e.target as Node)) {
511
+ closeDropdown();
512
+ }
513
+ }
514
+
515
+ function handleEscape(e: KeyboardEvent) {
516
+ if (e.key === 'Escape') {
517
+ closeDropdown();
518
+ }
519
+ }
520
+
521
+ doc.addEventListener('pointerdown', handleOutsideClick);
522
+ doc.addEventListener('keydown', handleEscape);
523
+
524
+ return () => {
525
+ doc.removeEventListener('pointerdown', handleOutsideClick);
526
+ doc.removeEventListener('keydown', handleEscape);
527
+ };
528
+ }, [isDropdownOpen]);
529
+
530
+ // Add click event listeners to detect time selector clicks
531
+ useEffect(() => {
532
+ const handleTimeClick = (event: MouseEvent, isStartPicker: boolean) => {
533
+ const target = event.target as HTMLElement;
534
+
535
+ // Check if the click was on time-related elements
536
+ const isTimeClick = target.closest('.react-datepicker__time-container') ||
537
+ target.closest('.react-datepicker__time-list') ||
538
+ target.closest('.react-datepicker__time-list-item') ||
539
+ target.classList.contains('react-datepicker__time-list-item');
540
+
541
+ if (isTimeClick && showTimeSelect) {
542
+ // Close the picker after a small delay to allow the time selection to complete
543
+ setTimeout(() => {
544
+ if (isStartPicker) {
545
+ setIsStartDateOpen(false);
546
+ } else {
547
+ setIsEndDateOpen(false);
548
+ }
549
+ }, 100);
550
+ }
551
+ };
552
+
553
+ const startPickerElement = startDatePickerRef.current;
554
+ const endPickerElement = endDatePickerRef.current;
555
+
556
+ if (isStartDateOpen && startPickerElement) {
557
+ const handleStartTimeClick = (e: MouseEvent) => handleTimeClick(e, true);
558
+ startPickerElement.addEventListener('click', handleStartTimeClick);
559
+
560
+ return () => {
561
+ startPickerElement.removeEventListener('click', handleStartTimeClick);
562
+ };
563
+ }
564
+
565
+ if (isEndDateOpen && endPickerElement) {
566
+ const handleEndTimeClick = (e: MouseEvent) => handleTimeClick(e, false);
567
+ endPickerElement.addEventListener('click', handleEndTimeClick);
568
+
569
+ return () => {
570
+ endPickerElement.removeEventListener('click', handleEndTimeClick);
571
+ };
572
+ }
573
+
574
+ // Always return undefined if no cleanup is needed
575
+ return undefined;
576
+ }, [isStartDateOpen, isEndDateOpen, showTimeSelect]);
577
+
578
+ const handleStartDateChange = (date: Date | null) => {
579
+ const adjustedDate = convertToTargetTimezone(date);
580
+ setTempStartDate(adjustedDate);
581
+
582
+ if (!showTimeSelect) {
583
+ setIsStartDateOpen(false);
584
+ }
585
+ };
586
+
587
+ const handleEndDateChange = (date: Date | null) => {
588
+ const adjustedDate = convertToTargetTimezone(date);
589
+ setTempEndDate(adjustedDate);
590
+
591
+ if (!showTimeSelect) {
592
+ setIsEndDateOpen(false);
593
+ }
594
+ };
595
+
596
+ const handleApply = async () => {
597
+ setIsApplying(true);
598
+
599
+ try {
600
+ const dateValues = {
601
+ start: tempStartDate?.toISOString() || null,
602
+ end: tempEndDate?.toISOString() || null,
603
+ };
604
+
605
+ gjsModel.set('componentProps', {
606
+ ...gjsModel.get('componentProps'),
607
+ value: dateValues,
608
+ });
609
+
610
+ const id = gjsModel.get('id');
611
+
612
+ setStartDate(tempStartDate);
613
+ setEndDate(tempEndDate);
614
+
615
+ await performInteraction({
616
+ interactionType: 'dateFilter',
617
+ id,
618
+ affectedComponents: getAffectedComponentsWithLoader([id, ...affectedComponents], true),
619
+ });
620
+
621
+ closeDropdown();
622
+ } finally {
623
+ setIsApplying(false);
624
+ }
625
+ };
626
+
627
+ useEffect(() => {
628
+ if (isDropdownOpen) {
629
+ setTempStartDate(startDate);
630
+ setTempEndDate(endDate);
631
+ } else {
632
+ closeAllPickers();
633
+ }
634
+ }, [isDropdownOpen, startDate, endDate]);
635
+
636
+ useEffect(() => {
637
+ if (!isDropdownOpen || !buttonRef.current) {
638
+ return;
639
+ }
640
+
641
+ const buttonRect = buttonRef.current.getBoundingClientRect();
642
+ const dropdownWidth = 380;
643
+ const iframeViewportWidth = document.documentElement.clientWidth;
644
+ const iframeBodyWidth =
645
+ document.getElementById('dashboard-root')?.clientWidth || iframeViewportWidth;
646
+
647
+ const spaceToRight = iframeBodyWidth - buttonRect.right;
648
+ const spaceToLeft = buttonRect.left;
649
+
650
+ if (spaceToLeft >= dropdownWidth) {
651
+ setDropdownPosition({ left: 'auto', right: '0px' });
652
+ } else if (spaceToRight < dropdownWidth && spaceToLeft < dropdownWidth) {
653
+ const shiftLeft = dropdownWidth - spaceToRight + 16;
654
+ setDropdownPosition({ left: `-${shiftLeft}px`, right: 'auto' });
655
+ } else if (spaceToRight >= dropdownWidth) {
656
+ setDropdownPosition({ left: '0px', right: 'auto' });
657
+ }
658
+ }, [isDropdownOpen]);
659
+
660
+ useEffect(() => {
661
+ const updateComponentData = () => {
662
+ const newData = { ...gjsModel.get('componentProps') };
663
+ setComponentData(newData);
664
+
665
+ if (newData.value?.start && newData.value.start !== componentData.value?.start) {
666
+ setStartDate(new Date(newData.value.start));
667
+ }
668
+
669
+ if (newData.value?.end && newData.value.end !== componentData.value?.end) {
670
+ setEndDate(new Date(newData.value.end));
671
+ }
672
+ };
673
+
674
+ const updateAttributes = () => {
675
+ setAttributes({ ...gjsModel.get('attributes') });
676
+ };
677
+
678
+ gjsModel.on('change:componentProps', updateComponentData);
679
+ gjsModel.on('change:attributes', updateAttributes);
680
+
681
+ return () => {
682
+ gjsModel.off('change:componentProps', updateComponentData);
683
+ gjsModel.off('change:attributes', updateAttributes);
684
+ };
685
+ }, [gjsModel, componentData.value]);
686
+
687
+ return (
688
+ <div ref={wrapperRef} className={`relative ${className}`}>
689
+ <button
690
+ ref={buttonRef}
691
+ onClick={toggleDropdown}
692
+ disabled={isBusy}
693
+ className={`${buttonClasses} ${isBusy ? disabledClasses : enabledClasses}`}
694
+ title="Open Date & Time Filter"
695
+ >
696
+ <div className="flex items-center gap-1">
697
+ <Calendar size={16} className="text-gray-600" />
698
+ </div>
699
+ {isBusy && <Loader2Icon size={14} className="animate-spin text-blue-500" />}
700
+ </button>
701
+
702
+ {isDropdownOpen && (
703
+ <div ref={dropdownRef} className={dropdownClasses} style={dropdownPosition}>
704
+ <div className="space-y-4">
705
+ <div className="flex items-center justify-between">
706
+ <h3 className="text-sm font-medium text-gray-900">Select date & time range</h3>
707
+ <button onClick={closeDropdown} className={closeButtonClasses}>
708
+ <X size={14} className="text-gray-400" />
709
+ </button>
710
+ </div>
711
+
712
+ <div className="space-y-2">
713
+ <label className="block text-sm font-medium text-gray-700">{startLabel}</label>
714
+ <div className="relative">
715
+ <DateInput
716
+ label={startLabel}
717
+ value={tempStartDate}
718
+ onClick={handleStartDateClick}
719
+ disabled={isApplying}
720
+ placeholder="Select start date"
721
+ isOpen={isStartDateOpen}
722
+ showTime={showTimeSelect}
723
+ onChange={handleStartDateChange}
724
+ minDate={globalMin ? (convertFromStoredDate(globalMin) ?? undefined) : undefined}
725
+ maxDate={getMaxStartDate() ? (convertFromStoredDate(getMaxStartDate()) ?? undefined) : undefined}
726
+ />
727
+ </div>
728
+
729
+ {isStartDateOpen && (
730
+ <div ref={startDatePickerRef} className="mt-2 p-2">
731
+ <ReactDatePicker
732
+ selected={convertFromStoredDate(tempStartDate)}
733
+ onChange={handleStartDateChange}
734
+ minDate={globalMin ? (convertFromStoredDate(globalMin) ?? undefined) : undefined}
735
+ maxDate={getMaxStartDate() ? (convertFromStoredDate(getMaxStartDate()) ?? undefined) : undefined}
736
+ showTimeSelect={showTimeSelect}
737
+ timeFormat={timeFormat}
738
+ timeIntervals={timeIntervals}
739
+ timeCaption="Time"
740
+ dateFormat={showTimeSelect ? 'dd/MM/yyyy HH:mm' : dateFormat}
741
+ inline={true}
742
+ isClearable={isClearable}
743
+ disabled={isApplying}
744
+ />
745
+ </div>
746
+ )}
747
+ </div>
748
+
749
+ <div className="space-y-2">
750
+ <label className="block text-sm font-medium text-gray-700">{endLabel}</label>
751
+ <div className="relative">
752
+ <DateInput
753
+ label={endLabel}
754
+ value={tempEndDate}
755
+ onClick={handleEndDateClick}
756
+ disabled={isApplying}
757
+ placeholder="Select end date"
758
+ isOpen={isEndDateOpen}
759
+ showTime={showTimeSelect}
760
+ onChange={handleEndDateChange}
761
+ minDate={getMinEndDate() ? (convertFromStoredDate(getMinEndDate()) ?? undefined) : undefined}
762
+ maxDate={globalMax ? (convertFromStoredDate(globalMax) ?? undefined) : undefined}
763
+ />
764
+ </div>
765
+
766
+ {isEndDateOpen && (
767
+ <div ref={endDatePickerRef} className="mt-2 p-2">
768
+ <ReactDatePicker
769
+ selected={convertFromStoredDate(tempEndDate)}
770
+ onChange={handleEndDateChange}
771
+ minDate={getMinEndDate() ? (convertFromStoredDate(getMinEndDate()) ?? undefined) : undefined}
772
+ maxDate={globalMax ? (convertFromStoredDate(globalMax) ?? undefined) : undefined}
773
+ showTimeSelect={showTimeSelect}
774
+ timeFormat={timeFormat}
775
+ timeIntervals={timeIntervals}
776
+ timeCaption="Time"
777
+ dateFormat={showTimeSelect ? 'dd/MM/yyyy HH:mm' : dateFormat}
778
+ inline={true}
779
+ isClearable={isClearable}
780
+ disabled={isApplying}
781
+ />
782
+ </div>
783
+ )}
784
+ </div>
785
+
786
+ <div className="flex gap-2 pt-2">
787
+ <Button
788
+ variant="outline"
789
+ onClick={closeDropdown}
790
+ disabled={isApplying}
791
+ className="flex-1 h-8 text-xs bg-[#EAECFB] border border-[#2A44D4] text-[#263DBF] hover:bg-[#EAECFB] hover:text-[#263DBF]"
792
+ size="sm"
793
+ >
794
+ Cancel
795
+ </Button>
796
+ <Button
797
+ onClick={handleApply}
798
+ disabled={isApplying}
799
+ className="flex-1 h-8 text-xs bg-[#2A44D4] text-white hover:bg-[#2A44D4] hover:text-white"
800
+ size="sm"
801
+ >
802
+ {isApplying ? (
803
+ <>
804
+ <Loader2Icon size={12} className="animate-spin mr-1" />
805
+ </>
806
+ ) : (
807
+ 'Apply'
808
+ )}
809
+ </Button>
810
+ </div>
811
+ </div>
812
+ </div>
813
+ )}
814
+ </div>
815
+ );
816
+ }