@addsign/moje-agenda-shared-lib 2.0.72 → 2.0.73

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 (45) hide show
  1. package/dist/Dialog-BmQoVu5C.js.map +1 -1
  2. package/dist/assets/style.css +1772 -1758
  3. package/dist/components/Attachments.js +2 -2
  4. package/dist/components/datatable/DataTable.js +3 -3
  5. package/dist/components/datatable/DataTableServer.js +3 -3
  6. package/dist/components/form/AutocompleteSearchBar.js +2 -2
  7. package/dist/components/form/AutocompleteSearchBarServer.js +2 -2
  8. package/dist/components/form/FileInput.js +3 -3
  9. package/dist/components/form/FileInputForm.d.ts +1 -0
  10. package/dist/components/form/FileInputForm.js +201 -93
  11. package/dist/components/form/FileInputForm.js.map +1 -1
  12. package/dist/components/form/FileInputFormMultiple.d.ts +1 -0
  13. package/dist/components/form/FileInputFormMultiple.js +203 -82
  14. package/dist/components/form/FileInputFormMultiple.js.map +1 -1
  15. package/dist/components/form/FileInputMultiple.js +3 -3
  16. package/dist/components/form/FormField.js +2 -2
  17. package/dist/components/form/PositionsSelectorSingle.js +3 -3
  18. package/dist/components/form/SelectField.js +2 -2
  19. package/dist/components/layout/Neoptimizovano.js +2 -2
  20. package/dist/components/profiles/ProfileOverview.js +2 -2
  21. package/dist/components/ui/Combobox.js +1 -1
  22. package/dist/components/ui/DatePicker.js +2 -2
  23. package/dist/components/ui/DateTimePicker.js +2 -2
  24. package/dist/components/ui/Dialog.js +1 -1
  25. package/dist/components/ui/ScrollArea.js +2 -2
  26. package/dist/components/ui/checkbox.js +4 -4
  27. package/dist/components/ui/command.d.ts +6 -6
  28. package/dist/components/ui/command.js +2 -2
  29. package/dist/components/ui/input.js +8 -107
  30. package/dist/components/ui/input.js.map +1 -1
  31. package/dist/components/ui/multi-select.js +1 -1
  32. package/dist/components/ui/popover.js +1 -1
  33. package/dist/components/ui/radioGroup.js +5 -5
  34. package/dist/components/ui/select.js +7 -7
  35. package/dist/components/ui/toast.js +5 -5
  36. package/dist/components/ui/tooltip.js +6 -6
  37. package/dist/input-Cm_FjJOF.js +111 -0
  38. package/dist/input-Cm_FjJOF.js.map +1 -0
  39. package/dist/main.js +3 -3
  40. package/dist/popover-DpJhfyvx.js.map +1 -1
  41. package/lib/components/form/FileInputForm.tsx +245 -99
  42. package/lib/components/form/FileInputFormMultiple.tsx +233 -65
  43. package/lib/css/tailwind.css +9 -9
  44. package/package.json +1 -1
  45. package/tailwind.config.js +97 -97
@@ -2,9 +2,19 @@ import React, { useState, useEffect, useCallback } from "react";
2
2
  import { useDropzone } from "react-dropzone";
3
3
  import { MdDeleteOutline, MdInsertDriveFile } from "react-icons/md";
4
4
  import { AxiosError } from "axios";
5
+ import { LoaderCircleIcon } from "lucide-react";
5
6
  import { cn } from "../../utils/utils";
6
7
  import { IAttachment } from "../../types";
7
8
  import { handleErrors, useFederationContext, useFormField } from "../../main";
9
+ import {
10
+ Dialog,
11
+ DialogContent,
12
+ DialogHeader,
13
+ DialogTitle,
14
+ DialogFooter,
15
+ } from "../ui/Dialog";
16
+ import { Input } from "../ui/input";
17
+ import { Button } from "../ui/button";
8
18
 
