ptechcore_ui 1.0.27 → 1.0.30

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/index.js CHANGED
@@ -11,9 +11,9 @@ var PrimaryButton = ({
11
11
  {
12
12
  type: "submit",
13
13
  disabled: loading || props.disabled,
14
- className: `px-4 py-2 text-sm rounded-lg hover:bg-opacity-80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex justify-center items-center ${classname} ${variant === "full" ? "bg-[#6A8A82] text-white" : variant === "outline" ? "border border-[#6A8A82] text-[#6A8A82] bg-transparent" : "bg-transparent text-[#6A8A82]"}`,
14
+ className: `px-4 py-2 text-sm rounded-lg hover:opacity-80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex justify-center items-center ${classname} ${variant === "full" ? "bg-[var(--color-primary)] text-[var(--color-text-inverse)]" : variant === "outline" ? "border border-[var(--color-primary)] text-[var(--color-primary)] bg-transparent" : "bg-transparent text-[var(--color-primary)]"}`,
15
15
  ...props,
16
- children: loading ? "Connexion en cours..." : children
16
+ children: loading ? "Chargement..." : children
17
17
  }
18
18
  );
19
19
  var SecondaryButton = ({
@@ -26,9 +26,9 @@ var SecondaryButton = ({
26
26
  {
27
27
  type: "button",
28
28
  disabled: loading || props.disabled,
29
- className: `px-4 py-2 rounded-lg hover:bg-opacity-80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center ${variant === "full" ? "bg-[#B87333] text-white" : variant === "outline" ? "border border-[#B87333] text-[#B87333] bg-transparent" : "bg-transparent text-[#B87333]"}`,
29
+ className: `px-4 py-2 text-sm rounded-lg hover:opacity-80 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center ${variant === "full" ? "bg-[var(--color-secondary)] text-[var(--color-text-inverse)]" : variant === "outline" ? "border border-[var(--color-secondary)] text-[var(--color-secondary)] bg-transparent" : "bg-transparent text-[var(--color-secondary)]"}`,
30
30
  ...props,
31
- children: loading ? "Connexion en cours..." : children
31
+ children: loading ? "Chargement..." : children
32
32
  }
33
33
  );
34
34
  var Buttons_default = PrimaryButton;
@@ -36,7 +36,7 @@ var Buttons_default = PrimaryButton;
36
36
  // src/components/common/Modals.tsx
37
37
  import { createPortal } from "react-dom";
38
38
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
39
- var Modal = ({ title, description, width = "max-w-md", open, onClose, children }) => {
39
+ var Modal = ({ title, description, width = "max-w-md", minContentHeight, open, onClose, children }) => {
40
40
  if (!open) return null;
41
41
  const modalContent = /* @__PURE__ */ jsx2("div", { className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[9999]", children: /* @__PURE__ */ jsxs("div", { className: `bg-[var(--color-surface)] rounded-lg p-6 mx-4 w-full ${width}`, children: [
42
42
  /* @__PURE__ */ jsxs("div", { className: "flex justify-between items-start mb-6", children: [
@@ -54,7 +54,14 @@ var Modal = ({ title, description, width = "max-w-md", open, onClose, children }
54
54
  }
55
55
  )
56
56
  ] }),
57
- /* @__PURE__ */ jsx2("div", { className: "w-full max-h-[80vh] overflow-y-auto", children })
57
+ /* @__PURE__ */ jsx2(
58
+ "div",
59
+ {
60
+ className: "w-full max-h-[80vh] overflow-y-auto",
61
+ style: minContentHeight ? { minHeight: minContentHeight } : void 0,
62
+ children
63
+ }
64
+ )
58
65
  ] }) });
59
66
  return createPortal(modalContent, document.body);
60
67
  };
@@ -76,10 +83,10 @@ var InputField = ({
76
83
  onBlur
77
84
  }) => {
78
85
  return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col gap-1 w-full", children: [
79
- label && /* @__PURE__ */ jsxs2("label", { htmlFor: name, className: "block text-gray-700 text-sm font-medium mb-2", children: [
86
+ label && /* @__PURE__ */ jsxs2("label", { htmlFor: name, className: "block text-[var(--color-text-secondary)] text-sm font-medium mb-2", children: [
80
87
  label,
81
88
  " ",
82
- required && /* @__PURE__ */ jsx3("span", { className: "text-red-500", children: "*" })
89
+ required && /* @__PURE__ */ jsx3("span", { className: "text-[var(--color-error)]", children: "*" })
83
90
  ] }),
84
91
  /* @__PURE__ */ jsx3(
85
92
  "input",
@@ -93,13 +100,15 @@ var InputField = ({
93
100
  disabled,
94
101
  onChange,
95
102
  onBlur,
96
- className: `w-full px-3 py-2 border border-[#D9D9D9] focus:ring-2 focus:ring-[#6A8A82]/20
97
- ${error ? "border-red-500" : "border-gray-300"}
98
- ${disabled ? "bg-gray-100 cursor-not-allowed" : ""}
103
+ className: `w-full px-3 py-2 border rounded-lg text-sm
104
+ bg-[var(--color-surface)] text-[var(--color-text-primary)]
105
+ focus:ring-2 focus:ring-[var(--color-primary)]/20 focus:border-[var(--color-primary)] focus:outline-none
106
+ ${error ? "border-[var(--color-error)]" : "border-[var(--color-border)]"}
107
+ ${disabled ? "bg-[var(--color-background)] cursor-not-allowed opacity-60" : ""}
99
108
  `
100
109
  }
101
110
  ),
102
- error && /* @__PURE__ */ jsx3("p", { className: "text-xs text-red-500", children: error })
111
+ error && /* @__PURE__ */ jsx3("p", { className: "text-xs text-[var(--color-error)]", children: error })
103
112
  ] });
104
113
  };
105
114
  var TextInput = (props) => {
@@ -129,11 +138,11 @@ var SelectInput = ({
129
138
  "label",
130
139
  {
131
140
  htmlFor: name,
132
- className: "block text-gray-700 text-sm font-medium mb-2",
141
+ className: "block text-[var(--color-text-secondary)] text-sm font-medium mb-2",
133
142
  children: [
134
143
  label,
135
144
  " ",
136
- required && /* @__PURE__ */ jsx3("span", { className: "text-red-500", children: "*" })
145
+ required && /* @__PURE__ */ jsx3("span", { className: "text-[var(--color-error)]", children: "*" })
137
146
  ]
138
147
  }
139
148
  ),
@@ -148,9 +157,10 @@ var SelectInput = ({
148
157
  onChange,
149
158
  onBlur,
150
159
  className: `w-full px-4 py-2 border rounded-lg text-sm
151
- focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent
152
- ${error ? "border-red-500" : "border-gray-300"}
153
- ${disabled ? "bg-gray-100 cursor-not-allowed" : ""}
160
+ bg-[var(--color-surface)] text-[var(--color-text-primary)]
161
+ focus:outline-none focus:ring-2 focus:ring-[var(--color-primary)]/20 focus:border-[var(--color-primary)]
162
+ ${error ? "border-[var(--color-error)]" : "border-[var(--color-border)]"}
163
+ ${disabled ? "bg-[var(--color-background)] cursor-not-allowed opacity-60" : ""}
154
164
  `,
155
165
  children: [
156
166
  defaultValue !== void 0 && /* @__PURE__ */ jsx3("option", { value: "", children: typeof defaultValue === "string" ? defaultValue : "S\xE9lectionnez une option" }),
@@ -158,7 +168,7 @@ var SelectInput = ({
158
168
  ]
159
169
  }
160
170
  ),
161
- error && /* @__PURE__ */ jsx3("p", { className: "text-xs text-red-500", children: error })
171
+ error && /* @__PURE__ */ jsx3("p", { className: "text-xs text-[var(--color-error)]", children: error })
162
172
  ] });
163
173
  };
164
174
  var addressIpformMedia = "http://localhost:8000/media/";
@@ -818,6 +828,7 @@ var AuthServices = {
818
828
  addUser: (payload) => FetchApi.post(`${API_BASE_URL}add-user/`, payload),
819
829
  login: (payload) => FetchApi.post(`${API_BASE_URL}login/`, payload),
820
830
  getUserInformations: (token) => FetchApi.get(`${API_BASE_URL}user-informations/`, token),
831
+ getUserInformationsByOldId: (id) => FetchApi.get(`${API_BASE_URL}user-informations-by-old-id/${id}/`),
821
832
  logout: () => FetchApi.post(`${API_BASE_URL}logout/`)
822
833
  };
823
834
 
@@ -861,13 +872,31 @@ var SessionProvider = ({ children }) => {
861
872
  const [isLoading, setIsLoading] = useState2(true);
862
873
  const [showAuthModal, setShowAuthModal] = useState2(false);
863
874
  useEffect2(() => {
864
- const params = new URLSearchParams(window.location.search);
865
- const tkn = params.get("tkn");
866
- if (tkn) {
867
- localStorage.setItem("token", tkn);
868
- setToken(tkn);
869
- window.history.replaceState({}, document.title, window.location.pathname);
870
- }
875
+ const initializeSession = async () => {
876
+ const params = new URLSearchParams(window.location.search);
877
+ const tkn = params.get("tkn");
878
+ if (tkn) {
879
+ localStorage.setItem("token", tkn);
880
+ setToken(tkn);
881
+ window.history.replaceState({}, document.title, window.location.pathname);
882
+ }
883
+ const old_rewise_user_id = params.get("rewise_user_id");
884
+ if (old_rewise_user_id) {
885
+ try {
886
+ const res = await AuthServices.getUserInformationsByOldId(parseInt(old_rewise_user_id));
887
+ const result = res;
888
+ if (result.success === true) {
889
+ setLoggedUser(result.data.user);
890
+ setActiveBusinessEntity(
891
+ result.data.user.centers_access.find((item) => parseInt(String(item.id)) === parseInt(saved_center_id)) || result.data.user.centers_access[0] || null
892
+ );
893
+ }
894
+ } catch (error) {
895
+ console.error("Failed to refresh session:", error);
896
+ }
897
+ }
898
+ };
899
+ initializeSession();
871
900
  }, []);
872
901
  const [vendors, setVendors] = useState2(() => {
873
902
  const cacheKey = `vendors_cache_${activeBusinessEntity?.id || "default"}`;
@@ -915,6 +944,21 @@ var SessionProvider = ({ children }) => {
915
944
  localStorage.removeItem("token");
916
945
  setToken(null);
917
946
  };
947
+ const refreshSession = async () => {
948
+ if (!token) return;
949
+ try {
950
+ const res = await AuthServices.getUserInformations(token);
951
+ const result = res;
952
+ if (result.success === true) {
953
+ setLoggedUser(result.data.user);
954
+ setActiveBusinessEntity(
955
+ result.data.user.centers_access.find((item) => parseInt(String(item.id)) === parseInt(saved_center_id)) || result.data.user.centers_access[0] || null
956
+ );
957
+ }
958
+ } catch (error) {
959
+ console.error("Failed to refresh session:", error);
960
+ }
961
+ };
918
962
  useEffect2(() => {
919
963
  if (token) {
920
964
  AuthServices.getUserInformations(token).then((res) => {
@@ -947,6 +991,7 @@ var SessionProvider = ({ children }) => {
947
991
  logout,
948
992
  showAuthModal,
949
993
  setShowAuthModal,
994
+ refreshSession,
950
995
  vendors,
951
996
  setVendors,
952
997
  loadingVendors,
@@ -1021,16 +1066,17 @@ var ToastProvider = ({ children }) => {
1021
1066
  const addToast = useCallback((toast) => {
1022
1067
  const id = generateId();
1023
1068
  const defaultDuration = toast.type === "error" ? 7e3 : 3e3;
1069
+ const duration = toast.duration ?? defaultDuration;
1024
1070
  const newToast = {
1071
+ ...toast,
1025
1072
  id,
1026
- duration: toast.duration ?? defaultDuration,
1027
- ...toast
1073
+ duration
1028
1074
  };
1029
1075
  setToasts((prev) => [...prev, newToast]);
1030
- if (newToast.duration && newToast.duration > 0) {
1076
+ if (duration > 0) {
1031
1077
  setTimeout(() => {
1032
- removeToast(id);
1033
- }, newToast.duration);
1078
+ setToasts((prev) => prev.filter((t) => t.id !== id));
1079
+ }, duration);
1034
1080
  }
1035
1081
  }, []);
1036
1082
  const removeToast = useCallback((id) => {
@@ -3417,6 +3463,16 @@ var Pages = ({
3417
3463
  };
3418
3464
  var Pages_default = Pages;
3419
3465
 
3466
+ // src/services/UnitServices.ts
3467
+ var URI = `${API_URL}/crm/units/`;
3468
+ var UnitServices = {
3469
+ create: (data) => FetchApi.post(`${URI}`, data),
3470
+ get: (id) => FetchApi.get(`${URI}${id}/`),
3471
+ list: (params) => FetchApi.get(`${URI}?${new URLSearchParams(params).toString()}`),
3472
+ update: (id, data) => FetchApi.put(`${URI}${id}/`, data),
3473
+ delete: (id) => FetchApi.delete(`${URI}${id}/`)
3474
+ };
3475
+
3420
3476
  // src/components/common/FDrawer.tsx
3421
3477
  import React7, { useEffect as useEffect6, useState as useState8, useRef } from "react";
3422
3478
  import { useLocation as useLocation2, useNavigate as useNavigate2, useSearchParams as useSearchParams2, Link as Link2 } from "react-router-dom";
@@ -5298,7 +5354,7 @@ var ApprovalWorkflow = ({
5298
5354
  if (CustomBtn) {
5299
5355
  return /* @__PURE__ */ jsxs11(Fragment6, { children: [
5300
5356
  /* @__PURE__ */ jsx14(CustomBtn, { onClick: open_modal }),
5301
- /* @__PURE__ */ jsx14(Modals_default, { open: isOpen, onClose: close_modal, title, children: formulaire() })
5357
+ /* @__PURE__ */ jsx14(Modals_default, { open: isOpen, onClose: close_modal, title, width: "max-w-lg", children: formulaire() })
5302
5358
  ] });
5303
5359
  }
5304
5360
  return /* @__PURE__ */ jsx14(Fragment6, { children: /* @__PURE__ */ jsx14(
@@ -5496,7 +5552,7 @@ var AddStageButton = ({
5496
5552
  /* @__PURE__ */ jsx14(
5497
5553
  Modals_default,
5498
5554
  {
5499
- width: "",
5555
+ width: "max-w-lg",
5500
5556
  title: "Ajouter une personne",
5501
5557
  description: "S\xE9lectionnez un utilisateur interne ou ajoutez un validateur externe",
5502
5558
  open: showModal,
@@ -5815,7 +5871,7 @@ var MinimalVendorForm = ({
5815
5871
  Modals_default,
5816
5872
  {
5817
5873
  title: "Ajouter un fournisseur",
5818
- width: "w-[100%]",
5874
+ width: "max-w-2xl",
5819
5875
  description: ``,
5820
5876
  open: isOpen,
5821
5877
  onClose,
@@ -5870,17 +5926,17 @@ var MinimalVendorForm = ({
5870
5926
  };
5871
5927
 
5872
5928
  // src/services/DepartmentServices.ts
5873
- var URI = `${API_URL}/core/departments/`;
5929
+ var URI2 = `${API_URL}/core/departments/`;
5874
5930
  var DepartmentServices = {
5875
- create: (data) => FetchApi.post(`${URI}`, data),
5876
- get: (id) => FetchApi.get(`${URI}${id}/`),
5877
- list: (params) => FetchApi.get(`${URI}?${new URLSearchParams(params).toString()}`),
5878
- update: (id, data) => FetchApi.put(`${URI}${id}/`, data),
5879
- delete: (id) => FetchApi.delete(`${URI}${id}/`)
5931
+ create: (data) => FetchApi.post(`${URI2}`, data),
5932
+ get: (id) => FetchApi.get(`${URI2}${id}/`),
5933
+ list: (params) => FetchApi.get(`${URI2}?${new URLSearchParams(params).toString()}`),
5934
+ update: (id, data) => FetchApi.put(`${URI2}${id}/`, data),
5935
+ delete: (id) => FetchApi.delete(`${URI2}${id}/`)
5880
5936
  };
5881
5937
 
5882
5938
  // src/services/ProfitCostsServices.ts
5883
- var URI2 = `${API_URL}/accounting/profit-or-cost-center/`;
5939
+ var URI3 = `${API_URL}/accounting/profit-or-cost-center/`;
5884
5940
  var COST_URI = `${API_URL}/accounting/cost-center/`;
5885
5941
  var CostServices = {
5886
5942
  create: (data) => FetchApi.post(`${COST_URI}`, data),
@@ -6187,6 +6243,80 @@ var SelectCostCenter = ({
6187
6243
  loading && /* @__PURE__ */ jsx18("p", { className: "text-sm text-gray-500 mt-2", children: "Chargement des centres de co\xFBt..." })
6188
6244
  ] });
6189
6245
  };
6246
+ var SelectUnit = ({
6247
+ value,
6248
+ onSelect
6249
+ }) => {
6250
+ const { token, activeBusinessEntity } = useSession();
6251
+ const [units, setUnits] = useState13(() => {
6252
+ const cacheKey = `units_cache_${activeBusinessEntity?.id || "default"}`;
6253
+ const cached = sessionStorage.getItem(cacheKey);
6254
+ return cached ? JSON.parse(cached) : [];
6255
+ });
6256
+ const [loading, setLoading] = useState13(false);
6257
+ useEffect10(() => {
6258
+ const cacheKey = `units_cache_${activeBusinessEntity?.id || "default"}`;
6259
+ const cached = sessionStorage.getItem(cacheKey);
6260
+ if (!cached) {
6261
+ loadUnits();
6262
+ } else {
6263
+ setUnits(JSON.parse(cached));
6264
+ }
6265
+ }, [activeBusinessEntity?.id]);
6266
+ const loadUnits = async () => {
6267
+ if (!token) return;
6268
+ try {
6269
+ setLoading(true);
6270
+ const result = await UnitServices.list({ business_entity_id: activeBusinessEntity?.id });
6271
+ if (result.success) {
6272
+ setUnits(result.data);
6273
+ const cacheKey = `units_cache_${activeBusinessEntity?.id || "default"}`;
6274
+ sessionStorage.setItem(cacheKey, JSON.stringify(result.data));
6275
+ }
6276
+ } catch (error) {
6277
+ console.error(error);
6278
+ } finally {
6279
+ setLoading(false);
6280
+ }
6281
+ };
6282
+ const handleRefresh = () => {
6283
+ const cacheKey = `units_cache_${activeBusinessEntity?.id || "default"}`;
6284
+ sessionStorage.removeItem(cacheKey);
6285
+ loadUnits();
6286
+ };
6287
+ const getUnitOptions = () => {
6288
+ return units.map((unit) => ({
6289
+ value: unit.id,
6290
+ label: `${unit.code ? `[${unit.code}] ` : ""}${unit.name || "Sans nom"}`,
6291
+ object: unit,
6292
+ content: /* @__PURE__ */ jsx18("div", { className: "flex items-center space-x-3", children: /* @__PURE__ */ jsxs15("div", { className: "flex-1", children: [
6293
+ /* @__PURE__ */ jsx18("div", { className: "font-medium text-gray-900", children: unit.name || "Sans nom" }),
6294
+ unit.code && /* @__PURE__ */ jsxs15("div", { className: "text-sm text-gray-500", children: [
6295
+ "Code: ",
6296
+ unit.code
6297
+ ] }),
6298
+ unit.location && /* @__PURE__ */ jsx18("div", { className: "text-xs text-gray-400", children: unit.location })
6299
+ ] }) })
6300
+ }));
6301
+ };
6302
+ return /* @__PURE__ */ jsxs15("div", { children: [
6303
+ /* @__PURE__ */ jsx18("div", { className: "flex justify-between ", children: /* @__PURE__ */ jsx18("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "S\xE9lectionner une unit\xE9" }) }),
6304
+ /* @__PURE__ */ jsx18(
6305
+ SearchableSelect,
6306
+ {
6307
+ value,
6308
+ options: getUnitOptions(),
6309
+ placeholder: "S\xE9lectionner une unit\xE9 ...",
6310
+ searchPlaceholder: "Rechercher...",
6311
+ onSelect,
6312
+ disabled: loading,
6313
+ refresh: handleRefresh
6314
+ },
6315
+ "unit" + value
6316
+ ),
6317
+ loading && /* @__PURE__ */ jsx18("p", { className: "text-sm text-gray-500 mt-2", children: "Chargement des unit\xE9s..." })
6318
+ ] });
6319
+ };
6190
6320
 
6191
6321
  // src/components/common/Choices.tsx
6192
6322
  import { jsx as jsx19 } from "react/jsx-runtime";
@@ -8210,6 +8340,384 @@ var EntityFileManager = ({
8210
8340
  }
8211
8341
  ) });
8212
8342
  };
8343
+
8344
+ // src/components/common/PrintPreview.tsx
8345
+ import React20, { useRef as useRef6 } from "react";
8346
+ import { useReactToPrint } from "react-to-print";
8347
+ import { X as X10, Printer as Printer2, Download as Download4, ZoomIn, ZoomOut } from "lucide-react";
8348
+ import { jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
8349
+ var PrintableDocument = ({
8350
+ children,
8351
+ className = "",
8352
+ style = {}
8353
+ }) => {
8354
+ return /* @__PURE__ */ jsx30(
8355
+ "div",
8356
+ {
8357
+ className: `bg-white text-gray-900 ${className}`,
8358
+ style: {
8359
+ fontFamily: "Arial, sans-serif",
8360
+ fontSize: "11px",
8361
+ ...style
8362
+ },
8363
+ children
8364
+ }
8365
+ );
8366
+ };
8367
+ var PrintPreview = ({
8368
+ children,
8369
+ isOpen,
8370
+ onClose,
8371
+ title = "Aper\xE7u avant impression",
8372
+ documentName = "document",
8373
+ pageWidth = "210mm",
8374
+ pageMinHeight = "297mm",
8375
+ orientation = "portrait",
8376
+ onAfterPrint,
8377
+ onBeforePrint
8378
+ }) => {
8379
+ const printRef = useRef6(null);
8380
+ const [zoom, setZoom] = React20.useState(100);
8381
+ const handlePrint = useReactToPrint({
8382
+ contentRef: printRef,
8383
+ documentTitle: documentName,
8384
+ onAfterPrint: () => {
8385
+ onAfterPrint?.();
8386
+ },
8387
+ onBeforePrint
8388
+ });
8389
+ const handleZoomIn = () => {
8390
+ setZoom((prev) => Math.min(prev + 10, 150));
8391
+ };
8392
+ const handleZoomOut = () => {
8393
+ setZoom((prev) => Math.max(prev - 10, 50));
8394
+ };
8395
+ if (!isOpen) return null;
8396
+ const pageStyles = {
8397
+ width: orientation === "portrait" ? pageWidth : pageMinHeight,
8398
+ minHeight: orientation === "portrait" ? pageMinHeight : pageWidth,
8399
+ transform: `scale(${zoom / 100})`,
8400
+ transformOrigin: "top center",
8401
+ transition: "transform 0.2s ease"
8402
+ };
8403
+ return /* @__PURE__ */ jsxs25("div", { className: "fixed inset-0 z-50 overflow-hidden print:hidden", children: [
8404
+ /* @__PURE__ */ jsx30(
8405
+ "div",
8406
+ {
8407
+ className: "absolute inset-0 bg-black/50 backdrop-blur-sm",
8408
+ onClick: onClose
8409
+ }
8410
+ ),
8411
+ /* @__PURE__ */ jsxs25("div", { className: "relative h-full flex flex-col", children: [
8412
+ /* @__PURE__ */ jsxs25("div", { className: "bg-white border-b shadow-sm px-6 py-3 flex items-center justify-between z-10", children: [
8413
+ /* @__PURE__ */ jsx30("h3", { className: "text-lg font-semibold text-gray-900", children: title }),
8414
+ /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-3", children: [
8415
+ /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-1 bg-gray-100 rounded-lg px-2 py-1", children: [
8416
+ /* @__PURE__ */ jsx30(
8417
+ "button",
8418
+ {
8419
+ onClick: handleZoomOut,
8420
+ className: "p-1 hover:bg-gray-200 rounded transition-colors",
8421
+ title: "Zoom arri\xE8re",
8422
+ children: /* @__PURE__ */ jsx30(ZoomOut, { className: "w-4 h-4 text-gray-600" })
8423
+ }
8424
+ ),
8425
+ /* @__PURE__ */ jsxs25("span", { className: "text-sm text-gray-600 min-w-[3rem] text-center", children: [
8426
+ zoom,
8427
+ "%"
8428
+ ] }),
8429
+ /* @__PURE__ */ jsx30(
8430
+ "button",
8431
+ {
8432
+ onClick: handleZoomIn,
8433
+ className: "p-1 hover:bg-gray-200 rounded transition-colors",
8434
+ title: "Zoom avant",
8435
+ children: /* @__PURE__ */ jsx30(ZoomIn, { className: "w-4 h-4 text-gray-600" })
8436
+ }
8437
+ )
8438
+ ] }),
8439
+ /* @__PURE__ */ jsxs25(
8440
+ "button",
8441
+ {
8442
+ onClick: () => handlePrint(),
8443
+ className: "px-4 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 transition-colors flex items-center gap-2",
8444
+ children: [
8445
+ /* @__PURE__ */ jsx30(Printer2, { className: "w-4 h-4" }),
8446
+ "Imprimer"
8447
+ ]
8448
+ }
8449
+ ),
8450
+ /* @__PURE__ */ jsxs25(
8451
+ "button",
8452
+ {
8453
+ onClick: () => handlePrint(),
8454
+ className: "px-4 py-2 bg-green-600 text-white text-sm font-medium rounded-lg hover:bg-green-700 transition-colors flex items-center gap-2",
8455
+ children: [
8456
+ /* @__PURE__ */ jsx30(Download4, { className: "w-4 h-4" }),
8457
+ "PDF"
8458
+ ]
8459
+ }
8460
+ ),
8461
+ /* @__PURE__ */ jsx30(
8462
+ "button",
8463
+ {
8464
+ onClick: onClose,
8465
+ className: "p-2 hover:bg-gray-100 rounded-full transition-colors",
8466
+ title: "Fermer",
8467
+ children: /* @__PURE__ */ jsx30(X10, { className: "w-5 h-5 text-gray-500" })
8468
+ }
8469
+ )
8470
+ ] })
8471
+ ] }),
8472
+ /* @__PURE__ */ jsx30("div", { className: "flex-1 overflow-auto bg-gray-200 p-6", children: /* @__PURE__ */ jsx30("div", { className: "flex justify-center", children: /* @__PURE__ */ jsx30(
8473
+ "div",
8474
+ {
8475
+ ref: printRef,
8476
+ className: "bg-white shadow-xl",
8477
+ style: pageStyles,
8478
+ children
8479
+ }
8480
+ ) }) })
8481
+ ] })
8482
+ ] });
8483
+ };
8484
+ var PRINT_GREEN = "#2d7d46";
8485
+ var DocumentHeader = ({
8486
+ companyName,
8487
+ address,
8488
+ phone,
8489
+ email,
8490
+ website,
8491
+ documentTitle,
8492
+ documentNumber,
8493
+ date,
8494
+ extraInfo = []
8495
+ }) => {
8496
+ return /* @__PURE__ */ jsxs25("div", { className: "flex justify-between items-start mb-6 p-8 pb-0", children: [
8497
+ /* @__PURE__ */ jsxs25("div", { className: "flex-1", children: [
8498
+ /* @__PURE__ */ jsxs25("div", { className: "mb-2", children: [
8499
+ /* @__PURE__ */ jsx30(
8500
+ "h1",
8501
+ {
8502
+ className: "text-xl font-bold tracking-wider text-gray-800",
8503
+ style: { letterSpacing: "0.15em" },
8504
+ children: companyName.toUpperCase()
8505
+ }
8506
+ ),
8507
+ address && /* @__PURE__ */ jsx30("p", { className: "text-xs text-gray-600 mt-1", children: address })
8508
+ ] }),
8509
+ /* @__PURE__ */ jsxs25("div", { className: "text-xs text-gray-600 space-y-0.5 mt-3", children: [
8510
+ phone && /* @__PURE__ */ jsx30("p", { children: phone }),
8511
+ email && /* @__PURE__ */ jsx30("p", { children: email }),
8512
+ website && /* @__PURE__ */ jsx30("p", { children: website })
8513
+ ] })
8514
+ ] }),
8515
+ /* @__PURE__ */ jsxs25("div", { className: "text-right", children: [
8516
+ /* @__PURE__ */ jsx30(
8517
+ "h2",
8518
+ {
8519
+ className: "text-4xl font-bold mb-4",
8520
+ style: { color: PRINT_GREEN },
8521
+ children: documentTitle
8522
+ }
8523
+ ),
8524
+ /* @__PURE__ */ jsx30("table", { className: "ml-auto text-xs", children: /* @__PURE__ */ jsxs25("tbody", { children: [
8525
+ /* @__PURE__ */ jsxs25("tr", { children: [
8526
+ /* @__PURE__ */ jsx30("td", { className: "text-gray-600 pr-4 py-0.5", children: "Date" }),
8527
+ /* @__PURE__ */ jsx30("td", { className: "font-medium border-b border-gray-300 pl-2 py-0.5", children: date })
8528
+ ] }),
8529
+ /* @__PURE__ */ jsxs25("tr", { children: [
8530
+ /* @__PURE__ */ jsx30("td", { className: "text-gray-600 pr-4 py-0.5", children: "N\xB0" }),
8531
+ /* @__PURE__ */ jsx30("td", { className: "font-medium border-b border-gray-300 pl-2 py-0.5", children: documentNumber })
8532
+ ] }),
8533
+ extraInfo.map((info, index) => /* @__PURE__ */ jsxs25("tr", { children: [
8534
+ /* @__PURE__ */ jsx30("td", { className: "text-gray-600 pr-4 py-0.5", children: info.label }),
8535
+ /* @__PURE__ */ jsx30("td", { className: "font-medium border-b border-gray-300 pl-2 py-0.5", children: info.value })
8536
+ ] }, index))
8537
+ ] }) })
8538
+ ] })
8539
+ ] });
8540
+ };
8541
+ var InfoBox = ({
8542
+ title,
8543
+ children,
8544
+ variant = "green"
8545
+ }) => {
8546
+ const headerBg = variant === "green" ? PRINT_GREEN : "#6b7280";
8547
+ return /* @__PURE__ */ jsxs25("div", { children: [
8548
+ /* @__PURE__ */ jsx30(
8549
+ "div",
8550
+ {
8551
+ className: "text-white text-xs font-semibold py-1.5 px-3 rounded-t",
8552
+ style: { backgroundColor: headerBg },
8553
+ children: title
8554
+ }
8555
+ ),
8556
+ /* @__PURE__ */ jsx30("div", { className: "border border-gray-300 border-t-0 p-3 min-h-[80px]", children })
8557
+ ] });
8558
+ };
8559
+ function DataTable({
8560
+ columns,
8561
+ data,
8562
+ minEmptyRows = 0,
8563
+ keyExtractor
8564
+ }) {
8565
+ const emptyRowsCount = Math.max(0, minEmptyRows - data.length);
8566
+ const getNestedValue = (obj, path) => {
8567
+ return path.split(".").reduce((acc, part) => acc && acc[part], obj);
8568
+ };
8569
+ return /* @__PURE__ */ jsxs25("table", { className: "w-full border-collapse", children: [
8570
+ /* @__PURE__ */ jsx30("thead", { children: /* @__PURE__ */ jsx30("tr", { style: { backgroundColor: PRINT_GREEN }, children: columns.map((col, index) => /* @__PURE__ */ jsx30(
8571
+ "th",
8572
+ {
8573
+ className: "text-white text-xs font-semibold py-2 px-2 border border-gray-400",
8574
+ style: {
8575
+ width: col.width,
8576
+ textAlign: col.align || "left"
8577
+ },
8578
+ children: col.header
8579
+ },
8580
+ index
8581
+ )) }) }),
8582
+ /* @__PURE__ */ jsxs25("tbody", { children: [
8583
+ data.map((item, rowIndex) => /* @__PURE__ */ jsx30("tr", { className: "border-b border-gray-300", children: columns.map((col, colIndex) => {
8584
+ const value = typeof col.key === "string" ? getNestedValue(item, col.key) : item[col.key];
8585
+ return /* @__PURE__ */ jsx30(
8586
+ "td",
8587
+ {
8588
+ className: "py-2 px-2 text-xs text-gray-900 border-l border-r border-gray-300",
8589
+ style: { textAlign: col.align || "left" },
8590
+ children: col.render ? col.render(value, item, rowIndex) : value
8591
+ },
8592
+ colIndex
8593
+ );
8594
+ }) }, keyExtractor(item, rowIndex))),
8595
+ Array.from({ length: emptyRowsCount }).map((_, index) => /* @__PURE__ */ jsx30("tr", { className: "border-b border-gray-300", children: columns.map((_2, colIndex) => /* @__PURE__ */ jsx30(
8596
+ "td",
8597
+ {
8598
+ className: "py-2 px-2 border-l border-r border-gray-300",
8599
+ children: "\xA0"
8600
+ },
8601
+ colIndex
8602
+ )) }, `empty-${index}`))
8603
+ ] })
8604
+ ] });
8605
+ }
8606
+ var TotalsSection = ({
8607
+ rows,
8608
+ amountInWords
8609
+ }) => {
8610
+ return /* @__PURE__ */ jsxs25("div", { className: "w-64", children: [
8611
+ /* @__PURE__ */ jsx30("table", { className: "w-full text-xs", children: /* @__PURE__ */ jsx30("tbody", { children: rows.map((row, index) => /* @__PURE__ */ jsxs25(
8612
+ "tr",
8613
+ {
8614
+ className: row.isTotal ? "" : "border-b border-gray-200",
8615
+ children: [
8616
+ /* @__PURE__ */ jsx30(
8617
+ "td",
8618
+ {
8619
+ className: `py-1.5 px-2 ${row.isTotal ? "font-semibold text-gray-900" : "text-gray-600"}`,
8620
+ children: row.label
8621
+ }
8622
+ ),
8623
+ /* @__PURE__ */ jsx30(
8624
+ "td",
8625
+ {
8626
+ className: `py-1.5 px-2 text-right font-medium`,
8627
+ style: row.isTotal ? {
8628
+ backgroundColor: PRINT_GREEN,
8629
+ color: "white",
8630
+ fontWeight: "bold"
8631
+ } : { color: row.valueColor },
8632
+ children: row.value
8633
+ }
8634
+ )
8635
+ ]
8636
+ },
8637
+ index
8638
+ )) }) }),
8639
+ amountInWords && /* @__PURE__ */ jsx30("div", { className: "mt-2 text-xs text-gray-600 italic text-center px-2", children: amountInWords })
8640
+ ] });
8641
+ };
8642
+ var SignatureSection = ({
8643
+ date,
8644
+ leftLabel = "Signature Client",
8645
+ rightLabel = "Cachet et signature",
8646
+ rightName
8647
+ }) => {
8648
+ return /* @__PURE__ */ jsxs25("div", { className: "grid grid-cols-2 gap-8 mt-12 pt-4 px-8", children: [
8649
+ /* @__PURE__ */ jsxs25("div", { children: [
8650
+ /* @__PURE__ */ jsx30("p", { className: "text-xs text-gray-600 mb-1", children: "Date:" }),
8651
+ /* @__PURE__ */ jsx30("div", { className: "border-b border-gray-400 w-32 mb-4 text-xs", children: date || "" }),
8652
+ /* @__PURE__ */ jsxs25("p", { className: "text-xs text-gray-600 mb-1", children: [
8653
+ leftLabel,
8654
+ ":"
8655
+ ] }),
8656
+ /* @__PURE__ */ jsx30("div", { className: "h-16 border-b border-gray-400 w-48" })
8657
+ ] }),
8658
+ /* @__PURE__ */ jsxs25("div", { className: "text-right", children: [
8659
+ /* @__PURE__ */ jsx30("p", { className: "text-xs text-gray-600 mb-2", children: rightLabel }),
8660
+ rightName && /* @__PURE__ */ jsx30("p", { className: "text-xs font-medium text-gray-900 mb-2", children: rightName }),
8661
+ /* @__PURE__ */ jsx30("div", { className: "inline-block border-2 border-dashed border-gray-300 rounded-full w-24 h-24" })
8662
+ ] })
8663
+ ] });
8664
+ };
8665
+ var DocumentFooter = ({ lines }) => {
8666
+ return /* @__PURE__ */ jsx30("div", { className: "mt-8 pt-3 border-t border-gray-300 text-center text-xs text-gray-500 px-8 pb-8", children: lines.map((line, index) => /* @__PURE__ */ jsx30("p", { children: line }, index)) });
8667
+ };
8668
+ var numberToWords = (num) => {
8669
+ if (num === 0) return "z\xE9ro";
8670
+ const units = ["", "un", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf", "dix", "onze", "douze", "treize", "quatorze", "quinze", "seize", "dix-sept", "dix-huit", "dix-neuf"];
8671
+ const tens = ["", "", "vingt", "trente", "quarante", "cinquante", "soixante", "soixante", "quatre-vingt", "quatre-vingt"];
8672
+ const convertHundreds = (n) => {
8673
+ if (n < 20) return units[n];
8674
+ if (n < 100) {
8675
+ const ten = Math.floor(n / 10);
8676
+ const unit = n % 10;
8677
+ if (ten === 7 || ten === 9) {
8678
+ return tens[ten] + "-" + units[10 + unit];
8679
+ }
8680
+ return tens[ten] + (unit ? "-" + units[unit] : ten === 8 ? "s" : "");
8681
+ }
8682
+ const hundred = Math.floor(n / 100);
8683
+ const rest = n % 100;
8684
+ return (hundred === 1 ? "cent" : units[hundred] + " cent") + (rest ? " " + convertHundreds(rest) : hundred > 1 && rest === 0 ? "s" : "");
8685
+ };
8686
+ const convertThousands = (n) => {
8687
+ if (n < 1e3) return convertHundreds(n);
8688
+ const thousands = Math.floor(n / 1e3);
8689
+ const rest = n % 1e3;
8690
+ return (thousands === 1 ? "mille" : convertHundreds(thousands) + " mille") + (rest ? " " + convertHundreds(rest) : "");
8691
+ };
8692
+ const convertMillions = (n) => {
8693
+ if (n < 1e6) return convertThousands(n);
8694
+ const millions = Math.floor(n / 1e6);
8695
+ const rest = n % 1e6;
8696
+ return convertHundreds(millions) + " million" + (millions > 1 ? "s" : "") + (rest ? " " + convertThousands(rest) : "");
8697
+ };
8698
+ return convertMillions(Math.floor(num));
8699
+ };
8700
+ var formatDateFR = (date, format = "short") => {
8701
+ const d = typeof date === "string" ? new Date(date) : date;
8702
+ if (format === "short") {
8703
+ return d.toLocaleDateString("fr-FR", {
8704
+ day: "2-digit",
8705
+ month: "2-digit",
8706
+ year: "numeric"
8707
+ });
8708
+ }
8709
+ return d.toLocaleDateString("fr-FR", {
8710
+ day: "numeric",
8711
+ month: "long",
8712
+ year: "numeric"
8713
+ });
8714
+ };
8715
+ var formatCurrency = (amount, currency = "XOF", showDecimals = false) => {
8716
+ return amount.toLocaleString("fr-FR", {
8717
+ minimumFractionDigits: showDecimals ? 2 : 0,
8718
+ maximumFractionDigits: showDecimals ? 2 : 0
8719
+ }) + (currency ? ` ${currency}` : "");
8720
+ };
8213
8721
  export {
8214
8722
  Alert_default as Alert,
8215
8723
  AlertProvider,
@@ -8221,7 +8729,10 @@ export {
8221
8729
  AuthServices,
8222
8730
  Choices_default as CHOICES,
8223
8731
  CountrySelector,
8732
+ DataTable,
8224
8733
  DateInput,
8734
+ DocumentFooter,
8735
+ DocumentHeader,
8225
8736
  EntityFileManager,
8226
8737
  FDrawer,
8227
8738
  FetchApi,
@@ -8229,33 +8740,44 @@ export {
8229
8740
  FileManager,
8230
8741
  FileManagerProvider,
8231
8742
  ForeignCurrencySelector,
8743
+ InfoBox,
8232
8744
  InputField,
8233
8745
  InvoiceTypeSelector,
8234
8746
  LegalFormSelector,
8235
8747
  Modals_default as Modal,
8236
8748
  NumberInput,
8749
+ PRINT_GREEN,
8237
8750
  Pages_default as Pages,
8238
8751
  PaymentMethodSelector,
8239
8752
  Buttons_default as PrimaryButton,
8753
+ PrintPreview,
8754
+ PrintableDocument,
8240
8755
  ModernDoubleSidebarLayout_default as RewiseLayout,
8241
8756
  SecondaryButton,
8242
8757
  SelectCostCenter,
8243
8758
  SelectDepartment,
8244
8759
  SelectInput,
8760
+ SelectUnit,
8245
8761
  SelectUser,
8246
8762
  SelectVendor,
8247
8763
  SessionProvider,
8764
+ SignatureSection,
8248
8765
  TaxSelector,
8249
8766
  TemplateFNESelector,
8250
8767
  TextInput,
8251
8768
  ThemeContext_default as ThemeProvider,
8252
8769
  Toast_default as ToastContainer,
8253
8770
  ToastProvider,
8771
+ TotalsSection,
8772
+ UnitServices,
8254
8773
  UserServices,
8255
8774
  fileManagerApi,
8775
+ formatCurrency,
8256
8776
  formatDate,
8777
+ formatDateFR,
8257
8778
  formatFileSize,
8258
8779
  getFileIcon,
8780
+ numberToWords,
8259
8781
  useAlert,
8260
8782
  useFileManager,
8261
8783
  useFileManagerApi,