@addsign/moje-agenda-shared-lib 1.0.61 → 2.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/dist/Dialog-IPFWYptw.js +421 -0
- package/dist/Dialog-IPFWYptw.js.map +1 -0
- package/dist/assets/style.css +4374 -0
- package/dist/assets/tailwind.css +1249 -1231
- package/dist/components/Button.js +1 -1
- package/dist/components/datatable/DataTable.js +2 -3
- package/dist/components/datatable/DataTable.js.map +1 -1
- package/dist/components/datatable/DataTableServer.js +2 -3
- package/dist/components/datatable/DataTableServer.js.map +1 -1
- package/dist/components/form/AutocompleteSearchBar.js +2 -3
- package/dist/components/form/AutocompleteSearchBar.js.map +1 -1
- package/dist/components/form/AutocompleteSearchBarServer.js +2 -3
- package/dist/components/form/AutocompleteSearchBarServer.js.map +1 -1
- package/dist/components/form/FileInput.js +2 -3
- package/dist/components/form/FileInput.js.map +1 -1
- package/dist/components/form/FileInputMultiple.js +2 -3
- package/dist/components/form/FileInputMultiple.js.map +1 -1
- package/dist/components/form/FormField.js +2 -3
- package/dist/components/form/FormField.js.map +1 -1
- package/dist/components/form/PositionsSelectorSingle.js +2 -3
- package/dist/components/form/PositionsSelectorSingle.js.map +1 -1
- package/dist/components/form/SelectField.js +2 -3
- package/dist/components/form/SelectField.js.map +1 -1
- package/dist/components/profiles/ProfileOverview.js +2 -3
- package/dist/components/profiles/ProfileOverview.js.map +1 -1
- package/dist/components/ui/Combobox.d.ts +14 -1
- package/dist/components/ui/Combobox.js +122 -98
- package/dist/components/ui/Combobox.js.map +1 -1
- package/dist/components/ui/command.js +1 -1
- package/dist/components/ui/command.js.map +1 -1
- package/dist/index-BF3Kkq7m.js +2212 -0
- package/dist/index-BF3Kkq7m.js.map +1 -0
- package/dist/index-Bm-YZac9.js +28864 -0
- package/dist/index-Bm-YZac9.js.map +1 -0
- package/dist/index-C4pkvuV-.js +1395 -0
- package/dist/index-C4pkvuV-.js.map +1 -0
- package/dist/index-M6rMtqtn.js +2203 -0
- package/dist/index-M6rMtqtn.js.map +1 -0
- package/dist/index.es-XWsfbnWC.js +9628 -0
- package/dist/index.es-XWsfbnWC.js.map +1 -0
- package/dist/jspdf.plugin.autotable-DXW3wQb-.js +11475 -0
- package/dist/jspdf.plugin.autotable-DXW3wQb-.js.map +1 -0
- package/dist/main.js +2 -5
- package/dist/main.js.map +1 -1
- package/dist/tailwind-l0sNRNKZ.js +2 -0
- package/dist/tailwind-l0sNRNKZ.js.map +1 -0
- package/lib/components/Button.tsx +57 -0
- package/lib/components/Calendar.tsx +242 -0
- package/lib/components/ConfirmationModalDialog.tsx +115 -0
- package/lib/components/Modal.tsx +73 -0
- package/lib/components/ModalDialog.tsx +63 -0
- package/lib/components/Spinner.tsx +25 -0
- package/lib/components/SpinnerIcon.tsx +12 -0
- package/lib/components/datatable/DataTable.tsx +442 -0
- package/lib/components/datatable/DataTableServer.tsx +939 -0
- package/lib/components/datatable/DatatableSettings.tsx +48 -0
- package/lib/components/datatable/Resizable.tsx +99 -0
- package/lib/components/datatable/types.ts +33 -0
- package/lib/components/form/AutocompleteSearchBar.tsx +424 -0
- package/lib/components/form/AutocompleteSearchBarServer.tsx +257 -0
- package/lib/components/form/DateField.tsx +124 -0
- package/lib/components/form/DateRangeField.tsx +116 -0
- package/lib/components/form/FileInput.tsx +188 -0
- package/lib/components/form/FileInputMultiple.tsx +186 -0
- package/lib/components/form/FormField.tsx +371 -0
- package/lib/components/form/InputField.tsx +230 -0
- package/lib/components/form/PositionsSelectorSingle.tsx +266 -0
- package/lib/components/form/RadioGroup.tsx +64 -0
- package/lib/components/form/SelectField.tsx +267 -0
- package/lib/components/layout/IconInCircle.tsx +29 -0
- package/lib/components/layout/PageTitle.tsx +19 -0
- package/lib/components/layout/SectionTitle.tsx +22 -0
- package/lib/components/profiles/ProfileOverview.tsx +212 -0
- package/lib/components/ui/Calendar.tsx +68 -0
- package/lib/components/ui/Combobox.tsx +175 -0
- package/lib/components/ui/DatePicker.tsx +124 -0
- package/lib/components/ui/DateTimePicker.tsx +187 -0
- package/lib/components/ui/Dialog.tsx +118 -0
- package/lib/components/ui/ScrollArea.tsx +45 -0
- package/lib/components/ui/button.tsx +56 -0
- package/lib/components/ui/command.tsx +153 -0
- package/lib/components/ui/form.tsx +177 -0
- package/lib/components/ui/input.tsx +22 -0
- package/lib/components/ui/label.tsx +24 -0
- package/lib/components/ui/popover.tsx +31 -0
- package/lib/components/ui/radioGroup.tsx +44 -0
- package/lib/components/ui/select.tsx +158 -0
- package/lib/contexts/FederationContext.tsx +28 -0
- package/lib/contexts/useFederationContext.ts +4 -0
- package/lib/css/tailwind.css +10 -0
- package/lib/fonts/arial.ts +3 -0
- package/lib/fonts/arialBold.ts +4 -0
- package/lib/main.ts +64 -0
- package/lib/types.ts +492 -0
- package/lib/utils/PdfManager.ts +224 -0
- package/lib/utils/getFullName.tsx +83 -0
- package/lib/utils/getIntersectingDays.ts +28 -0
- package/lib/utils/handleErrors.ts +28 -0
- package/lib/utils/hasRightInModule.ts +17 -0
- package/lib/utils/hasRole.ts +12 -0
- package/lib/utils/utils.ts +6 -0
- package/lib/vite-env.d.ts +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { IFormFieldGlobalProps, IOptionItem } from "../../types";
|
|
3
|
+
import { MdCheck, MdClose, MdExpandLess, MdExpandMore } from "react-icons/md";
|
|
4
|
+
import { useClickAway } from "react-use";
|
|
5
|
+
import { SpinnerIcon, useFederationContext } from "../../main";
|
|
6
|
+
import { FaSpinner } from "react-icons/fa";
|
|
7
|
+
|
|
8
|
+
export interface ISelectFieldProps extends IFormFieldGlobalProps {
|
|
9
|
+
options?: IOptionItem[];
|
|
10
|
+
valueKey?: string;
|
|
11
|
+
labelKey?: string | ((item: any) => string);
|
|
12
|
+
fetchUrl?: string;
|
|
13
|
+
loading?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function SelectField({
|
|
17
|
+
label,
|
|
18
|
+
name,
|
|
19
|
+
value,
|
|
20
|
+
description,
|
|
21
|
+
onInputChange,
|
|
22
|
+
options,
|
|
23
|
+
placeholder,
|
|
24
|
+
className,
|
|
25
|
+
errors = {},
|
|
26
|
+
clearable,
|
|
27
|
+
required,
|
|
28
|
+
disabled,
|
|
29
|
+
rounded = true,
|
|
30
|
+
fetchUrl,
|
|
31
|
+
valueKey,
|
|
32
|
+
labelKey,
|
|
33
|
+
loading,
|
|
34
|
+
}: ISelectFieldProps) {
|
|
35
|
+
const [isFocused, setIsFocused] = React.useState(false);
|
|
36
|
+
const [isLoading, setIsLoading] = React.useState(false);
|
|
37
|
+
const apiClient = useFederationContext()?.apiClient;
|
|
38
|
+
const [localOptions, setLocalOptions] = React.useState(options);
|
|
39
|
+
|
|
40
|
+
const ref = React.useRef(null);
|
|
41
|
+
const handleClear = (e: any) => {
|
|
42
|
+
// value;
|
|
43
|
+
e.stopPropagation(); // Add this line
|
|
44
|
+
|
|
45
|
+
setIsFocused(false);
|
|
46
|
+
onInputChange({
|
|
47
|
+
...e,
|
|
48
|
+
target: {
|
|
49
|
+
value: "",
|
|
50
|
+
name: name,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
const fetchOptions = async (fetchUrl: string) => {
|
|
57
|
+
setIsLoading(true);
|
|
58
|
+
const { data } = await apiClient.get(fetchUrl);
|
|
59
|
+
|
|
60
|
+
// Check if the first item in the data array is a number to determine the data type
|
|
61
|
+
const isArrayOfNumbers = typeof data[0] === "number";
|
|
62
|
+
const isInPageable = data && data.hasOwnProperty("content");
|
|
63
|
+
|
|
64
|
+
// Transform data based on its type
|
|
65
|
+
const transformedOptions = (isInPageable ? data.content : data).map(
|
|
66
|
+
(item: any) => {
|
|
67
|
+
if (isArrayOfNumbers) {
|
|
68
|
+
// If it's a number, use the number for both value and label
|
|
69
|
+
return { value: item, label: item.toString() };
|
|
70
|
+
} else {
|
|
71
|
+
// Otherwise, extract using predefined keys or defaults
|
|
72
|
+
return {
|
|
73
|
+
value: item[valueKey || "id"],
|
|
74
|
+
label:
|
|
75
|
+
labelKey instanceof Function && labelKey !== undefined
|
|
76
|
+
? labelKey(item)
|
|
77
|
+
: item[labelKey as string],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
setLocalOptions([
|
|
84
|
+
{ value: null, label: " " }, // Add an empty option as the first item
|
|
85
|
+
...transformedOptions,
|
|
86
|
+
]);
|
|
87
|
+
setIsLoading(false);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (fetchUrl) fetchOptions(fetchUrl);
|
|
91
|
+
if (options) setLocalOptions(options);
|
|
92
|
+
}, [fetchUrl, options, apiClient, valueKey, labelKey]); // ensure valueKey and labelKey are also in the dependency array if they are dynamic
|
|
93
|
+
|
|
94
|
+
const handleToggleFocus = () => {
|
|
95
|
+
setIsFocused((prev) => !prev);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
useClickAway(ref, () => {
|
|
99
|
+
setIsFocused(false);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const hangleChange = (option: IOptionItem) => {
|
|
103
|
+
const tmp: any = {
|
|
104
|
+
target: {
|
|
105
|
+
value: option.value,
|
|
106
|
+
name: name,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
onInputChange(tmp);
|
|
111
|
+
setIsFocused(false);
|
|
112
|
+
};
|
|
113
|
+
const currentlySelectedOption = React.useMemo(() => {
|
|
114
|
+
if (value === undefined || value === "") return null;
|
|
115
|
+
return localOptions?.find((option) => option.value == value);
|
|
116
|
+
}, [localOptions, value, options]);
|
|
117
|
+
|
|
118
|
+
const listOfOptions = () => {
|
|
119
|
+
if (!localOptions) return [];
|
|
120
|
+
return (
|
|
121
|
+
<div
|
|
122
|
+
id="list"
|
|
123
|
+
className="max-h-[390px] min-w-20 w-auto whitespace-nowrap absolute z-[100000] -top-[1px] mt-[4px] bg-white rounded-lg shadow-xl border
|
|
124
|
+
border-gray-200 justify-start items-start inline-flex overflow-auto cursor-default"
|
|
125
|
+
>
|
|
126
|
+
<div className="grow shrink basis-0 py-1 flex-col justify-start items-start inline-flex">
|
|
127
|
+
{localOptions.map((option, index) => {
|
|
128
|
+
return (
|
|
129
|
+
<div
|
|
130
|
+
className="group self-stretch px-1.5 py-px justify-start items-center inline-flex hover:bg-gray-50 "
|
|
131
|
+
onClick={() => hangleChange(option)}
|
|
132
|
+
key={index}
|
|
133
|
+
>
|
|
134
|
+
<div className="grow shrink basis-0 px-1 py-2 rounded-md flex-col justify-start items-start gap-2 inline-flex">
|
|
135
|
+
<div className="self-stretch justify-start items-center gap-2 inline-flex">
|
|
136
|
+
<div className="text-gray-900 text-sm font-normal leading-normal">
|
|
137
|
+
{option.label}
|
|
138
|
+
</div>
|
|
139
|
+
{option.description && (
|
|
140
|
+
<div className="text-slate-600 sm-base font-normal leading-normal">
|
|
141
|
+
{option.description}
|
|
142
|
+
</div>
|
|
143
|
+
)}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
<div className="w-5 h-5 relative">
|
|
147
|
+
{option.value === value && value !== "" && (
|
|
148
|
+
<MdCheck size={22} className="text-primary" />
|
|
149
|
+
)}{" "}
|
|
150
|
+
{option.value !== value && (
|
|
151
|
+
<MdCheck
|
|
152
|
+
size={22}
|
|
153
|
+
className="text-transparent group-hover:text-gray-300"
|
|
154
|
+
/>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
);
|
|
159
|
+
})}
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<>
|
|
167
|
+
<div
|
|
168
|
+
className={
|
|
169
|
+
"w-full min-h-30 flex-col justify-start items-start gap-0inline-flex " +
|
|
170
|
+
className
|
|
171
|
+
}
|
|
172
|
+
id="selectField"
|
|
173
|
+
ref={ref}
|
|
174
|
+
>
|
|
175
|
+
<div className="self-stretch flex-col justify-start items-start gap-1.5 flex">
|
|
176
|
+
{label && (
|
|
177
|
+
<label
|
|
178
|
+
className="text-slate-700 text-sm font-medium leading-tight"
|
|
179
|
+
htmlFor={name}
|
|
180
|
+
>
|
|
181
|
+
{label} {required ? "*" : ""}
|
|
182
|
+
</label>
|
|
183
|
+
)}
|
|
184
|
+
<div
|
|
185
|
+
className={`self-stretch w-full px-3 py-1 bg-white border justify-start items-center gap-0 inline-flex outline-none
|
|
186
|
+
${isFocused ? "outline-4 outline-indigo-200 outline-offset-0 border-indigo-300 " : ""}
|
|
187
|
+
${
|
|
188
|
+
isFocused && errors[name]?.message
|
|
189
|
+
? "outline-4 outline-red-200 outline-offset-0 border-none"
|
|
190
|
+
: ""
|
|
191
|
+
}
|
|
192
|
+
${rounded ? " rounded-lg " : " rounded-none "}
|
|
193
|
+
${!isFocused && errors[name]?.message ? "border-red-200" : ""}
|
|
194
|
+
${disabled || isLoading ? "opacity-80 cursor-not-allowed " : ""}
|
|
195
|
+
`}
|
|
196
|
+
onClick={() =>
|
|
197
|
+
!disabled && !isLoading ? handleToggleFocus() : null
|
|
198
|
+
}
|
|
199
|
+
>
|
|
200
|
+
<div className="grow shrink basis-0 min-h-[32px] justify-start items-center gap-0 flex whitespace-nowrap w-[calc(100%-40px)] ">
|
|
201
|
+
<div
|
|
202
|
+
className="text-gray-900 text-sm font-normal leading-normal text-ellipsis overflow-hidden w-full"
|
|
203
|
+
id={name}
|
|
204
|
+
>
|
|
205
|
+
{currentlySelectedOption?.label}
|
|
206
|
+
{!currentlySelectedOption?.label && placeholder && (
|
|
207
|
+
<span className="text-slate-400 font-normal">
|
|
208
|
+
{" "}
|
|
209
|
+
{placeholder}
|
|
210
|
+
</span>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
<div className="text-slate-600 text-base font-normal leading-normal">
|
|
214
|
+
{currentlySelectedOption?.description}
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
217
|
+
{clearable &&
|
|
218
|
+
value !== "" &&
|
|
219
|
+
value !== null &&
|
|
220
|
+
value !== undefined &&
|
|
221
|
+
!disabled && (
|
|
222
|
+
<div
|
|
223
|
+
className="w-6 h-6 relative cursor-pointer "
|
|
224
|
+
id={name + ":clear"}
|
|
225
|
+
onClick={handleClear}
|
|
226
|
+
>
|
|
227
|
+
<div className="absolute inset-0 flex items-center justify-center hover:bg-gray-100 w-6 rounded-full text-lg">
|
|
228
|
+
<MdClose />
|
|
229
|
+
</div>{" "}
|
|
230
|
+
</div>
|
|
231
|
+
)}
|
|
232
|
+
{isLoading === true ||
|
|
233
|
+
(loading && (
|
|
234
|
+
<div className="w-6 h-6 relative flex items-center justify-center align-middle">
|
|
235
|
+
<SpinnerIcon icon={<FaSpinner />} />
|
|
236
|
+
</div>
|
|
237
|
+
))}
|
|
238
|
+
|
|
239
|
+
<div className="w-6 h-6 relative cursor-pointer ">
|
|
240
|
+
<div className="absolute inset-0 flex items-center justify-center hover:bg-gray-100 w-6 rounded-full text-lg">
|
|
241
|
+
{isFocused && !disabled && <MdExpandLess />}
|
|
242
|
+
{!isFocused && !disabled && <MdExpandMore />}
|
|
243
|
+
</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
</div>{" "}
|
|
247
|
+
<div className="w-full relative">{isFocused && listOfOptions()}</div>
|
|
248
|
+
{description && !isFocused && (
|
|
249
|
+
<div
|
|
250
|
+
className="self-stretch text-slate-600 text-sm font-normal leading-tight"
|
|
251
|
+
id={name + ":description"}
|
|
252
|
+
>
|
|
253
|
+
{description}
|
|
254
|
+
</div>
|
|
255
|
+
)}
|
|
256
|
+
{errors[name] && (
|
|
257
|
+
<div
|
|
258
|
+
className="HintText self-stretch text-red-600 text-sm font-normal leading-tight"
|
|
259
|
+
id={name + ":error"}
|
|
260
|
+
>
|
|
261
|
+
{errors[name]?.message}
|
|
262
|
+
</div>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
</>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { MdCheck, MdOutlinePending } from "react-icons/md";
|
|
3
|
+
|
|
4
|
+
interface IconInCircleProps {
|
|
5
|
+
isComplete: boolean;
|
|
6
|
+
isPending?: boolean;
|
|
7
|
+
size?: number;
|
|
8
|
+
title?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const IconInCircle: React.FC<IconInCircleProps> = ({
|
|
12
|
+
isComplete,
|
|
13
|
+
size = 20,
|
|
14
|
+
title,
|
|
15
|
+
isPending,
|
|
16
|
+
}) => {
|
|
17
|
+
if (!isComplete && !isPending) return "";
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
className="p-2 rounded-full border text-success flex items-center justify-center text-2xl "
|
|
21
|
+
style={{ width: 2 * size, height: 2 * size }}
|
|
22
|
+
title={title || ""}
|
|
23
|
+
>
|
|
24
|
+
{isPending ? <MdOutlinePending size={size} /> : <MdCheck size={size} />}
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default IconInCircle;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface IPageTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
|
|
2
|
+
textSize?: string;
|
|
3
|
+
children?: React.ReactNode;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export default function PageTitle({
|
|
7
|
+
children,
|
|
8
|
+
textSize = "text-2xl",
|
|
9
|
+
...props
|
|
10
|
+
}: IPageTitleProps) {
|
|
11
|
+
// Combine textSize with the other fixed class names
|
|
12
|
+
const className = ` text-gray-900 ${textSize} font-medium font-['Inter'] leading-7 mb-5`;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<h1 {...props} className={className}>
|
|
16
|
+
{children}
|
|
17
|
+
</h1>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
interface ISectionTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
|
|
4
|
+
textSize?: string;
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
}
|
|
7
|
+
export default function SectionTitle({
|
|
8
|
+
children,
|
|
9
|
+
textSize = "text-lg",
|
|
10
|
+
...props
|
|
11
|
+
}: ISectionTitleProps) {
|
|
12
|
+
// Combine textSize with the other fixed class names
|
|
13
|
+
const className = ` text-gray-900 ${textSize} font-semibold font-['Inter'] leading-7 mb-3`;
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<div className="border-b border-gray-300 pt-2 mb-5 ">
|
|
17
|
+
<h2 {...props} className={className}>
|
|
18
|
+
{children}
|
|
19
|
+
</h2>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { useEffect, useMemo, useState } from "react";
|
|
2
|
+
import SectionTitle from "../layout/SectionTitle";
|
|
3
|
+
import {
|
|
4
|
+
EApproverRoles,
|
|
5
|
+
IProfileApprover,
|
|
6
|
+
IStep,
|
|
7
|
+
getFullName,
|
|
8
|
+
handleErrors,
|
|
9
|
+
useFederationContext,
|
|
10
|
+
} from "../../main";
|
|
11
|
+
import { AxiosResponse } from "axios";
|
|
12
|
+
import IconInCircle from "../layout/IconInCircle";
|
|
13
|
+
|
|
14
|
+
interface ProfileOverviewProps {
|
|
15
|
+
employeeId?: number;
|
|
16
|
+
onProfileApproversChange?: (data: IProfileApprover[]) => void;
|
|
17
|
+
steps: IStep[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface IApproverRoleGroup {
|
|
21
|
+
name: string;
|
|
22
|
+
roles: EApproverRoles[];
|
|
23
|
+
all: boolean;
|
|
24
|
+
parallel: boolean;
|
|
25
|
+
stepType: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const approverRoleGroups: IApproverRoleGroup[] = [
|
|
29
|
+
{
|
|
30
|
+
name: "Předschvalovatelé",
|
|
31
|
+
roles: [EApproverRoles.predschvalovatel],
|
|
32
|
+
all: true,
|
|
33
|
+
parallel: true,
|
|
34
|
+
stepType: 1,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "Schvalovatelé",
|
|
38
|
+
roles: [
|
|
39
|
+
EApproverRoles.vedouci,
|
|
40
|
+
EApproverRoles.zastupce,
|
|
41
|
+
EApproverRoles.zastupce9,
|
|
42
|
+
EApproverRoles.vyssiSchvalovatel,
|
|
43
|
+
EApproverRoles.vyssiSchvalovatelZastupce,
|
|
44
|
+
EApproverRoles.vyssiSchvalovatelZastupce9,
|
|
45
|
+
],
|
|
46
|
+
all: false,
|
|
47
|
+
parallel: true,
|
|
48
|
+
stepType: 2,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "Docházkový vedoucí",
|
|
52
|
+
roles: [EApproverRoles.dochazkovyVedouci],
|
|
53
|
+
all: true,
|
|
54
|
+
parallel: false,
|
|
55
|
+
stepType: 3,
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
export default function ProfileOverview({
|
|
60
|
+
employeeId,
|
|
61
|
+
onProfileApproversChange,
|
|
62
|
+
steps,
|
|
63
|
+
}: ProfileOverviewProps) {
|
|
64
|
+
const [profileApprovers, setProfileApprovers] = useState<
|
|
65
|
+
Array<IProfileApprover>
|
|
66
|
+
>([]);
|
|
67
|
+
const context = useFederationContext();
|
|
68
|
+
|
|
69
|
+
const currentApprovers = useMemo(() => {
|
|
70
|
+
return approverRoleGroups.map((group) => {
|
|
71
|
+
const groupApprovers = profileApprovers.filter((approver) =>
|
|
72
|
+
group.roles.includes(approver.role)
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const groupApprovedSteps = steps.filter(
|
|
76
|
+
(step) =>
|
|
77
|
+
step.type == group.stepType &&
|
|
78
|
+
groupApprovers.some(
|
|
79
|
+
(approver) =>
|
|
80
|
+
step.employee.employeeId == approver?.approver?.employeeId
|
|
81
|
+
)
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
...group,
|
|
86
|
+
numberOfApprovers: groupApprovers.length,
|
|
87
|
+
numberOfApprovedSteps: groupApprovedSteps.length,
|
|
88
|
+
isComplete:
|
|
89
|
+
(group.all && groupApprovers.length == groupApprovedSteps.length) ||
|
|
90
|
+
(!group.all && groupApprovedSteps.length > 0),
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
}, [profileApprovers, steps]);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
context.apiClient
|
|
97
|
+
.get<IProfileApprover[]>(
|
|
98
|
+
"/timeoff/approve-profile" + (employeeId ? "/" + employeeId : "")
|
|
99
|
+
)
|
|
100
|
+
.then((response: AxiosResponse) => {
|
|
101
|
+
setProfileApprovers(response.data);
|
|
102
|
+
})
|
|
103
|
+
.catch((error) => {
|
|
104
|
+
handleErrors(
|
|
105
|
+
error,
|
|
106
|
+
context.emitter,
|
|
107
|
+
"Nepodařilo se dohledat schvalovací profil pro uživatele " +
|
|
108
|
+
employeeId
|
|
109
|
+
);
|
|
110
|
+
console.error("Error submitting form:", error);
|
|
111
|
+
});
|
|
112
|
+
}, [employeeId, context.emitter, context.apiClient]);
|
|
113
|
+
|
|
114
|
+
const decisionsTranslations = (decision: string): string | null => {
|
|
115
|
+
const translations: { [key: string]: string } = {
|
|
116
|
+
approved: "Schváleno",
|
|
117
|
+
rejected: "Zamítnuto",
|
|
118
|
+
cancelled: "Stornováno",
|
|
119
|
+
evided: "Zaevidováno",
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return translations[decision] || null;
|
|
123
|
+
};
|
|
124
|
+
// pri nacteni profileApprovers se muze dat vedet parent componente inbfo o aktualnich schvalovatelich
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (onProfileApproversChange) onProfileApproversChange(profileApprovers);
|
|
127
|
+
}, [profileApprovers, onProfileApproversChange]);
|
|
128
|
+
|
|
129
|
+
const findStep = (
|
|
130
|
+
steps: IStep[],
|
|
131
|
+
group: IApproverRoleGroup,
|
|
132
|
+
row: IProfileApprover
|
|
133
|
+
) => {
|
|
134
|
+
return steps.find(
|
|
135
|
+
(step) =>
|
|
136
|
+
step.type == group.stepType &&
|
|
137
|
+
step.employee.employeeId == row.position?.employee?.employeeId
|
|
138
|
+
);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<>
|
|
143
|
+
<SectionTitle>Postup schvalování</SectionTitle>
|
|
144
|
+
<div className="flex gap-4">
|
|
145
|
+
{currentApprovers.map((group, index) => (
|
|
146
|
+
<div
|
|
147
|
+
key={group.name}
|
|
148
|
+
className={
|
|
149
|
+
"w-full lg:w-1/3 rounded-lg shadow-xl p-5 border gap-4 flex flex-col "
|
|
150
|
+
}
|
|
151
|
+
>
|
|
152
|
+
<div className="flex justify-between">
|
|
153
|
+
<h3 className="text-xl font-bold">
|
|
154
|
+
{group.name} - {index}
|
|
155
|
+
</h3>
|
|
156
|
+
|
|
157
|
+
<IconInCircle
|
|
158
|
+
isComplete={group.isComplete}
|
|
159
|
+
title={
|
|
160
|
+
group.numberOfApprovedSteps + " / " + group.numberOfApprovers
|
|
161
|
+
}
|
|
162
|
+
isPending={!group.isComplete}
|
|
163
|
+
></IconInCircle>
|
|
164
|
+
</div>
|
|
165
|
+
{profileApprovers
|
|
166
|
+
.filter((it) => group.roles.includes(it.role))
|
|
167
|
+
.map((row, index) => (
|
|
168
|
+
<div
|
|
169
|
+
key={index}
|
|
170
|
+
className=" border rounded-lg p-4 flex justify-between gap-5"
|
|
171
|
+
>
|
|
172
|
+
<div>
|
|
173
|
+
<h3 className="">
|
|
174
|
+
{getFullName(row.position?.employee, true)}
|
|
175
|
+
</h3>
|
|
176
|
+
<p className="text-sm ">{row.roleTxt}</p>
|
|
177
|
+
{findStep(steps, group, row) && (
|
|
178
|
+
<>
|
|
179
|
+
<p className="text-sm "></p>{" "}
|
|
180
|
+
{findStep(steps, group, row)?.decision && (
|
|
181
|
+
<p className="text-sm ">
|
|
182
|
+
{decisionsTranslations(
|
|
183
|
+
findStep(steps, group, row)?.decision || ""
|
|
184
|
+
)}{" "}
|
|
185
|
+
(
|
|
186
|
+
{new Date(
|
|
187
|
+
findStep(steps, group, row)?.date || 0
|
|
188
|
+
).toLocaleString()}
|
|
189
|
+
)
|
|
190
|
+
</p>
|
|
191
|
+
)}
|
|
192
|
+
{findStep(steps, group, row)?.comment && (
|
|
193
|
+
<p className="text-sm ">
|
|
194
|
+
<b>Komentář</b>:{" "}
|
|
195
|
+
{findStep(steps, group, row)?.comment}{" "}
|
|
196
|
+
</p>
|
|
197
|
+
)}
|
|
198
|
+
</>
|
|
199
|
+
)}
|
|
200
|
+
</div>
|
|
201
|
+
<IconInCircle
|
|
202
|
+
isComplete={findStep(steps, group, row) != null}
|
|
203
|
+
isPending={!group.isComplete}
|
|
204
|
+
/>
|
|
205
|
+
</div>
|
|
206
|
+
))}
|
|
207
|
+
</div>
|
|
208
|
+
))}
|
|
209
|
+
</div>
|
|
210
|
+
</>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
5
|
+
import { DayPicker } from "react-day-picker";
|
|
6
|
+
|
|
7
|
+
import { cs } from "date-fns/locale";
|
|
8
|
+
import { cn } from "../../utils/utils";
|
|
9
|
+
import { buttonVariants } from "./button";
|
|
10
|
+
|
|
11
|
+
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
|
12
|
+
|
|
13
|
+
function Calendar({
|
|
14
|
+
className,
|
|
15
|
+
classNames,
|
|
16
|
+
showOutsideDays = true,
|
|
17
|
+
...props
|
|
18
|
+
}: CalendarProps) {
|
|
19
|
+
return (
|
|
20
|
+
<DayPicker
|
|
21
|
+
locale={cs}
|
|
22
|
+
showOutsideDays={showOutsideDays}
|
|
23
|
+
className={cn("p-3", className)}
|
|
24
|
+
classNames={{
|
|
25
|
+
months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
|
|
26
|
+
month: "space-y-4",
|
|
27
|
+
caption: "flex justify-center pt-1 relative items-center",
|
|
28
|
+
caption_label: "text-sm font-medium",
|
|
29
|
+
nav: "space-x-1 flex items-center",
|
|
30
|
+
nav_button: cn(
|
|
31
|
+
buttonVariants({ variant: "outline" }),
|
|
32
|
+
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
|
|
33
|
+
),
|
|
34
|
+
nav_button_previous: "absolute left-1",
|
|
35
|
+
nav_button_next: "absolute right-1",
|
|
36
|
+
table: "w-full border-collapse space-y-1",
|
|
37
|
+
head_row: "flex",
|
|
38
|
+
head_cell:
|
|
39
|
+
"text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
|
|
40
|
+
row: "flex w-full mt-2",
|
|
41
|
+
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
|
|
42
|
+
day: cn(
|
|
43
|
+
buttonVariants({ variant: "ghost" }),
|
|
44
|
+
"h-9 w-9 p-0 font-normal aria-selected:opacity-100"
|
|
45
|
+
),
|
|
46
|
+
day_range_end: "day-range-end",
|
|
47
|
+
day_selected:
|
|
48
|
+
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
|
|
49
|
+
day_today: "bg-accent text-accent-foreground",
|
|
50
|
+
day_outside:
|
|
51
|
+
"day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground",
|
|
52
|
+
day_disabled: "text-muted-foreground opacity-50",
|
|
53
|
+
day_range_middle:
|
|
54
|
+
"aria-selected:bg-accent aria-selected:text-accent-foreground",
|
|
55
|
+
day_hidden: "invisible",
|
|
56
|
+
...classNames,
|
|
57
|
+
}}
|
|
58
|
+
components={{
|
|
59
|
+
IconLeft: () => <ChevronLeft className="h-4 w-4" />,
|
|
60
|
+
IconRight: () => <ChevronRight className="h-4 w-4" />,
|
|
61
|
+
}}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
Calendar.displayName = "Calendar";
|
|
67
|
+
|
|
68
|
+
export { Calendar };
|