9
19
  export interface FileInputFormProps {
10
20
  name: string;
@@ -14,6 +24,7 @@ export interface FileInputFormProps {
14
24
  disabled?: boolean;
15
25
  attachmentName?: string;
16
26
  attachmentType?: string;
27
+ askForAttachmentName?: boolean;
17
28
  }
18
29
 
19
30
  const MAX_FILE_SIZE = 1024 * 1024; // 1MB
@@ -26,9 +37,14 @@ const FileInputForm: React.FC<FileInputFormProps> = ({
26
37
  disabled,
27
38
  attachmentName,
28
39
  attachmentType,
40
+ askForAttachmentName = false,
29
41
  }) => {
30
42
  const [fileData, setFileData] = useState<IAttachment | null>(value || null);
31
43
  const [isFocused, setIsFocused] = useState(false);
44
+ const [pendingFile, setPendingFile] = useState<File | null>(null);
45
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
46
+ const [fileName, setFileName] = useState<string>("");
47
+ const [isUploading, setIsUploading] = useState(false);
32
48
 
33
49
  const federationContext = useFederationContext();
34
50
  const { error } = useFormField();
@@ -36,6 +52,67 @@ const FileInputForm: React.FC<FileInputFormProps> = ({
36
52
  useEffect(() => {
37
53
  setFileData(value || null);
38
54
  }, [value]);
55
+ const uploadFile = useCallback(
56
+ async (file: File, customName?: string) => {
57
+ if (file.size > MAX_FILE_SIZE) {
58
+ // Handle the case when the file size is exceeded
59
+ federationContext.emitter.emit("message", {
60
+ title: "Velikost souboru byla překročena",
61
+ message: `Maximální povolená velikost je ${MAX_FILE_SIZE / (1024 * 1024)} MB.`,
62
+ classes: "bg-danger ",
63
+ timeout: 0,
64
+ type: "error",
65
+ });
66
+ return;
67
+ }
68
+
69
+ setIsUploading(true);
70
+ const formData = new FormData();
71
+
72
+ // Add the JSON data object
73
+ const dataObject = {
74
+ ...(customName
75
+ ? { attachmentName: customName }
76
+ : attachmentName && { attachmentName }),
77
+ ...(attachmentType && { attachmentType }),
78
+ };
79
+
80
+ formData.append(
81
+ "data",
82
+ new Blob([JSON.stringify(dataObject)], { type: "application/json" })
83
+ );
84
+ formData.append("file", file);
85
+
86
+ try {
87
+ const response = await federationContext.apiClient.post<IAttachment>(
88
+ "/files/upload",
89
+ formData,
90
+ {
91
+ headers: {
92
+ "Content-Type": "multipart/form-data",
93
+ },
94
+ }
95
+ );
96
+ const uploadedFile = response.data;
97
+ setFileData(uploadedFile);
98
+ onFileChanged(uploadedFile);
99
+ setIsFocused(false);
100
+ } catch (error) {
101
+ handleErrors(error as AxiosError, federationContext.emitter);
102
+ console.error("There was an error!", error);
103
+ } finally {
104
+ setIsUploading(false);
105
+ }
106
+ },
107
+ [
108
+ federationContext.apiClient,
109
+ federationContext.emitter,
110
+ onFileChanged,
111
+ attachmentName,
112
+ attachmentType,
113
+ ]
114
+ );
115
+
39
116
  const onDrop = useCallback(
40
117
  async (acceptedFiles: File[]) => {
41
118
  if (acceptedFiles.length > 0) {
@@ -53,52 +130,44 @@ const FileInputForm: React.FC<FileInputFormProps> = ({
53
130
  return;
54
131
  }
55
132
 
56
- const formData = new FormData();
57
-
58
- // Add the JSON data object
59
- const dataObject = {
60
- ...(attachmentName && { attachmentName }),
61
- ...(attachmentType && { attachmentType }),
62
- };
63
-
64
- formData.append(
65
- "data",
66
- new Blob([JSON.stringify(dataObject)], { type: "application/json" })
67
- );
68
- formData.append("file", file);
69
-
70
- try {
71
- const response = await federationContext.apiClient.post<IAttachment>(
72
- "/files/upload",
73
- formData,
74
- {
75
- headers: {
76
- "Content-Type": "multipart/form-data",
77
- },
78
- }
79
- );
80
- const uploadedFile = response.data;
81
- setFileData(uploadedFile);
82
- onFileChanged(uploadedFile);
83
- setIsFocused(false);
84
- } catch (error) {
85
- handleErrors(error as AxiosError, federationContext.emitter);
86
- console.error("There was an error!", error);
133
+ if (askForAttachmentName) {
134
+ setPendingFile(file);
135
+ setFileName(file.name);
136
+ setIsDialogOpen(true);
137
+ } else {
138
+ await uploadFile(file);
87
139
  }
88
140
  }
89
141
  },
90
- [
91
- federationContext.apiClient,
92
- federationContext.emitter,
93
- onFileChanged,
94
- attachmentName,
95
- attachmentType,
96
- ]
142
+ [askForAttachmentName, uploadFile, federationContext]
97
143
  );
98
144
 
145
+ const handleDialogSubmit = async () => {
146
+ if (pendingFile) {
147
+ setIsDialogOpen(false);
148
+ await uploadFile(pendingFile, fileName);
149
+ setPendingFile(null);
150
+ setFileName("");
151
+ }
152
+ };
153
+
154
+ const handleDialogCancel = () => {
155
+ setIsDialogOpen(false);
156
+ setPendingFile(null);
157
+ setFileName("");
158
+ };
159
+
160
+ const formatFileSize = (bytes: number): string => {
161
+ if (bytes === 0) return "0 Bytes";
162
+ const k = 1024;
163
+ const sizes = ["Bytes", "KB", "MB", "GB"];
164
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
165
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
166
+ };
167
+
99
168
  const { getRootProps, getInputProps, isDragActive } = useDropzone({
100
169
  onDrop,
101
- disabled,
170
+ disabled: disabled || isUploading,
102
171
  multiple: false,
103
172
  });
104
173
 
@@ -108,76 +177,153 @@ const FileInputForm: React.FC<FileInputFormProps> = ({
108
177
  };
109
178
 
110
179
  return (
111
- <div className="w-full min-h-30 flex-col justify-start items-start gap-1.5 inline-flex sharedLibrary">
112
- <div className="self-stretch flex-col justify-start items-start gap-1.5 flex">
113
- <div
114
- className={cn(
115
- `self-stretch px-2 py-2 rounded-lg justify-start items-center gap-2 inline-flex outline-none border`,
116
- isFocused &&
180
+ <>
181
+ <div className="w-full min-h-30 flex-col justify-start items-start gap-1.5 inline-flex sharedLibrary">
182
+ <div className="self-stretch flex-col justify-start items-start gap-1.5 flex">
183
+ <div
184
+ className={cn(
185
+ `self-stretch px-2 py-2 rounded-lg justify-start items-center gap-2 inline-flex outline-none border relative`,
186
+ isFocused &&
117
187
  !hasError &&
118
188
  "outline-4 outline-indigo-200 outline-offset-0 border-indigo-300",
119
189
 
120
- hasError &&
190
+ hasError &&
121
191
  "outline-4 outline-red-200 outline-offset-0 border-none",
122
- !isFocused && hasError && "border-red-200 ",
123
- disabled ? "bg-gray-100" : "bg-transparent"
124
- )}
125
- onFocus={() => setIsFocused(true)}
126
- onBlur={() => setIsFocused(false)}
127
- >
128
- <div className="flex relative grow shrink basis-0 min-h-5 lg:min-h-[32px] justify-start items-stretch gap-2 max-w-full ">
129
- {!fileData ? (
130
- <div
131
- {...getRootProps()}
132
- className={`w-full p-4 border-dashed border-2 rounded-lg text-center ${
133
- isDragActive
134
- ? "border-indigo-300 bg-indigo-50"
135
- : "border-gray-300"
136
- }`}
137
- >
138
- <input {...getInputProps()} id={name} />
139
- <p className="text-gray-500">
140
- {isDragActive
141
- ? "Sem přetáhněte soubor"
142
- : "Klikněte pro nahrání, nebo nahrajte přetažením souboru"}
143
- </p>
144
- </div>
145
- ) : (
146
- <div className="w-full flex items-center justify-between">
147
- <div className=" flex">
148
- <MdInsertDriveFile style={{ fontSize: "2rem" }} />
149
- <a
150
- href={`/api/files/download/${fileData.id}`}
151
- className="pl-2 text-left underline text-primary"
152
- target="_blank"
153
- >
154
- {fileData.filename}
155
- </a>
192
+ !isFocused && hasError && "border-red-200 ",
193
+ disabled ? "bg-gray-100" : "bg-transparent"
194
+ )}
195
+ onFocus={() => setIsFocused(true)}
196
+ onBlur={() => setIsFocused(false)}
197
+ >
198
+ {isUploading && (
199
+ <div className="absolute inset-0 bg-white/80 backdrop-blur-sm rounded-lg z-10 flex items-center justify-center">
200
+ <div className="flex flex-col items-center gap-2">
201
+ <LoaderCircleIcon className="h-8 w-8 animate-spin text-primary" />
202
+ <p className="text-sm text-gray-600">Nahrávání...</p>
156
203
  </div>
157
- {!disabled && (
158
- <div
159
- onClick={handleRemove}
160
- className="text-gray-600 cursor-pointer hover:text-primary hover:bg-gray-200 rounded-full ml-4"
161
- >
162
- <MdDeleteOutline
163
- style={{ fontSize: "1.5rem", margin: "15px" }}
164
- />
165
- </div>
166
- )}
167
204
  </div>
168
205
  )}
206
+ <div className="flex relative grow shrink basis-0 min-h-5 lg:min-h-[32px] justify-start items-stretch gap-2 max-w-full ">
207
+ {!fileData ? (
208
+ <div
209
+ {...getRootProps()}
210
+ className={cn(
211
+ `w-full p-4 border-dashed border-2 rounded-lg text-center`,
212
+ isUploading
213
+ ? "cursor-not-allowed opacity-50"
214
+ : "cursor-pointer hover:bg-gray-100",
215
+ isDragActive ? "border-indigo-300 bg-indigo-50" : "border-gray-300"
216
+ )}
217
+ >
218
+ <input {...getInputProps()} id={name} />
219
+ <p className="text-gray-500">
220
+ {isDragActive
221
+ ? "Sem přetáhněte soubor"
222
+ : "Klikněte pro nahrání, nebo nahrajte přetažením souboru"}
223
+ </p>
224
+ </div>
225
+ ) : (
226
+ <div className="w-full flex items-center justify-between py-2 border-b">
227
+ <div className="flex flex-col flex-1">
228
+ <div className="flex items-center gap-2">
229
+ <MdInsertDriveFile style={{ fontSize: "2rem" }} />
230
+ <div className="flex flex-col flex-1">
231
+ <a
232
+ href={`/api/files/download/${fileData.id}`}
233
+ className="text-left underline text-primary text-sm cursor-pointer"
234
+ target="_blank"
235
+ >
236
+ {fileData.filename}
237
+ </a>
238
+ {fileData.attachmentName && (
239
+ <div className="text-sm text-gray-600">
240
+ {fileData.attachmentName}
241
+ </div>
242
+ )}
243
+ </div>
244
+ </div>
245
+ </div>
246
+ {!disabled && !isUploading && (
247
+ <div
248
+ onClick={handleRemove}
249
+ className="text-gray-600 cursor-pointer hover:text-primary hover:bg-gray-200 rounded-full ml-4"
250
+ >
251
+ <MdDeleteOutline
252
+ style={{ fontSize: "1.5rem", margin: "15px" }}
253
+ />
254
+ </div>
255
+ )}
256
+ </div>
257
+ )}
258
+ </div>
169
259
  </div>
170
260
  </div>
261
+ {description && (
262
+ <div
263
+ className="HintText self-stretch text-slate-600 text-sm font-normal leading-tight"
264
+ id={name + ":description"}
265
+ >
266
+ {description}
267
+ </div>
268
+ )}
171
269
  </div>
172
- {description && (
173
- <div
174
- className="HintText self-stretch text-slate-600 text-sm font-normal leading-tight"
175
- id={name + ":description"}
176
- >
177
- {description}
178
- </div>
179
- )}
180
- </div>
270
+
271
+ <Dialog
272
+ open={isDialogOpen}
273
+ onOpenChange={(open) => {
274
+ setIsDialogOpen(open);
275
+ if (!open) {
276
+ handleDialogCancel();
277
+ }
278
+ }}
279
+ >
280
+ <DialogContent className="max-w-lg">
281
+ <DialogHeader>
282
+ <DialogTitle>Zadejte popis přílohy</DialogTitle>
283
+ </DialogHeader>
284
+ {pendingFile && (
285
+ <div className="space-y-4 py-4">
286
+ <div className="flex flex-col gap-2 p-4 border rounded-lg">
287
+ <div className="flex items-center gap-2">
288
+ <MdInsertDriveFile style={{ fontSize: "1.5rem" }} />
289
+ <div className="flex-1">
290
+ <div className="font-medium text-sm">{pendingFile.name}</div>
291
+ <div className="text-xs text-gray-500">
292
+ {formatFileSize(pendingFile.size)}
293
+ </div>
294
+ </div>
295
+ </div>
296
+ <div className="mt-2">
297
+ <label className="text-sm font-medium mb-1 block">
298
+ Popis přílohy:
299
+ </label>
300
+ <Input
301
+ value={fileName}
302
+ onChange={(e) => setFileName(e.target.value)}
303
+ placeholder="Zadejte popis přílohy"
304
+ />
305
+ </div>
306
+ </div>
307
+ </div>
308
+ )}
309
+ <DialogFooter>
310
+ <Button variant="outline" onClick={handleDialogCancel} disabled={isUploading}>
311
+ Zrušit
312
+ </Button>
313
+ <Button onClick={handleDialogSubmit} disabled={isUploading}>
314
+ {isUploading ? (
315
+ <>
316
+ <LoaderCircleIcon className="h-4 w-4 animate-spin mr-2" />
317
+ Nahrávání...
318
+ </>
319
+ ) : (
320
+ "Nahrát"
321
+ )}
322
+ </Button>
323
+ </DialogFooter>
324
+ </DialogContent>
325
+ </Dialog>
326
+ </>
181
327
  );
182
328
  };
183
329