@growflowstudio/growflowbooking-admin-ui 1.0.0
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.d.mts +208 -0
- package/dist/index.d.ts +208 -0
- package/dist/index.js +1569 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1550 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +85 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1550 @@
|
|
|
1
|
+
// src/provider.tsx
|
|
2
|
+
import React, { useMemo } from "react";
|
|
3
|
+
import {
|
|
4
|
+
BookingAdminClientContext,
|
|
5
|
+
createBookingAdminClient
|
|
6
|
+
} from "@growflowstudio/growflowbooking-admin-core";
|
|
7
|
+
import { jsx } from "react/jsx-runtime";
|
|
8
|
+
var DEFAULT_MODULES = {
|
|
9
|
+
tenants: true,
|
|
10
|
+
customers: true,
|
|
11
|
+
subscriptionPlans: true,
|
|
12
|
+
billingPlans: true,
|
|
13
|
+
billingSubscriptions: true,
|
|
14
|
+
settings: true
|
|
15
|
+
};
|
|
16
|
+
var BookingAdminConfigContext = React.createContext(null);
|
|
17
|
+
function useBookingAdminConfig() {
|
|
18
|
+
const config = React.useContext(BookingAdminConfigContext);
|
|
19
|
+
if (!config) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
"useBookingAdminConfig must be used within a BookingAdminProvider."
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return config;
|
|
25
|
+
}
|
|
26
|
+
function useEnabledModules() {
|
|
27
|
+
const config = useBookingAdminConfig();
|
|
28
|
+
return { ...DEFAULT_MODULES, ...config.modules };
|
|
29
|
+
}
|
|
30
|
+
function BookingAdminProvider({ config, children }) {
|
|
31
|
+
const client = useMemo(
|
|
32
|
+
() => createBookingAdminClient({
|
|
33
|
+
basePath: config.basePath,
|
|
34
|
+
fetcher: config.fetcher
|
|
35
|
+
}),
|
|
36
|
+
[config.basePath, config.fetcher]
|
|
37
|
+
);
|
|
38
|
+
return /* @__PURE__ */ jsx(BookingAdminConfigContext.Provider, { value: config, children: /* @__PURE__ */ jsx(BookingAdminClientContext.Provider, { value: client, children }) });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/pages/TenantsPage.tsx
|
|
42
|
+
import { useState as useState2 } from "react";
|
|
43
|
+
import { toast } from "sonner";
|
|
44
|
+
import { Plus, Building2 } from "lucide-react";
|
|
45
|
+
import {
|
|
46
|
+
useAdminTenants,
|
|
47
|
+
useAdminTenant,
|
|
48
|
+
useCreateTenant,
|
|
49
|
+
useUpdateTenant,
|
|
50
|
+
useDeleteTenant
|
|
51
|
+
} from "@growflowstudio/growflowbooking-admin-core";
|
|
52
|
+
|
|
53
|
+
// src/components/tenants/TenantsTable.tsx
|
|
54
|
+
import { Pencil, Trash2 } from "lucide-react";
|
|
55
|
+
|
|
56
|
+
// src/primitives/utils.ts
|
|
57
|
+
import { clsx } from "clsx";
|
|
58
|
+
import { twMerge } from "tailwind-merge";
|
|
59
|
+
function cn(...inputs) {
|
|
60
|
+
return twMerge(clsx(inputs));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/components/shared/StatusBadge.tsx
|
|
64
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
65
|
+
function StatusBadge({ label, colorClass, className }) {
|
|
66
|
+
return /* @__PURE__ */ jsx2(
|
|
67
|
+
"span",
|
|
68
|
+
{
|
|
69
|
+
className: cn(
|
|
70
|
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors",
|
|
71
|
+
colorClass,
|
|
72
|
+
className
|
|
73
|
+
),
|
|
74
|
+
children: label
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// src/components/shared/SkeletonRows.tsx
|
|
80
|
+
import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
|
|
81
|
+
function SkeletonRows({ rows = 5, columns = 1 }) {
|
|
82
|
+
return /* @__PURE__ */ jsx3(Fragment, { children: Array.from({ length: rows }).map((_, i) => /* @__PURE__ */ jsx3("tr", { children: /* @__PURE__ */ jsx3("td", { colSpan: columns, className: "p-4", children: /* @__PURE__ */ jsx3("div", { className: "h-10 w-full animate-pulse rounded-md bg-muted" }) }) }, i)) });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/components/tenants/TenantsTable.tsx
|
|
86
|
+
import {
|
|
87
|
+
tenantStatusColors,
|
|
88
|
+
tenantStatusLabels,
|
|
89
|
+
tenantPlanColors,
|
|
90
|
+
tenantPlanLabels,
|
|
91
|
+
formatDate
|
|
92
|
+
} from "@growflowstudio/growflowbooking-admin-core";
|
|
93
|
+
import { jsx as jsx4, jsxs } from "react/jsx-runtime";
|
|
94
|
+
function TenantsTable({ tenants, isLoading, onEdit, onDelete, onView }) {
|
|
95
|
+
return /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
|
|
96
|
+
/* @__PURE__ */ jsx4("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b", children: [
|
|
97
|
+
/* @__PURE__ */ jsx4("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Nome" }),
|
|
98
|
+
/* @__PURE__ */ jsx4("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Slug" }),
|
|
99
|
+
/* @__PURE__ */ jsx4("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Piano" }),
|
|
100
|
+
/* @__PURE__ */ jsx4("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Stato" }),
|
|
101
|
+
/* @__PURE__ */ jsx4("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Email" }),
|
|
102
|
+
/* @__PURE__ */ jsx4("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Creato" }),
|
|
103
|
+
/* @__PURE__ */ jsx4("th", { className: "h-12 px-4 text-right align-middle font-medium text-muted-foreground", children: "Azioni" })
|
|
104
|
+
] }) }),
|
|
105
|
+
/* @__PURE__ */ jsx4("tbody", { children: isLoading ? /* @__PURE__ */ jsx4(SkeletonRows, { rows: 5, columns: 7 }) : tenants.length === 0 ? /* @__PURE__ */ jsx4("tr", { children: /* @__PURE__ */ jsx4("td", { colSpan: 7, className: "h-24 text-center text-muted-foreground", children: "Nessun tenant trovato." }) }) : tenants.map((tenant) => /* @__PURE__ */ jsxs("tr", { className: "border-b hover:bg-muted/50 cursor-pointer", onClick: () => onView(tenant), children: [
|
|
106
|
+
/* @__PURE__ */ jsx4("td", { className: "p-4 font-medium", children: tenant.name }),
|
|
107
|
+
/* @__PURE__ */ jsx4("td", { className: "p-4", children: /* @__PURE__ */ jsx4("code", { className: "text-xs bg-muted px-1.5 py-0.5 rounded", children: tenant.slug }) }),
|
|
108
|
+
/* @__PURE__ */ jsx4("td", { className: "p-4", children: /* @__PURE__ */ jsx4(StatusBadge, { label: tenantPlanLabels[tenant.plan], colorClass: tenantPlanColors[tenant.plan] }) }),
|
|
109
|
+
/* @__PURE__ */ jsx4("td", { className: "p-4", children: /* @__PURE__ */ jsx4(
|
|
110
|
+
StatusBadge,
|
|
111
|
+
{
|
|
112
|
+
label: tenant.is_active ? tenantStatusLabels.active : tenantStatusLabels.inactive,
|
|
113
|
+
colorClass: tenant.is_active ? tenantStatusColors.active : tenantStatusColors.inactive
|
|
114
|
+
}
|
|
115
|
+
) }),
|
|
116
|
+
/* @__PURE__ */ jsx4("td", { className: "p-4 text-sm text-muted-foreground", children: tenant.owner_email }),
|
|
117
|
+
/* @__PURE__ */ jsx4("td", { className: "p-4 text-sm text-muted-foreground", children: formatDate(tenant.created_at) }),
|
|
118
|
+
/* @__PURE__ */ jsx4("td", { className: "p-4 text-right", children: /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-1", onClick: (e) => e.stopPropagation(), children: [
|
|
119
|
+
/* @__PURE__ */ jsx4("button", { className: "inline-flex items-center justify-center rounded-md text-sm h-8 w-8 hover:bg-accent", onClick: () => onEdit(tenant), children: /* @__PURE__ */ jsx4(Pencil, { className: "w-4 h-4" }) }),
|
|
120
|
+
/* @__PURE__ */ jsx4("button", { className: "inline-flex items-center justify-center rounded-md text-sm h-8 w-8 hover:bg-accent text-destructive", onClick: () => onDelete(tenant), children: /* @__PURE__ */ jsx4(Trash2, { className: "w-4 h-4" }) })
|
|
121
|
+
] }) })
|
|
122
|
+
] }, tenant.id)) })
|
|
123
|
+
] });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// src/components/tenants/TenantFormDialog.tsx
|
|
127
|
+
import { useState, useEffect } from "react";
|
|
128
|
+
import { Loader2 } from "lucide-react";
|
|
129
|
+
import { validateRequired, validateSlug, validateEmail } from "@growflowstudio/growflowbooking-admin-core";
|
|
130
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
131
|
+
function TenantFormDialog({ isOpen, onOpenChange, tenant, onSubmit, isPending }) {
|
|
132
|
+
const isEdit = !!tenant;
|
|
133
|
+
const [name, setName] = useState("");
|
|
134
|
+
const [slug, setSlug] = useState("");
|
|
135
|
+
const [ownerEmail, setOwnerEmail] = useState("");
|
|
136
|
+
const [plan, setPlan] = useState("free");
|
|
137
|
+
const [domain, setDomain] = useState("");
|
|
138
|
+
const [errors, setErrors] = useState({});
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
if (isOpen) {
|
|
141
|
+
if (tenant) {
|
|
142
|
+
setName(tenant.name);
|
|
143
|
+
setSlug(tenant.slug);
|
|
144
|
+
setOwnerEmail(tenant.owner_email);
|
|
145
|
+
setPlan(tenant.plan);
|
|
146
|
+
setDomain(tenant.domain || "");
|
|
147
|
+
} else {
|
|
148
|
+
setName("");
|
|
149
|
+
setSlug("");
|
|
150
|
+
setOwnerEmail("");
|
|
151
|
+
setPlan("free");
|
|
152
|
+
setDomain("");
|
|
153
|
+
}
|
|
154
|
+
setErrors({});
|
|
155
|
+
}
|
|
156
|
+
}, [isOpen, tenant]);
|
|
157
|
+
const handleSubmit = () => {
|
|
158
|
+
const errs = {};
|
|
159
|
+
const nameErr = validateRequired(name, "Il nome");
|
|
160
|
+
if (nameErr) errs.name = nameErr;
|
|
161
|
+
if (!isEdit) {
|
|
162
|
+
const slugErr = validateSlug(slug);
|
|
163
|
+
if (slugErr) errs.slug = slugErr;
|
|
164
|
+
}
|
|
165
|
+
const emailErr = validateEmail(ownerEmail);
|
|
166
|
+
if (emailErr) errs.ownerEmail = emailErr;
|
|
167
|
+
setErrors(errs);
|
|
168
|
+
if (Object.keys(errs).length > 0) return;
|
|
169
|
+
if (isEdit) {
|
|
170
|
+
onSubmit({ name, owner_email: ownerEmail, domain: domain || void 0 });
|
|
171
|
+
} else {
|
|
172
|
+
onSubmit({ name, slug, owner_email: ownerEmail, plan, domain: domain || void 0 });
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
if (!isOpen) return null;
|
|
176
|
+
return /* @__PURE__ */ jsxs2("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
177
|
+
/* @__PURE__ */ jsx5("div", { className: "fixed inset-0 bg-black/80", onClick: () => onOpenChange(false) }),
|
|
178
|
+
/* @__PURE__ */ jsxs2("div", { className: "relative z-50 w-full max-w-lg max-h-[90vh] overflow-y-auto rounded-lg border bg-background p-6 shadow-lg", children: [
|
|
179
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col space-y-1.5 pb-4", children: [
|
|
180
|
+
/* @__PURE__ */ jsx5("h2", { className: "text-lg font-semibold", children: isEdit ? "Modifica Tenant" : "Nuovo Tenant" }),
|
|
181
|
+
/* @__PURE__ */ jsx5("p", { className: "text-sm text-muted-foreground", children: isEdit ? "Modifica i dati del tenant selezionato" : "Crea un nuovo tenant per la piattaforma di booking" })
|
|
182
|
+
] }),
|
|
183
|
+
/* @__PURE__ */ jsxs2("div", { className: "grid gap-4 py-4", children: [
|
|
184
|
+
/* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
185
|
+
/* @__PURE__ */ jsx5("label", { className: "text-sm font-medium leading-none", children: "Nome *" }),
|
|
186
|
+
/* @__PURE__ */ jsx5("input", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: name, onChange: (e) => setName(e.target.value), placeholder: "Acme Salon" }),
|
|
187
|
+
errors.name && /* @__PURE__ */ jsx5("p", { className: "text-xs text-destructive", children: errors.name })
|
|
188
|
+
] }),
|
|
189
|
+
!isEdit && /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
190
|
+
/* @__PURE__ */ jsx5("label", { className: "text-sm font-medium leading-none", children: "Slug *" }),
|
|
191
|
+
/* @__PURE__ */ jsx5("input", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: slug, onChange: (e) => setSlug(e.target.value), placeholder: "acme-salon" }),
|
|
192
|
+
errors.slug && /* @__PURE__ */ jsx5("p", { className: "text-xs text-destructive", children: errors.slug })
|
|
193
|
+
] }),
|
|
194
|
+
/* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
195
|
+
/* @__PURE__ */ jsx5("label", { className: "text-sm font-medium leading-none", children: "Email Proprietario *" }),
|
|
196
|
+
/* @__PURE__ */ jsx5("input", { type: "email", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: ownerEmail, onChange: (e) => setOwnerEmail(e.target.value), placeholder: "owner@example.com" }),
|
|
197
|
+
errors.ownerEmail && /* @__PURE__ */ jsx5("p", { className: "text-xs text-destructive", children: errors.ownerEmail })
|
|
198
|
+
] }),
|
|
199
|
+
!isEdit && /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
200
|
+
/* @__PURE__ */ jsx5("label", { className: "text-sm font-medium leading-none", children: "Piano" }),
|
|
201
|
+
/* @__PURE__ */ jsxs2("select", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: plan, onChange: (e) => setPlan(e.target.value), children: [
|
|
202
|
+
/* @__PURE__ */ jsx5("option", { value: "free", children: "Free" }),
|
|
203
|
+
/* @__PURE__ */ jsx5("option", { value: "starter", children: "Starter" }),
|
|
204
|
+
/* @__PURE__ */ jsx5("option", { value: "professional", children: "Professional" }),
|
|
205
|
+
/* @__PURE__ */ jsx5("option", { value: "enterprise", children: "Enterprise" })
|
|
206
|
+
] })
|
|
207
|
+
] }),
|
|
208
|
+
/* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
209
|
+
/* @__PURE__ */ jsx5("label", { className: "text-sm font-medium leading-none", children: "Dominio" }),
|
|
210
|
+
/* @__PURE__ */ jsx5("input", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: domain, onChange: (e) => setDomain(e.target.value), placeholder: "salon.example.com" })
|
|
211
|
+
] })
|
|
212
|
+
] }),
|
|
213
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 pt-4", children: [
|
|
214
|
+
/* @__PURE__ */ jsx5("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent mt-2 sm:mt-0", onClick: () => onOpenChange(false), children: "Annulla" }),
|
|
215
|
+
/* @__PURE__ */ jsxs2("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50", onClick: handleSubmit, disabled: isPending, children: [
|
|
216
|
+
isPending && /* @__PURE__ */ jsx5(Loader2, { className: "w-4 h-4 animate-spin mr-2" }),
|
|
217
|
+
isEdit ? "Salva Modifiche" : "Crea Tenant"
|
|
218
|
+
] })
|
|
219
|
+
] })
|
|
220
|
+
] })
|
|
221
|
+
] });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/components/tenants/TenantDetailDialog.tsx
|
|
225
|
+
import { X } from "lucide-react";
|
|
226
|
+
import { tenantPlanColors as tenantPlanColors2, tenantPlanLabels as tenantPlanLabels2, tenantStatusColors as tenantStatusColors2, tenantStatusLabels as tenantStatusLabels2, formatDate as formatDate2 } from "@growflowstudio/growflowbooking-admin-core";
|
|
227
|
+
import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
228
|
+
function TenantDetailDialog({ isOpen, onOpenChange, tenant, isLoading }) {
|
|
229
|
+
if (!isOpen) return null;
|
|
230
|
+
return /* @__PURE__ */ jsxs3("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
231
|
+
/* @__PURE__ */ jsx6("div", { className: "fixed inset-0 bg-black/80", onClick: () => onOpenChange(false) }),
|
|
232
|
+
/* @__PURE__ */ jsxs3("div", { className: "relative z-50 w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-lg border bg-background p-6 shadow-lg", children: [
|
|
233
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between pb-4", children: [
|
|
234
|
+
/* @__PURE__ */ jsx6("h2", { className: "text-lg font-semibold", children: "Dettaglio Tenant" }),
|
|
235
|
+
/* @__PURE__ */ jsx6("button", { className: "rounded-md h-8 w-8 inline-flex items-center justify-center hover:bg-accent", onClick: () => onOpenChange(false), children: /* @__PURE__ */ jsx6(X, { className: "w-4 h-4" }) })
|
|
236
|
+
] }),
|
|
237
|
+
isLoading || !tenant ? /* @__PURE__ */ jsx6("div", { className: "space-y-4", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsx6("div", { className: "h-6 w-full animate-pulse rounded-md bg-muted" }, i)) }) : /* @__PURE__ */ jsxs3("div", { className: "space-y-6", children: [
|
|
238
|
+
/* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
239
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
240
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm text-muted-foreground", children: "Nome" }),
|
|
241
|
+
/* @__PURE__ */ jsx6("p", { className: "font-medium", children: tenant.name })
|
|
242
|
+
] }),
|
|
243
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
244
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm text-muted-foreground", children: "Slug" }),
|
|
245
|
+
/* @__PURE__ */ jsx6("p", { children: /* @__PURE__ */ jsx6("code", { className: "text-xs bg-muted px-1.5 py-0.5 rounded", children: tenant.slug }) })
|
|
246
|
+
] }),
|
|
247
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
248
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm text-muted-foreground", children: "Piano" }),
|
|
249
|
+
/* @__PURE__ */ jsx6("p", { children: /* @__PURE__ */ jsx6(StatusBadge, { label: tenantPlanLabels2[tenant.plan], colorClass: tenantPlanColors2[tenant.plan] }) })
|
|
250
|
+
] }),
|
|
251
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
252
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm text-muted-foreground", children: "Stato" }),
|
|
253
|
+
/* @__PURE__ */ jsx6("p", { children: /* @__PURE__ */ jsx6(StatusBadge, { label: tenant.is_active ? tenantStatusLabels2.active : tenantStatusLabels2.inactive, colorClass: tenant.is_active ? tenantStatusColors2.active : tenantStatusColors2.inactive }) })
|
|
254
|
+
] }),
|
|
255
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
256
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm text-muted-foreground", children: "Email" }),
|
|
257
|
+
/* @__PURE__ */ jsx6("p", { className: "text-sm", children: tenant.owner_email })
|
|
258
|
+
] }),
|
|
259
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
260
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm text-muted-foreground", children: "Dominio" }),
|
|
261
|
+
/* @__PURE__ */ jsx6("p", { className: "text-sm", children: tenant.domain || "-" })
|
|
262
|
+
] }),
|
|
263
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
264
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm text-muted-foreground", children: "Creato" }),
|
|
265
|
+
/* @__PURE__ */ jsx6("p", { className: "text-sm", children: formatDate2(tenant.created_at) })
|
|
266
|
+
] }),
|
|
267
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
268
|
+
/* @__PURE__ */ jsx6("span", { className: "text-sm text-muted-foreground", children: "Aggiornato" }),
|
|
269
|
+
/* @__PURE__ */ jsx6("p", { className: "text-sm", children: formatDate2(tenant.updated_at) })
|
|
270
|
+
] })
|
|
271
|
+
] }),
|
|
272
|
+
/* @__PURE__ */ jsxs3("div", { className: "border-t pt-4", children: [
|
|
273
|
+
/* @__PURE__ */ jsx6("h3", { className: "font-medium mb-3", children: "Statistiche" }),
|
|
274
|
+
/* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-3 gap-4", children: [
|
|
275
|
+
/* @__PURE__ */ jsxs3("div", { className: "rounded-lg border p-3 text-center", children: [
|
|
276
|
+
/* @__PURE__ */ jsx6("p", { className: "text-2xl font-bold", children: tenant.services_count }),
|
|
277
|
+
/* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground", children: "Servizi" })
|
|
278
|
+
] }),
|
|
279
|
+
/* @__PURE__ */ jsxs3("div", { className: "rounded-lg border p-3 text-center", children: [
|
|
280
|
+
/* @__PURE__ */ jsx6("p", { className: "text-2xl font-bold", children: tenant.staff_count }),
|
|
281
|
+
/* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground", children: "Staff" })
|
|
282
|
+
] }),
|
|
283
|
+
/* @__PURE__ */ jsxs3("div", { className: "rounded-lg border p-3 text-center", children: [
|
|
284
|
+
/* @__PURE__ */ jsx6("p", { className: "text-2xl font-bold", children: tenant.locations_count }),
|
|
285
|
+
/* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground", children: "Sedi" })
|
|
286
|
+
] }),
|
|
287
|
+
/* @__PURE__ */ jsxs3("div", { className: "rounded-lg border p-3 text-center", children: [
|
|
288
|
+
/* @__PURE__ */ jsx6("p", { className: "text-2xl font-bold", children: tenant.customers_count }),
|
|
289
|
+
/* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground", children: "Clienti" })
|
|
290
|
+
] }),
|
|
291
|
+
/* @__PURE__ */ jsxs3("div", { className: "rounded-lg border p-3 text-center", children: [
|
|
292
|
+
/* @__PURE__ */ jsx6("p", { className: "text-2xl font-bold", children: tenant.bookings_count }),
|
|
293
|
+
/* @__PURE__ */ jsx6("p", { className: "text-xs text-muted-foreground", children: "Prenotazioni" })
|
|
294
|
+
] })
|
|
295
|
+
] })
|
|
296
|
+
] }),
|
|
297
|
+
/* @__PURE__ */ jsxs3("div", { className: "border-t pt-4", children: [
|
|
298
|
+
/* @__PURE__ */ jsx6("h3", { className: "font-medium mb-3", children: "Limiti" }),
|
|
299
|
+
/* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-3 gap-4 text-sm", children: [
|
|
300
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
301
|
+
/* @__PURE__ */ jsx6("span", { className: "text-muted-foreground", children: "Max Servizi:" }),
|
|
302
|
+
" ",
|
|
303
|
+
tenant.max_services === -1 ? "Illimitati" : tenant.max_services
|
|
304
|
+
] }),
|
|
305
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
306
|
+
/* @__PURE__ */ jsx6("span", { className: "text-muted-foreground", children: "Max Staff:" }),
|
|
307
|
+
" ",
|
|
308
|
+
tenant.max_staff === -1 ? "Illimitati" : tenant.max_staff
|
|
309
|
+
] }),
|
|
310
|
+
/* @__PURE__ */ jsxs3("div", { children: [
|
|
311
|
+
/* @__PURE__ */ jsx6("span", { className: "text-muted-foreground", children: "Max Sedi:" }),
|
|
312
|
+
" ",
|
|
313
|
+
tenant.max_locations === -1 ? "Illimitate" : tenant.max_locations
|
|
314
|
+
] })
|
|
315
|
+
] })
|
|
316
|
+
] })
|
|
317
|
+
] })
|
|
318
|
+
] })
|
|
319
|
+
] });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/components/shared/DeleteConfirmDialog.tsx
|
|
323
|
+
import { Loader2 as Loader22 } from "lucide-react";
|
|
324
|
+
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
325
|
+
function DeleteConfirmDialog({
|
|
326
|
+
isOpen,
|
|
327
|
+
onOpenChange,
|
|
328
|
+
title = "Sei sicuro?",
|
|
329
|
+
description,
|
|
330
|
+
onConfirm,
|
|
331
|
+
isDeleting,
|
|
332
|
+
confirmLabel = "Elimina",
|
|
333
|
+
cancelLabel = "Annulla"
|
|
334
|
+
}) {
|
|
335
|
+
if (!isOpen) return null;
|
|
336
|
+
return /* @__PURE__ */ jsxs4("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
337
|
+
/* @__PURE__ */ jsx7(
|
|
338
|
+
"div",
|
|
339
|
+
{
|
|
340
|
+
className: "fixed inset-0 bg-black/80",
|
|
341
|
+
onClick: () => onOpenChange(false)
|
|
342
|
+
}
|
|
343
|
+
),
|
|
344
|
+
/* @__PURE__ */ jsxs4("div", { className: "relative z-50 w-full max-w-lg rounded-lg border bg-background p-6 shadow-lg", children: [
|
|
345
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex flex-col space-y-2 text-center sm:text-left", children: [
|
|
346
|
+
/* @__PURE__ */ jsx7("h2", { className: "text-lg font-semibold", children: title }),
|
|
347
|
+
/* @__PURE__ */ jsx7("div", { className: "text-sm text-muted-foreground", children: description })
|
|
348
|
+
] }),
|
|
349
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 mt-4", children: [
|
|
350
|
+
/* @__PURE__ */ jsx7(
|
|
351
|
+
"button",
|
|
352
|
+
{
|
|
353
|
+
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent hover:text-accent-foreground mt-2 sm:mt-0",
|
|
354
|
+
onClick: () => onOpenChange(false),
|
|
355
|
+
children: cancelLabel
|
|
356
|
+
}
|
|
357
|
+
),
|
|
358
|
+
/* @__PURE__ */ jsxs4(
|
|
359
|
+
"button",
|
|
360
|
+
{
|
|
361
|
+
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
362
|
+
onClick: onConfirm,
|
|
363
|
+
disabled: isDeleting,
|
|
364
|
+
children: [
|
|
365
|
+
isDeleting && /* @__PURE__ */ jsx7(Loader22, { className: "w-4 h-4 animate-spin mr-2" }),
|
|
366
|
+
confirmLabel
|
|
367
|
+
]
|
|
368
|
+
}
|
|
369
|
+
)
|
|
370
|
+
] })
|
|
371
|
+
] })
|
|
372
|
+
] });
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// src/components/shared/FilterBar.tsx
|
|
376
|
+
import { Search, RefreshCw } from "lucide-react";
|
|
377
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
378
|
+
function FilterBar({
|
|
379
|
+
searchPlaceholder = "Cerca...",
|
|
380
|
+
searchValue,
|
|
381
|
+
onSearchChange,
|
|
382
|
+
statusFilter,
|
|
383
|
+
onStatusFilterChange,
|
|
384
|
+
statusOptions,
|
|
385
|
+
onRefresh
|
|
386
|
+
}) {
|
|
387
|
+
return /* @__PURE__ */ jsx8("div", { className: "rounded-lg border bg-card text-card-foreground shadow-sm", children: /* @__PURE__ */ jsx8("div", { className: "p-6 pt-6", children: /* @__PURE__ */ jsxs5("div", { className: "flex flex-col sm:flex-row gap-4", children: [
|
|
388
|
+
/* @__PURE__ */ jsxs5("div", { className: "relative flex-1", children: [
|
|
389
|
+
/* @__PURE__ */ jsx8(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" }),
|
|
390
|
+
/* @__PURE__ */ jsx8(
|
|
391
|
+
"input",
|
|
392
|
+
{
|
|
393
|
+
className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 pl-9",
|
|
394
|
+
placeholder: searchPlaceholder,
|
|
395
|
+
value: searchValue,
|
|
396
|
+
onChange: (e) => onSearchChange(e.target.value)
|
|
397
|
+
}
|
|
398
|
+
)
|
|
399
|
+
] }),
|
|
400
|
+
statusOptions && onStatusFilterChange && /* @__PURE__ */ jsx8(
|
|
401
|
+
"select",
|
|
402
|
+
{
|
|
403
|
+
className: "flex h-10 w-[180px] rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
404
|
+
value: statusFilter || "all",
|
|
405
|
+
onChange: (e) => onStatusFilterChange(e.target.value),
|
|
406
|
+
children: statusOptions.map((opt) => /* @__PURE__ */ jsx8("option", { value: opt.value, children: opt.label }, opt.value))
|
|
407
|
+
}
|
|
408
|
+
),
|
|
409
|
+
/* @__PURE__ */ jsxs5(
|
|
410
|
+
"button",
|
|
411
|
+
{
|
|
412
|
+
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
413
|
+
onClick: onRefresh,
|
|
414
|
+
children: [
|
|
415
|
+
/* @__PURE__ */ jsx8(RefreshCw, { className: "w-4 h-4 mr-2" }),
|
|
416
|
+
"Aggiorna"
|
|
417
|
+
]
|
|
418
|
+
}
|
|
419
|
+
)
|
|
420
|
+
] }) }) });
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// src/components/shared/Pagination.tsx
|
|
424
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
425
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
426
|
+
function Pagination({ page, pageSize, total, onPageChange }) {
|
|
427
|
+
const totalPages = Math.ceil(total / pageSize);
|
|
428
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex justify-between items-center", children: [
|
|
429
|
+
/* @__PURE__ */ jsx9("span", { className: "text-sm text-muted-foreground", children: total > 0 ? `${(page - 1) * pageSize + 1}-${Math.min(page * pageSize, total)} di ${total}` : "Nessun risultato" }),
|
|
430
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex gap-2", children: [
|
|
431
|
+
/* @__PURE__ */ jsxs6(
|
|
432
|
+
"button",
|
|
433
|
+
{
|
|
434
|
+
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 border border-input bg-background hover:bg-accent hover:text-accent-foreground disabled:opacity-50 disabled:pointer-events-none",
|
|
435
|
+
onClick: () => onPageChange(page - 1),
|
|
436
|
+
disabled: page <= 1,
|
|
437
|
+
children: [
|
|
438
|
+
/* @__PURE__ */ jsx9(ChevronLeft, { className: "w-4 h-4 mr-1" }),
|
|
439
|
+
"Precedente"
|
|
440
|
+
]
|
|
441
|
+
}
|
|
442
|
+
),
|
|
443
|
+
/* @__PURE__ */ jsxs6(
|
|
444
|
+
"button",
|
|
445
|
+
{
|
|
446
|
+
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 border border-input bg-background hover:bg-accent hover:text-accent-foreground disabled:opacity-50 disabled:pointer-events-none",
|
|
447
|
+
onClick: () => onPageChange(page + 1),
|
|
448
|
+
disabled: page >= totalPages,
|
|
449
|
+
children: [
|
|
450
|
+
"Successiva",
|
|
451
|
+
/* @__PURE__ */ jsx9(ChevronRight, { className: "w-4 h-4 ml-1" })
|
|
452
|
+
]
|
|
453
|
+
}
|
|
454
|
+
)
|
|
455
|
+
] })
|
|
456
|
+
] });
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// src/pages/TenantsPage.tsx
|
|
460
|
+
import { Fragment as Fragment2, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
461
|
+
function TenantsPage({ wrapper: Wrapper, header }) {
|
|
462
|
+
const [search, setSearch] = useState2("");
|
|
463
|
+
const [planFilter, setPlanFilter] = useState2("all");
|
|
464
|
+
const [page, setPage] = useState2(1);
|
|
465
|
+
const pageSize = 20;
|
|
466
|
+
const [showForm, setShowForm] = useState2(false);
|
|
467
|
+
const [showDelete, setShowDelete] = useState2(false);
|
|
468
|
+
const [showDetail, setShowDetail] = useState2(false);
|
|
469
|
+
const [selectedTenant, setSelectedTenant] = useState2(null);
|
|
470
|
+
const { data, isLoading, refetch } = useAdminTenants({
|
|
471
|
+
search: search || void 0,
|
|
472
|
+
plan: planFilter !== "all" ? planFilter : void 0,
|
|
473
|
+
page,
|
|
474
|
+
page_size: pageSize
|
|
475
|
+
});
|
|
476
|
+
const { data: tenantDetail, isLoading: isLoadingDetail } = useAdminTenant(
|
|
477
|
+
showDetail && selectedTenant ? selectedTenant.id : ""
|
|
478
|
+
);
|
|
479
|
+
const createMutation = useCreateTenant();
|
|
480
|
+
const updateMutation = useUpdateTenant();
|
|
481
|
+
const deleteMutation = useDeleteTenant();
|
|
482
|
+
const tenants = data?.items || [];
|
|
483
|
+
const total = data?.total || 0;
|
|
484
|
+
const handleSubmit = (formData) => {
|
|
485
|
+
if (selectedTenant) {
|
|
486
|
+
updateMutation.mutate({ id: selectedTenant.id, data: formData }, {
|
|
487
|
+
onSuccess: () => {
|
|
488
|
+
toast.success("Tenant aggiornato con successo");
|
|
489
|
+
setShowForm(false);
|
|
490
|
+
setSelectedTenant(null);
|
|
491
|
+
},
|
|
492
|
+
onError: (err) => toast.error(`Errore: ${err.message}`)
|
|
493
|
+
});
|
|
494
|
+
} else {
|
|
495
|
+
createMutation.mutate(formData, {
|
|
496
|
+
onSuccess: () => {
|
|
497
|
+
toast.success("Tenant creato con successo");
|
|
498
|
+
setShowForm(false);
|
|
499
|
+
},
|
|
500
|
+
onError: (err) => toast.error(`Errore: ${err.message}`)
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
const handleDelete = () => {
|
|
505
|
+
if (selectedTenant) {
|
|
506
|
+
deleteMutation.mutate(selectedTenant.id, {
|
|
507
|
+
onSuccess: () => {
|
|
508
|
+
toast.success("Tenant eliminato con successo");
|
|
509
|
+
setShowDelete(false);
|
|
510
|
+
setSelectedTenant(null);
|
|
511
|
+
},
|
|
512
|
+
onError: (err) => toast.error(`Errore: ${err.message}`)
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
const content = /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
517
|
+
header,
|
|
518
|
+
/* @__PURE__ */ jsxs7("div", { className: "space-y-4", children: [
|
|
519
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between", children: [
|
|
520
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
521
|
+
/* @__PURE__ */ jsx10(Building2, { className: "w-4 h-4" }),
|
|
522
|
+
/* @__PURE__ */ jsxs7("span", { children: [
|
|
523
|
+
total,
|
|
524
|
+
" tenant"
|
|
525
|
+
] })
|
|
526
|
+
] }),
|
|
527
|
+
/* @__PURE__ */ jsxs7("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90", onClick: () => {
|
|
528
|
+
setSelectedTenant(null);
|
|
529
|
+
setShowForm(true);
|
|
530
|
+
}, children: [
|
|
531
|
+
/* @__PURE__ */ jsx10(Plus, { className: "w-4 h-4 mr-2" }),
|
|
532
|
+
"Nuovo Tenant"
|
|
533
|
+
] })
|
|
534
|
+
] }),
|
|
535
|
+
/* @__PURE__ */ jsx10(
|
|
536
|
+
FilterBar,
|
|
537
|
+
{
|
|
538
|
+
searchPlaceholder: "Cerca tenant...",
|
|
539
|
+
searchValue: search,
|
|
540
|
+
onSearchChange: (v) => {
|
|
541
|
+
setSearch(v);
|
|
542
|
+
setPage(1);
|
|
543
|
+
},
|
|
544
|
+
statusFilter: planFilter,
|
|
545
|
+
onStatusFilterChange: (v) => {
|
|
546
|
+
setPlanFilter(v);
|
|
547
|
+
setPage(1);
|
|
548
|
+
},
|
|
549
|
+
statusOptions: [
|
|
550
|
+
{ value: "all", label: "Tutti i piani" },
|
|
551
|
+
{ value: "free", label: "Free" },
|
|
552
|
+
{ value: "starter", label: "Starter" },
|
|
553
|
+
{ value: "professional", label: "Professional" },
|
|
554
|
+
{ value: "enterprise", label: "Enterprise" }
|
|
555
|
+
],
|
|
556
|
+
onRefresh: () => refetch()
|
|
557
|
+
}
|
|
558
|
+
),
|
|
559
|
+
/* @__PURE__ */ jsxs7("div", { className: "rounded-lg border bg-card text-card-foreground shadow-sm", children: [
|
|
560
|
+
/* @__PURE__ */ jsx10("div", { className: "p-0", children: /* @__PURE__ */ jsx10(
|
|
561
|
+
TenantsTable,
|
|
562
|
+
{
|
|
563
|
+
tenants,
|
|
564
|
+
isLoading,
|
|
565
|
+
onEdit: (t) => {
|
|
566
|
+
setSelectedTenant(t);
|
|
567
|
+
setShowForm(true);
|
|
568
|
+
},
|
|
569
|
+
onDelete: (t) => {
|
|
570
|
+
setSelectedTenant(t);
|
|
571
|
+
setShowDelete(true);
|
|
572
|
+
},
|
|
573
|
+
onView: (t) => {
|
|
574
|
+
setSelectedTenant(t);
|
|
575
|
+
setShowDetail(true);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
) }),
|
|
579
|
+
total > pageSize && /* @__PURE__ */ jsx10("div", { className: "p-4 border-t", children: /* @__PURE__ */ jsx10(Pagination, { page, pageSize, total, onPageChange: setPage }) })
|
|
580
|
+
] })
|
|
581
|
+
] }),
|
|
582
|
+
/* @__PURE__ */ jsx10(
|
|
583
|
+
TenantFormDialog,
|
|
584
|
+
{
|
|
585
|
+
isOpen: showForm,
|
|
586
|
+
onOpenChange: setShowForm,
|
|
587
|
+
tenant: selectedTenant,
|
|
588
|
+
onSubmit: handleSubmit,
|
|
589
|
+
isPending: createMutation.isPending || updateMutation.isPending
|
|
590
|
+
}
|
|
591
|
+
),
|
|
592
|
+
/* @__PURE__ */ jsx10(
|
|
593
|
+
TenantDetailDialog,
|
|
594
|
+
{
|
|
595
|
+
isOpen: showDetail,
|
|
596
|
+
onOpenChange: setShowDetail,
|
|
597
|
+
tenant: tenantDetail,
|
|
598
|
+
isLoading: isLoadingDetail
|
|
599
|
+
}
|
|
600
|
+
),
|
|
601
|
+
/* @__PURE__ */ jsx10(
|
|
602
|
+
DeleteConfirmDialog,
|
|
603
|
+
{
|
|
604
|
+
isOpen: showDelete,
|
|
605
|
+
onOpenChange: setShowDelete,
|
|
606
|
+
title: "Eliminare Tenant?",
|
|
607
|
+
description: /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
608
|
+
"Stai per eliminare il tenant ",
|
|
609
|
+
/* @__PURE__ */ jsx10("strong", { children: selectedTenant?.name }),
|
|
610
|
+
". Questa azione non pu\xF2 essere annullata e rimuover\xE0 tutti i dati associati."
|
|
611
|
+
] }),
|
|
612
|
+
onConfirm: handleDelete,
|
|
613
|
+
isDeleting: deleteMutation.isPending,
|
|
614
|
+
confirmLabel: "Elimina Tenant"
|
|
615
|
+
}
|
|
616
|
+
)
|
|
617
|
+
] });
|
|
618
|
+
return Wrapper ? /* @__PURE__ */ jsx10(Wrapper, { children: content }) : content;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/pages/CustomersPage.tsx
|
|
622
|
+
import { useState as useState4 } from "react";
|
|
623
|
+
import { toast as toast2 } from "sonner";
|
|
624
|
+
import { Plus as Plus2, Users } from "lucide-react";
|
|
625
|
+
import {
|
|
626
|
+
useAdminCustomers,
|
|
627
|
+
useCreateCustomer,
|
|
628
|
+
useUpdateCustomer,
|
|
629
|
+
useDeleteCustomer
|
|
630
|
+
} from "@growflowstudio/growflowbooking-admin-core";
|
|
631
|
+
|
|
632
|
+
// src/components/customers/CustomersTable.tsx
|
|
633
|
+
import { Pencil as Pencil2, Trash2 as Trash22 } from "lucide-react";
|
|
634
|
+
import { formatDate as formatDate3 } from "@growflowstudio/growflowbooking-admin-core";
|
|
635
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
636
|
+
function CustomersTable({ customers, isLoading, onEdit, onDelete }) {
|
|
637
|
+
return /* @__PURE__ */ jsxs8("table", { className: "w-full", children: [
|
|
638
|
+
/* @__PURE__ */ jsx11("thead", { children: /* @__PURE__ */ jsxs8("tr", { className: "border-b", children: [
|
|
639
|
+
/* @__PURE__ */ jsx11("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Nome" }),
|
|
640
|
+
/* @__PURE__ */ jsx11("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Email" }),
|
|
641
|
+
/* @__PURE__ */ jsx11("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Tenant" }),
|
|
642
|
+
/* @__PURE__ */ jsx11("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Stato" }),
|
|
643
|
+
/* @__PURE__ */ jsx11("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Creato" }),
|
|
644
|
+
/* @__PURE__ */ jsx11("th", { className: "h-12 px-4 text-right align-middle font-medium text-muted-foreground", children: "Azioni" })
|
|
645
|
+
] }) }),
|
|
646
|
+
/* @__PURE__ */ jsx11("tbody", { children: isLoading ? /* @__PURE__ */ jsx11(SkeletonRows, { rows: 5, columns: 6 }) : customers.length === 0 ? /* @__PURE__ */ jsx11("tr", { children: /* @__PURE__ */ jsx11("td", { colSpan: 6, className: "h-24 text-center text-muted-foreground", children: "Nessun cliente trovato." }) }) : customers.map((customer) => /* @__PURE__ */ jsxs8("tr", { className: "border-b hover:bg-muted/50", children: [
|
|
647
|
+
/* @__PURE__ */ jsxs8("td", { className: "p-4 font-medium", children: [
|
|
648
|
+
customer.first_name,
|
|
649
|
+
" ",
|
|
650
|
+
customer.last_name
|
|
651
|
+
] }),
|
|
652
|
+
/* @__PURE__ */ jsx11("td", { className: "p-4 text-sm", children: customer.email }),
|
|
653
|
+
/* @__PURE__ */ jsx11("td", { className: "p-4 text-sm text-muted-foreground", children: customer.tenant_name }),
|
|
654
|
+
/* @__PURE__ */ jsx11("td", { className: "p-4", children: /* @__PURE__ */ jsx11(
|
|
655
|
+
StatusBadge,
|
|
656
|
+
{
|
|
657
|
+
label: customer.is_active ? "Attivo" : "Inattivo",
|
|
658
|
+
colorClass: customer.is_active ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-400"
|
|
659
|
+
}
|
|
660
|
+
) }),
|
|
661
|
+
/* @__PURE__ */ jsx11("td", { className: "p-4 text-sm text-muted-foreground", children: formatDate3(customer.created_at) }),
|
|
662
|
+
/* @__PURE__ */ jsx11("td", { className: "p-4 text-right", children: /* @__PURE__ */ jsxs8("div", { className: "flex justify-end gap-1", children: [
|
|
663
|
+
/* @__PURE__ */ jsx11("button", { className: "inline-flex items-center justify-center rounded-md text-sm h-8 w-8 hover:bg-accent", onClick: () => onEdit(customer), children: /* @__PURE__ */ jsx11(Pencil2, { className: "w-4 h-4" }) }),
|
|
664
|
+
/* @__PURE__ */ jsx11("button", { className: "inline-flex items-center justify-center rounded-md text-sm h-8 w-8 hover:bg-accent text-destructive", onClick: () => onDelete(customer), children: /* @__PURE__ */ jsx11(Trash22, { className: "w-4 h-4" }) })
|
|
665
|
+
] }) })
|
|
666
|
+
] }, customer.id)) })
|
|
667
|
+
] });
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/components/customers/CustomerFormDialog.tsx
|
|
671
|
+
import { useState as useState3, useEffect as useEffect2 } from "react";
|
|
672
|
+
import { Loader2 as Loader23 } from "lucide-react";
|
|
673
|
+
import { validateRequired as validateRequired2, validateEmail as validateEmail2 } from "@growflowstudio/growflowbooking-admin-core";
|
|
674
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
675
|
+
function CustomerFormDialog({ isOpen, onOpenChange, customer, onSubmit, isPending, tenantId }) {
|
|
676
|
+
const isEdit = !!customer;
|
|
677
|
+
const [firstName, setFirstName] = useState3("");
|
|
678
|
+
const [lastName, setLastName] = useState3("");
|
|
679
|
+
const [email, setEmail] = useState3("");
|
|
680
|
+
const [phone, setPhone] = useState3("");
|
|
681
|
+
const [notes, setNotes] = useState3("");
|
|
682
|
+
const [errors, setErrors] = useState3({});
|
|
683
|
+
useEffect2(() => {
|
|
684
|
+
if (isOpen) {
|
|
685
|
+
if (customer) {
|
|
686
|
+
setFirstName(customer.first_name);
|
|
687
|
+
setLastName(customer.last_name);
|
|
688
|
+
setEmail(customer.email);
|
|
689
|
+
setPhone(customer.phone || "");
|
|
690
|
+
setNotes(customer.notes || "");
|
|
691
|
+
} else {
|
|
692
|
+
setFirstName("");
|
|
693
|
+
setLastName("");
|
|
694
|
+
setEmail("");
|
|
695
|
+
setPhone("");
|
|
696
|
+
setNotes("");
|
|
697
|
+
}
|
|
698
|
+
setErrors({});
|
|
699
|
+
}
|
|
700
|
+
}, [isOpen, customer]);
|
|
701
|
+
const handleSubmit = () => {
|
|
702
|
+
const errs = {};
|
|
703
|
+
const fnErr = validateRequired2(firstName, "Il nome");
|
|
704
|
+
if (fnErr) errs.firstName = fnErr;
|
|
705
|
+
const lnErr = validateRequired2(lastName, "Il cognome");
|
|
706
|
+
if (lnErr) errs.lastName = lnErr;
|
|
707
|
+
const emailErr = validateEmail2(email);
|
|
708
|
+
if (emailErr) errs.email = emailErr;
|
|
709
|
+
setErrors(errs);
|
|
710
|
+
if (Object.keys(errs).length > 0) return;
|
|
711
|
+
if (isEdit) {
|
|
712
|
+
onSubmit({ first_name: firstName, last_name: lastName, email, phone: phone || void 0, notes: notes || void 0 });
|
|
713
|
+
} else {
|
|
714
|
+
onSubmit({ tenant_id: tenantId || "", first_name: firstName, last_name: lastName, email, phone: phone || void 0, notes: notes || void 0 });
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
if (!isOpen) return null;
|
|
718
|
+
return /* @__PURE__ */ jsxs9("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
719
|
+
/* @__PURE__ */ jsx12("div", { className: "fixed inset-0 bg-black/80", onClick: () => onOpenChange(false) }),
|
|
720
|
+
/* @__PURE__ */ jsxs9("div", { className: "relative z-50 w-full max-w-lg max-h-[90vh] overflow-y-auto rounded-lg border bg-background p-6 shadow-lg", children: [
|
|
721
|
+
/* @__PURE__ */ jsx12("div", { className: "flex flex-col space-y-1.5 pb-4", children: /* @__PURE__ */ jsx12("h2", { className: "text-lg font-semibold", children: isEdit ? "Modifica Cliente" : "Nuovo Cliente" }) }),
|
|
722
|
+
/* @__PURE__ */ jsxs9("div", { className: "grid gap-4 py-4", children: [
|
|
723
|
+
/* @__PURE__ */ jsxs9("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
724
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
|
|
725
|
+
/* @__PURE__ */ jsx12("label", { className: "text-sm font-medium leading-none", children: "Nome *" }),
|
|
726
|
+
/* @__PURE__ */ jsx12("input", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: firstName, onChange: (e) => setFirstName(e.target.value), placeholder: "Mario" }),
|
|
727
|
+
errors.firstName && /* @__PURE__ */ jsx12("p", { className: "text-xs text-destructive", children: errors.firstName })
|
|
728
|
+
] }),
|
|
729
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
|
|
730
|
+
/* @__PURE__ */ jsx12("label", { className: "text-sm font-medium leading-none", children: "Cognome *" }),
|
|
731
|
+
/* @__PURE__ */ jsx12("input", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: lastName, onChange: (e) => setLastName(e.target.value), placeholder: "Rossi" }),
|
|
732
|
+
errors.lastName && /* @__PURE__ */ jsx12("p", { className: "text-xs text-destructive", children: errors.lastName })
|
|
733
|
+
] })
|
|
734
|
+
] }),
|
|
735
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
|
|
736
|
+
/* @__PURE__ */ jsx12("label", { className: "text-sm font-medium leading-none", children: "Email *" }),
|
|
737
|
+
/* @__PURE__ */ jsx12("input", { type: "email", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: email, onChange: (e) => setEmail(e.target.value), placeholder: "mario@example.com" }),
|
|
738
|
+
errors.email && /* @__PURE__ */ jsx12("p", { className: "text-xs text-destructive", children: errors.email })
|
|
739
|
+
] }),
|
|
740
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
|
|
741
|
+
/* @__PURE__ */ jsx12("label", { className: "text-sm font-medium leading-none", children: "Telefono" }),
|
|
742
|
+
/* @__PURE__ */ jsx12("input", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: phone, onChange: (e) => setPhone(e.target.value), placeholder: "+39 123 456 7890" })
|
|
743
|
+
] }),
|
|
744
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-2", children: [
|
|
745
|
+
/* @__PURE__ */ jsx12("label", { className: "text-sm font-medium leading-none", children: "Note" }),
|
|
746
|
+
/* @__PURE__ */ jsx12("textarea", { className: "flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: notes, onChange: (e) => setNotes(e.target.value), rows: 2 })
|
|
747
|
+
] })
|
|
748
|
+
] }),
|
|
749
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 pt-4", children: [
|
|
750
|
+
/* @__PURE__ */ jsx12("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent mt-2 sm:mt-0", onClick: () => onOpenChange(false), children: "Annulla" }),
|
|
751
|
+
/* @__PURE__ */ jsxs9("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50", onClick: handleSubmit, disabled: isPending, children: [
|
|
752
|
+
isPending && /* @__PURE__ */ jsx12(Loader23, { className: "w-4 h-4 animate-spin mr-2" }),
|
|
753
|
+
isEdit ? "Salva Modifiche" : "Crea Cliente"
|
|
754
|
+
] })
|
|
755
|
+
] })
|
|
756
|
+
] })
|
|
757
|
+
] });
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// src/pages/CustomersPage.tsx
|
|
761
|
+
import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
762
|
+
function CustomersPage({ wrapper: Wrapper, header }) {
|
|
763
|
+
const [search, setSearch] = useState4("");
|
|
764
|
+
const [page, setPage] = useState4(1);
|
|
765
|
+
const pageSize = 20;
|
|
766
|
+
const [showForm, setShowForm] = useState4(false);
|
|
767
|
+
const [showDelete, setShowDelete] = useState4(false);
|
|
768
|
+
const [selectedCustomer, setSelectedCustomer] = useState4(null);
|
|
769
|
+
const { data, isLoading, refetch } = useAdminCustomers({
|
|
770
|
+
search: search || void 0,
|
|
771
|
+
page,
|
|
772
|
+
page_size: pageSize
|
|
773
|
+
});
|
|
774
|
+
const createMutation = useCreateCustomer();
|
|
775
|
+
const updateMutation = useUpdateCustomer();
|
|
776
|
+
const deleteMutation = useDeleteCustomer();
|
|
777
|
+
const customers = data?.items || [];
|
|
778
|
+
const total = data?.total || 0;
|
|
779
|
+
const handleSubmit = (formData) => {
|
|
780
|
+
if (selectedCustomer) {
|
|
781
|
+
updateMutation.mutate({ id: selectedCustomer.id, data: formData }, {
|
|
782
|
+
onSuccess: () => {
|
|
783
|
+
toast2.success("Cliente aggiornato con successo");
|
|
784
|
+
setShowForm(false);
|
|
785
|
+
setSelectedCustomer(null);
|
|
786
|
+
},
|
|
787
|
+
onError: (err) => toast2.error(`Errore: ${err.message}`)
|
|
788
|
+
});
|
|
789
|
+
} else {
|
|
790
|
+
createMutation.mutate(formData, {
|
|
791
|
+
onSuccess: () => {
|
|
792
|
+
toast2.success("Cliente creato con successo");
|
|
793
|
+
setShowForm(false);
|
|
794
|
+
},
|
|
795
|
+
onError: (err) => toast2.error(`Errore: ${err.message}`)
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
const handleDelete = () => {
|
|
800
|
+
if (selectedCustomer) {
|
|
801
|
+
deleteMutation.mutate(selectedCustomer.id, {
|
|
802
|
+
onSuccess: () => {
|
|
803
|
+
toast2.success("Cliente eliminato con successo");
|
|
804
|
+
setShowDelete(false);
|
|
805
|
+
setSelectedCustomer(null);
|
|
806
|
+
},
|
|
807
|
+
onError: (err) => toast2.error(`Errore: ${err.message}`)
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
const content = /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
812
|
+
header,
|
|
813
|
+
/* @__PURE__ */ jsxs10("div", { className: "space-y-4", children: [
|
|
814
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between", children: [
|
|
815
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
816
|
+
/* @__PURE__ */ jsx13(Users, { className: "w-4 h-4" }),
|
|
817
|
+
/* @__PURE__ */ jsxs10("span", { children: [
|
|
818
|
+
total,
|
|
819
|
+
" clienti"
|
|
820
|
+
] })
|
|
821
|
+
] }),
|
|
822
|
+
/* @__PURE__ */ jsxs10("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90", onClick: () => {
|
|
823
|
+
setSelectedCustomer(null);
|
|
824
|
+
setShowForm(true);
|
|
825
|
+
}, children: [
|
|
826
|
+
/* @__PURE__ */ jsx13(Plus2, { className: "w-4 h-4 mr-2" }),
|
|
827
|
+
"Nuovo Cliente"
|
|
828
|
+
] })
|
|
829
|
+
] }),
|
|
830
|
+
/* @__PURE__ */ jsx13(
|
|
831
|
+
FilterBar,
|
|
832
|
+
{
|
|
833
|
+
searchPlaceholder: "Cerca clienti...",
|
|
834
|
+
searchValue: search,
|
|
835
|
+
onSearchChange: (v) => {
|
|
836
|
+
setSearch(v);
|
|
837
|
+
setPage(1);
|
|
838
|
+
},
|
|
839
|
+
onRefresh: () => refetch()
|
|
840
|
+
}
|
|
841
|
+
),
|
|
842
|
+
/* @__PURE__ */ jsxs10("div", { className: "rounded-lg border bg-card text-card-foreground shadow-sm", children: [
|
|
843
|
+
/* @__PURE__ */ jsx13("div", { className: "p-0", children: /* @__PURE__ */ jsx13(
|
|
844
|
+
CustomersTable,
|
|
845
|
+
{
|
|
846
|
+
customers,
|
|
847
|
+
isLoading,
|
|
848
|
+
onEdit: (c) => {
|
|
849
|
+
setSelectedCustomer(c);
|
|
850
|
+
setShowForm(true);
|
|
851
|
+
},
|
|
852
|
+
onDelete: (c) => {
|
|
853
|
+
setSelectedCustomer(c);
|
|
854
|
+
setShowDelete(true);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
) }),
|
|
858
|
+
total > pageSize && /* @__PURE__ */ jsx13("div", { className: "p-4 border-t", children: /* @__PURE__ */ jsx13(Pagination, { page, pageSize, total, onPageChange: setPage }) })
|
|
859
|
+
] })
|
|
860
|
+
] }),
|
|
861
|
+
/* @__PURE__ */ jsx13(
|
|
862
|
+
CustomerFormDialog,
|
|
863
|
+
{
|
|
864
|
+
isOpen: showForm,
|
|
865
|
+
onOpenChange: setShowForm,
|
|
866
|
+
customer: selectedCustomer,
|
|
867
|
+
onSubmit: handleSubmit,
|
|
868
|
+
isPending: createMutation.isPending || updateMutation.isPending
|
|
869
|
+
}
|
|
870
|
+
),
|
|
871
|
+
/* @__PURE__ */ jsx13(
|
|
872
|
+
DeleteConfirmDialog,
|
|
873
|
+
{
|
|
874
|
+
isOpen: showDelete,
|
|
875
|
+
onOpenChange: setShowDelete,
|
|
876
|
+
title: "Eliminare Cliente?",
|
|
877
|
+
description: /* @__PURE__ */ jsxs10(Fragment3, { children: [
|
|
878
|
+
"Stai per eliminare il cliente ",
|
|
879
|
+
/* @__PURE__ */ jsxs10("strong", { children: [
|
|
880
|
+
selectedCustomer?.first_name,
|
|
881
|
+
" ",
|
|
882
|
+
selectedCustomer?.last_name
|
|
883
|
+
] }),
|
|
884
|
+
". Questa azione non pu\xF2 essere annullata."
|
|
885
|
+
] }),
|
|
886
|
+
onConfirm: handleDelete,
|
|
887
|
+
isDeleting: deleteMutation.isPending,
|
|
888
|
+
confirmLabel: "Elimina Cliente"
|
|
889
|
+
}
|
|
890
|
+
)
|
|
891
|
+
] });
|
|
892
|
+
return Wrapper ? /* @__PURE__ */ jsx13(Wrapper, { children: content }) : content;
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// src/pages/SubscriptionPlansPage.tsx
|
|
896
|
+
import { useState as useState6 } from "react";
|
|
897
|
+
import { toast as toast3 } from "sonner";
|
|
898
|
+
import { Plus as Plus3, LayoutGrid } from "lucide-react";
|
|
899
|
+
import {
|
|
900
|
+
useSubscriptionPlans,
|
|
901
|
+
useCreateSubscriptionPlan,
|
|
902
|
+
useUpdateSubscriptionPlan,
|
|
903
|
+
useDeleteSubscriptionPlan
|
|
904
|
+
} from "@growflowstudio/growflowbooking-admin-core";
|
|
905
|
+
|
|
906
|
+
// src/components/subscription-plans/SubscriptionPlansTable.tsx
|
|
907
|
+
import { Pencil as Pencil3, Trash2 as Trash23 } from "lucide-react";
|
|
908
|
+
import { formatPrice } from "@growflowstudio/growflowbooking-admin-core";
|
|
909
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
910
|
+
function SubscriptionPlansTable({ plans, isLoading, onEdit, onDelete }) {
|
|
911
|
+
return /* @__PURE__ */ jsxs11("table", { className: "w-full", children: [
|
|
912
|
+
/* @__PURE__ */ jsx14("thead", { children: /* @__PURE__ */ jsxs11("tr", { className: "border-b", children: [
|
|
913
|
+
/* @__PURE__ */ jsx14("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Nome" }),
|
|
914
|
+
/* @__PURE__ */ jsx14("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Slug" }),
|
|
915
|
+
/* @__PURE__ */ jsx14("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Prezzo Mensile" }),
|
|
916
|
+
/* @__PURE__ */ jsx14("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Prezzo Annuale" }),
|
|
917
|
+
/* @__PURE__ */ jsx14("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Stato" }),
|
|
918
|
+
/* @__PURE__ */ jsx14("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Trial" }),
|
|
919
|
+
/* @__PURE__ */ jsx14("th", { className: "h-12 px-4 text-right align-middle font-medium text-muted-foreground", children: "Azioni" })
|
|
920
|
+
] }) }),
|
|
921
|
+
/* @__PURE__ */ jsx14("tbody", { children: isLoading ? /* @__PURE__ */ jsx14(SkeletonRows, { rows: 5, columns: 7 }) : plans.length === 0 ? /* @__PURE__ */ jsx14("tr", { children: /* @__PURE__ */ jsx14("td", { colSpan: 7, className: "h-24 text-center text-muted-foreground", children: "Nessun piano trovato." }) }) : plans.map((plan) => /* @__PURE__ */ jsxs11("tr", { className: "border-b hover:bg-muted/50", children: [
|
|
922
|
+
/* @__PURE__ */ jsx14("td", { className: "p-4 font-medium", children: plan.name }),
|
|
923
|
+
/* @__PURE__ */ jsx14("td", { className: "p-4", children: /* @__PURE__ */ jsx14("code", { className: "text-xs bg-muted px-1.5 py-0.5 rounded", children: plan.slug }) }),
|
|
924
|
+
/* @__PURE__ */ jsx14("td", { className: "p-4 text-sm", children: formatPrice(plan.price_monthly, plan.currency) }),
|
|
925
|
+
/* @__PURE__ */ jsx14("td", { className: "p-4 text-sm", children: formatPrice(plan.price_yearly, plan.currency) }),
|
|
926
|
+
/* @__PURE__ */ jsx14("td", { className: "p-4", children: /* @__PURE__ */ jsx14(
|
|
927
|
+
StatusBadge,
|
|
928
|
+
{
|
|
929
|
+
label: plan.is_active ? "Attivo" : "Inattivo",
|
|
930
|
+
colorClass: plan.is_active ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-400"
|
|
931
|
+
}
|
|
932
|
+
) }),
|
|
933
|
+
/* @__PURE__ */ jsxs11("td", { className: "p-4 text-sm text-muted-foreground", children: [
|
|
934
|
+
plan.trial_days,
|
|
935
|
+
" giorni"
|
|
936
|
+
] }),
|
|
937
|
+
/* @__PURE__ */ jsx14("td", { className: "p-4 text-right", children: /* @__PURE__ */ jsxs11("div", { className: "flex justify-end gap-1", children: [
|
|
938
|
+
/* @__PURE__ */ jsx14("button", { className: "inline-flex items-center justify-center rounded-md text-sm h-8 w-8 hover:bg-accent", onClick: () => onEdit(plan), children: /* @__PURE__ */ jsx14(Pencil3, { className: "w-4 h-4" }) }),
|
|
939
|
+
/* @__PURE__ */ jsx14("button", { className: "inline-flex items-center justify-center rounded-md text-sm h-8 w-8 hover:bg-accent text-destructive", onClick: () => onDelete(plan), children: /* @__PURE__ */ jsx14(Trash23, { className: "w-4 h-4" }) })
|
|
940
|
+
] }) })
|
|
941
|
+
] }, plan.id)) })
|
|
942
|
+
] });
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// src/components/subscription-plans/SubscriptionPlanFormDialog.tsx
|
|
946
|
+
import { useState as useState5, useEffect as useEffect3 } from "react";
|
|
947
|
+
import { Loader2 as Loader24 } from "lucide-react";
|
|
948
|
+
import { validateRequired as validateRequired3, validateSlug as validateSlug2 } from "@growflowstudio/growflowbooking-admin-core";
|
|
949
|
+
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
950
|
+
function SubscriptionPlanFormDialog({ isOpen, onOpenChange, plan, onSubmit, isPending }) {
|
|
951
|
+
const isEdit = !!plan;
|
|
952
|
+
const [slug, setSlug] = useState5("");
|
|
953
|
+
const [name, setName] = useState5("");
|
|
954
|
+
const [description, setDescription] = useState5("");
|
|
955
|
+
const [pricingModel, setPricingModel] = useState5("flat");
|
|
956
|
+
const [priceMonthly, setPriceMonthly] = useState5("");
|
|
957
|
+
const [priceYearly, setPriceYearly] = useState5("");
|
|
958
|
+
const [currency, setCurrency] = useState5("eur");
|
|
959
|
+
const [trialDays, setTrialDays] = useState5(14);
|
|
960
|
+
const [isActive, setIsActive] = useState5(true);
|
|
961
|
+
const [errors, setErrors] = useState5({});
|
|
962
|
+
useEffect3(() => {
|
|
963
|
+
if (isOpen) {
|
|
964
|
+
if (plan) {
|
|
965
|
+
setSlug(plan.slug);
|
|
966
|
+
setName(plan.name);
|
|
967
|
+
setDescription(plan.description || "");
|
|
968
|
+
setPricingModel(plan.pricing_model);
|
|
969
|
+
setPriceMonthly(plan.price_monthly?.toString() || "");
|
|
970
|
+
setPriceYearly(plan.price_yearly?.toString() || "");
|
|
971
|
+
setCurrency(plan.currency);
|
|
972
|
+
setTrialDays(plan.trial_days);
|
|
973
|
+
setIsActive(plan.is_active);
|
|
974
|
+
} else {
|
|
975
|
+
setSlug("");
|
|
976
|
+
setName("");
|
|
977
|
+
setDescription("");
|
|
978
|
+
setPricingModel("flat");
|
|
979
|
+
setPriceMonthly("");
|
|
980
|
+
setPriceYearly("");
|
|
981
|
+
setCurrency("eur");
|
|
982
|
+
setTrialDays(14);
|
|
983
|
+
setIsActive(true);
|
|
984
|
+
}
|
|
985
|
+
setErrors({});
|
|
986
|
+
}
|
|
987
|
+
}, [isOpen, plan]);
|
|
988
|
+
const handleSubmit = () => {
|
|
989
|
+
const errs = {};
|
|
990
|
+
if (!isEdit) {
|
|
991
|
+
const slugErr = validateSlug2(slug);
|
|
992
|
+
if (slugErr) errs.slug = slugErr;
|
|
993
|
+
}
|
|
994
|
+
const nameErr = validateRequired3(name, "Il nome");
|
|
995
|
+
if (nameErr) errs.name = nameErr;
|
|
996
|
+
setErrors(errs);
|
|
997
|
+
if (Object.keys(errs).length > 0) return;
|
|
998
|
+
const data = {
|
|
999
|
+
...isEdit ? {} : { slug },
|
|
1000
|
+
name,
|
|
1001
|
+
description: description || void 0,
|
|
1002
|
+
pricing_model: pricingModel,
|
|
1003
|
+
price_monthly: priceMonthly ? parseFloat(priceMonthly) : void 0,
|
|
1004
|
+
price_yearly: priceYearly ? parseFloat(priceYearly) : void 0,
|
|
1005
|
+
currency,
|
|
1006
|
+
trial_days: trialDays,
|
|
1007
|
+
is_active: isActive
|
|
1008
|
+
};
|
|
1009
|
+
onSubmit(data);
|
|
1010
|
+
};
|
|
1011
|
+
if (!isOpen) return null;
|
|
1012
|
+
return /* @__PURE__ */ jsxs12("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
1013
|
+
/* @__PURE__ */ jsx15("div", { className: "fixed inset-0 bg-black/80", onClick: () => onOpenChange(false) }),
|
|
1014
|
+
/* @__PURE__ */ jsxs12("div", { className: "relative z-50 w-full max-w-xl max-h-[90vh] overflow-y-auto rounded-lg border bg-background p-6 shadow-lg", children: [
|
|
1015
|
+
/* @__PURE__ */ jsx15("div", { className: "flex flex-col space-y-1.5 pb-4", children: /* @__PURE__ */ jsx15("h2", { className: "text-lg font-semibold", children: isEdit ? "Modifica Piano" : "Nuovo Piano" }) }),
|
|
1016
|
+
/* @__PURE__ */ jsxs12("div", { className: "grid gap-4 py-4", children: [
|
|
1017
|
+
!isEdit && /* @__PURE__ */ jsxs12("div", { className: "space-y-2", children: [
|
|
1018
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium leading-none", children: "Slug *" }),
|
|
1019
|
+
/* @__PURE__ */ jsx15("input", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: slug, onChange: (e) => setSlug(e.target.value), placeholder: "pro-monthly" }),
|
|
1020
|
+
errors.slug && /* @__PURE__ */ jsx15("p", { className: "text-xs text-destructive", children: errors.slug })
|
|
1021
|
+
] }),
|
|
1022
|
+
/* @__PURE__ */ jsxs12("div", { className: "space-y-2", children: [
|
|
1023
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium leading-none", children: "Nome *" }),
|
|
1024
|
+
/* @__PURE__ */ jsx15("input", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: name, onChange: (e) => setName(e.target.value), placeholder: "Professional" }),
|
|
1025
|
+
errors.name && /* @__PURE__ */ jsx15("p", { className: "text-xs text-destructive", children: errors.name })
|
|
1026
|
+
] }),
|
|
1027
|
+
/* @__PURE__ */ jsxs12("div", { className: "space-y-2", children: [
|
|
1028
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium leading-none", children: "Descrizione" }),
|
|
1029
|
+
/* @__PURE__ */ jsx15("textarea", { className: "flex min-h-[60px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: description, onChange: (e) => setDescription(e.target.value), rows: 2 })
|
|
1030
|
+
] }),
|
|
1031
|
+
/* @__PURE__ */ jsxs12("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1032
|
+
/* @__PURE__ */ jsxs12("div", { className: "space-y-2", children: [
|
|
1033
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium leading-none", children: "Prezzo Mensile" }),
|
|
1034
|
+
/* @__PURE__ */ jsx15("input", { type: "number", step: "0.01", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: priceMonthly, onChange: (e) => setPriceMonthly(e.target.value), placeholder: "29.00" })
|
|
1035
|
+
] }),
|
|
1036
|
+
/* @__PURE__ */ jsxs12("div", { className: "space-y-2", children: [
|
|
1037
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium leading-none", children: "Prezzo Annuale" }),
|
|
1038
|
+
/* @__PURE__ */ jsx15("input", { type: "number", step: "0.01", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: priceYearly, onChange: (e) => setPriceYearly(e.target.value), placeholder: "290.00" })
|
|
1039
|
+
] })
|
|
1040
|
+
] }),
|
|
1041
|
+
/* @__PURE__ */ jsxs12("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1042
|
+
/* @__PURE__ */ jsxs12("div", { className: "space-y-2", children: [
|
|
1043
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium leading-none", children: "Valuta" }),
|
|
1044
|
+
/* @__PURE__ */ jsxs12("select", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: currency, onChange: (e) => setCurrency(e.target.value), children: [
|
|
1045
|
+
/* @__PURE__ */ jsx15("option", { value: "eur", children: "EUR" }),
|
|
1046
|
+
/* @__PURE__ */ jsx15("option", { value: "usd", children: "USD" }),
|
|
1047
|
+
/* @__PURE__ */ jsx15("option", { value: "gbp", children: "GBP" })
|
|
1048
|
+
] })
|
|
1049
|
+
] }),
|
|
1050
|
+
/* @__PURE__ */ jsxs12("div", { className: "space-y-2", children: [
|
|
1051
|
+
/* @__PURE__ */ jsx15("label", { className: "text-sm font-medium leading-none", children: "Giorni Prova" }),
|
|
1052
|
+
/* @__PURE__ */ jsx15("input", { type: "number", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: trialDays, onChange: (e) => setTrialDays(parseInt(e.target.value) || 0) })
|
|
1053
|
+
] })
|
|
1054
|
+
] }),
|
|
1055
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
|
|
1056
|
+
/* @__PURE__ */ jsx15(
|
|
1057
|
+
"button",
|
|
1058
|
+
{
|
|
1059
|
+
type: "button",
|
|
1060
|
+
role: "switch",
|
|
1061
|
+
"aria-checked": isActive,
|
|
1062
|
+
onClick: () => setIsActive(!isActive),
|
|
1063
|
+
className: `inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors ${isActive ? "bg-primary" : "bg-input"}`,
|
|
1064
|
+
children: /* @__PURE__ */ jsx15("span", { className: `pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg transition-transform ${isActive ? "translate-x-4" : "translate-x-0"}` })
|
|
1065
|
+
}
|
|
1066
|
+
),
|
|
1067
|
+
/* @__PURE__ */ jsx15("span", { className: "text-sm font-medium leading-none", children: "Piano Attivo" })
|
|
1068
|
+
] })
|
|
1069
|
+
] }),
|
|
1070
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2 pt-4", children: [
|
|
1071
|
+
/* @__PURE__ */ jsx15("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 border border-input bg-background hover:bg-accent mt-2 sm:mt-0", onClick: () => onOpenChange(false), children: "Annulla" }),
|
|
1072
|
+
/* @__PURE__ */ jsxs12("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50", onClick: handleSubmit, disabled: isPending, children: [
|
|
1073
|
+
isPending && /* @__PURE__ */ jsx15(Loader24, { className: "w-4 h-4 animate-spin mr-2" }),
|
|
1074
|
+
isEdit ? "Salva Modifiche" : "Crea Piano"
|
|
1075
|
+
] })
|
|
1076
|
+
] })
|
|
1077
|
+
] })
|
|
1078
|
+
] });
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// src/pages/SubscriptionPlansPage.tsx
|
|
1082
|
+
import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1083
|
+
function SubscriptionPlansPage({ wrapper: Wrapper, header }) {
|
|
1084
|
+
const [showForm, setShowForm] = useState6(false);
|
|
1085
|
+
const [showDelete, setShowDelete] = useState6(false);
|
|
1086
|
+
const [selectedPlan, setSelectedPlan] = useState6(null);
|
|
1087
|
+
const { data, isLoading, refetch } = useSubscriptionPlans(true);
|
|
1088
|
+
const createMutation = useCreateSubscriptionPlan();
|
|
1089
|
+
const updateMutation = useUpdateSubscriptionPlan();
|
|
1090
|
+
const deleteMutation = useDeleteSubscriptionPlan();
|
|
1091
|
+
const plans = data?.items || [];
|
|
1092
|
+
const handleSubmit = (formData) => {
|
|
1093
|
+
if (selectedPlan) {
|
|
1094
|
+
updateMutation.mutate({ id: selectedPlan.id, data: formData }, {
|
|
1095
|
+
onSuccess: () => {
|
|
1096
|
+
toast3.success("Piano aggiornato con successo");
|
|
1097
|
+
setShowForm(false);
|
|
1098
|
+
setSelectedPlan(null);
|
|
1099
|
+
},
|
|
1100
|
+
onError: (err) => toast3.error(`Errore: ${err.message}`)
|
|
1101
|
+
});
|
|
1102
|
+
} else {
|
|
1103
|
+
createMutation.mutate(formData, {
|
|
1104
|
+
onSuccess: () => {
|
|
1105
|
+
toast3.success("Piano creato con successo");
|
|
1106
|
+
setShowForm(false);
|
|
1107
|
+
},
|
|
1108
|
+
onError: (err) => toast3.error(`Errore: ${err.message}`)
|
|
1109
|
+
});
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
const handleDelete = () => {
|
|
1113
|
+
if (selectedPlan) {
|
|
1114
|
+
deleteMutation.mutate(selectedPlan.id, {
|
|
1115
|
+
onSuccess: () => {
|
|
1116
|
+
toast3.success("Piano eliminato con successo");
|
|
1117
|
+
setShowDelete(false);
|
|
1118
|
+
setSelectedPlan(null);
|
|
1119
|
+
},
|
|
1120
|
+
onError: (err) => toast3.error(`Errore: ${err.message}`)
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
};
|
|
1124
|
+
const content = /* @__PURE__ */ jsxs13(Fragment4, { children: [
|
|
1125
|
+
header,
|
|
1126
|
+
/* @__PURE__ */ jsxs13("div", { className: "space-y-4", children: [
|
|
1127
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex items-center justify-between", children: [
|
|
1128
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1129
|
+
/* @__PURE__ */ jsx16(LayoutGrid, { className: "w-4 h-4" }),
|
|
1130
|
+
/* @__PURE__ */ jsxs13("span", { children: [
|
|
1131
|
+
plans.length,
|
|
1132
|
+
" piani configurati"
|
|
1133
|
+
] })
|
|
1134
|
+
] }),
|
|
1135
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex gap-2", children: [
|
|
1136
|
+
/* @__PURE__ */ jsx16("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 border border-input bg-background hover:bg-accent", onClick: () => refetch(), children: "Aggiorna" }),
|
|
1137
|
+
/* @__PURE__ */ jsxs13("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90", onClick: () => {
|
|
1138
|
+
setSelectedPlan(null);
|
|
1139
|
+
setShowForm(true);
|
|
1140
|
+
}, children: [
|
|
1141
|
+
/* @__PURE__ */ jsx16(Plus3, { className: "w-4 h-4 mr-2" }),
|
|
1142
|
+
"Nuovo Piano"
|
|
1143
|
+
] })
|
|
1144
|
+
] })
|
|
1145
|
+
] }),
|
|
1146
|
+
/* @__PURE__ */ jsx16("div", { className: "rounded-lg border bg-card text-card-foreground shadow-sm", children: /* @__PURE__ */ jsx16("div", { className: "p-0", children: /* @__PURE__ */ jsx16(
|
|
1147
|
+
SubscriptionPlansTable,
|
|
1148
|
+
{
|
|
1149
|
+
plans,
|
|
1150
|
+
isLoading,
|
|
1151
|
+
onEdit: (p) => {
|
|
1152
|
+
setSelectedPlan(p);
|
|
1153
|
+
setShowForm(true);
|
|
1154
|
+
},
|
|
1155
|
+
onDelete: (p) => {
|
|
1156
|
+
setSelectedPlan(p);
|
|
1157
|
+
setShowDelete(true);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
) }) })
|
|
1161
|
+
] }),
|
|
1162
|
+
/* @__PURE__ */ jsx16(
|
|
1163
|
+
SubscriptionPlanFormDialog,
|
|
1164
|
+
{
|
|
1165
|
+
isOpen: showForm,
|
|
1166
|
+
onOpenChange: setShowForm,
|
|
1167
|
+
plan: selectedPlan,
|
|
1168
|
+
onSubmit: handleSubmit,
|
|
1169
|
+
isPending: createMutation.isPending || updateMutation.isPending
|
|
1170
|
+
}
|
|
1171
|
+
),
|
|
1172
|
+
/* @__PURE__ */ jsx16(
|
|
1173
|
+
DeleteConfirmDialog,
|
|
1174
|
+
{
|
|
1175
|
+
isOpen: showDelete,
|
|
1176
|
+
onOpenChange: setShowDelete,
|
|
1177
|
+
title: "Eliminare Piano?",
|
|
1178
|
+
description: /* @__PURE__ */ jsxs13(Fragment4, { children: [
|
|
1179
|
+
"Stai per eliminare il piano ",
|
|
1180
|
+
/* @__PURE__ */ jsx16("strong", { children: selectedPlan?.name }),
|
|
1181
|
+
" (",
|
|
1182
|
+
/* @__PURE__ */ jsx16("code", { children: selectedPlan?.slug }),
|
|
1183
|
+
"). Questa azione non pu\xF2 essere annullata."
|
|
1184
|
+
] }),
|
|
1185
|
+
onConfirm: handleDelete,
|
|
1186
|
+
isDeleting: deleteMutation.isPending,
|
|
1187
|
+
confirmLabel: "Elimina Piano"
|
|
1188
|
+
}
|
|
1189
|
+
)
|
|
1190
|
+
] });
|
|
1191
|
+
return Wrapper ? /* @__PURE__ */ jsx16(Wrapper, { children: content }) : content;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// src/pages/BillingPlansPage.tsx
|
|
1195
|
+
import { toast as toast4 } from "sonner";
|
|
1196
|
+
import { CreditCard, RefreshCw as RefreshCw2, CloudUpload } from "lucide-react";
|
|
1197
|
+
import {
|
|
1198
|
+
useBillingPlans,
|
|
1199
|
+
useSyncBillingPlanToStripe,
|
|
1200
|
+
formatPrice as formatPrice2
|
|
1201
|
+
} from "@growflowstudio/growflowbooking-admin-core";
|
|
1202
|
+
import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1203
|
+
function BillingPlansPage({ wrapper: Wrapper, header }) {
|
|
1204
|
+
const { data, isLoading, refetch } = useBillingPlans();
|
|
1205
|
+
const syncMutation = useSyncBillingPlanToStripe();
|
|
1206
|
+
const plans = data?.items || [];
|
|
1207
|
+
const content = /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
1208
|
+
header,
|
|
1209
|
+
/* @__PURE__ */ jsxs14("div", { className: "space-y-4", children: [
|
|
1210
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center justify-between", children: [
|
|
1211
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1212
|
+
/* @__PURE__ */ jsx17(CreditCard, { className: "w-4 h-4" }),
|
|
1213
|
+
/* @__PURE__ */ jsxs14("span", { children: [
|
|
1214
|
+
plans.length,
|
|
1215
|
+
" piani billing (GrowFlow)"
|
|
1216
|
+
] })
|
|
1217
|
+
] }),
|
|
1218
|
+
/* @__PURE__ */ jsxs14("button", { className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 border border-input bg-background hover:bg-accent", onClick: () => refetch(), children: [
|
|
1219
|
+
/* @__PURE__ */ jsx17(RefreshCw2, { className: "w-4 h-4 mr-2" }),
|
|
1220
|
+
"Aggiorna"
|
|
1221
|
+
] })
|
|
1222
|
+
] }),
|
|
1223
|
+
/* @__PURE__ */ jsx17("div", { className: "rounded-lg border bg-card text-card-foreground shadow-sm", children: /* @__PURE__ */ jsx17("div", { className: "p-0", children: /* @__PURE__ */ jsxs14("table", { className: "w-full", children: [
|
|
1224
|
+
/* @__PURE__ */ jsx17("thead", { children: /* @__PURE__ */ jsxs14("tr", { className: "border-b", children: [
|
|
1225
|
+
/* @__PURE__ */ jsx17("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Nome" }),
|
|
1226
|
+
/* @__PURE__ */ jsx17("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Slug" }),
|
|
1227
|
+
/* @__PURE__ */ jsx17("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Mensile" }),
|
|
1228
|
+
/* @__PURE__ */ jsx17("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Annuale" }),
|
|
1229
|
+
/* @__PURE__ */ jsx17("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Stato" }),
|
|
1230
|
+
/* @__PURE__ */ jsx17("th", { className: "h-12 px-4 text-right align-middle font-medium text-muted-foreground", children: "Azioni" })
|
|
1231
|
+
] }) }),
|
|
1232
|
+
/* @__PURE__ */ jsx17("tbody", { children: isLoading ? /* @__PURE__ */ jsx17(SkeletonRows, { rows: 5, columns: 6 }) : plans.length === 0 ? /* @__PURE__ */ jsx17("tr", { children: /* @__PURE__ */ jsx17("td", { colSpan: 6, className: "h-24 text-center text-muted-foreground", children: "Nessun piano billing configurato." }) }) : plans.map((plan) => /* @__PURE__ */ jsxs14("tr", { className: "border-b hover:bg-muted/50", children: [
|
|
1233
|
+
/* @__PURE__ */ jsx17("td", { className: "p-4 font-medium", children: plan.name }),
|
|
1234
|
+
/* @__PURE__ */ jsx17("td", { className: "p-4", children: /* @__PURE__ */ jsx17("code", { className: "text-xs bg-muted px-1.5 py-0.5 rounded", children: plan.slug }) }),
|
|
1235
|
+
/* @__PURE__ */ jsx17("td", { className: "p-4 text-sm", children: formatPrice2(plan.priceMonthly, plan.currency) }),
|
|
1236
|
+
/* @__PURE__ */ jsx17("td", { className: "p-4 text-sm", children: formatPrice2(plan.priceYearly, plan.currency) }),
|
|
1237
|
+
/* @__PURE__ */ jsx17("td", { className: "p-4", children: /* @__PURE__ */ jsx17(
|
|
1238
|
+
StatusBadge,
|
|
1239
|
+
{
|
|
1240
|
+
label: plan.isActive ? "Attivo" : "Inattivo",
|
|
1241
|
+
colorClass: plan.isActive ? "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400" : "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-400"
|
|
1242
|
+
}
|
|
1243
|
+
) }),
|
|
1244
|
+
/* @__PURE__ */ jsx17("td", { className: "p-4 text-right", children: /* @__PURE__ */ jsxs14(
|
|
1245
|
+
"button",
|
|
1246
|
+
{
|
|
1247
|
+
className: "inline-flex items-center justify-center rounded-md text-sm h-8 px-2 hover:bg-accent disabled:opacity-50",
|
|
1248
|
+
onClick: () => syncMutation.mutate(plan.id, {
|
|
1249
|
+
onSuccess: () => toast4.success("Piano sincronizzato con Stripe"),
|
|
1250
|
+
onError: (err) => toast4.error(`Errore sync: ${err.message}`)
|
|
1251
|
+
}),
|
|
1252
|
+
disabled: syncMutation.isPending,
|
|
1253
|
+
children: [
|
|
1254
|
+
/* @__PURE__ */ jsx17(CloudUpload, { className: "w-4 h-4 mr-1" }),
|
|
1255
|
+
"Sync"
|
|
1256
|
+
]
|
|
1257
|
+
}
|
|
1258
|
+
) })
|
|
1259
|
+
] }, plan.id)) })
|
|
1260
|
+
] }) }) })
|
|
1261
|
+
] })
|
|
1262
|
+
] });
|
|
1263
|
+
return Wrapper ? /* @__PURE__ */ jsx17(Wrapper, { children: content }) : content;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// src/pages/BillingSubscriptionsPage.tsx
|
|
1267
|
+
import { useState as useState7 } from "react";
|
|
1268
|
+
import { toast as toast5 } from "sonner";
|
|
1269
|
+
import { CreditCard as CreditCard2 } from "lucide-react";
|
|
1270
|
+
import {
|
|
1271
|
+
useBillingSubscriptions,
|
|
1272
|
+
useCancelBillingSubscription,
|
|
1273
|
+
formatDate as formatDate4,
|
|
1274
|
+
billingSubscriptionStatusColors as billingSubscriptionStatusColors2,
|
|
1275
|
+
billingSubscriptionStatusLabels
|
|
1276
|
+
} from "@growflowstudio/growflowbooking-admin-core";
|
|
1277
|
+
import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1278
|
+
function BillingSubscriptionsPage({ wrapper: Wrapper, header }) {
|
|
1279
|
+
const [search, setSearch] = useState7("");
|
|
1280
|
+
const [statusFilter, setStatusFilter] = useState7("all");
|
|
1281
|
+
const { data, isLoading, refetch } = useBillingSubscriptions({
|
|
1282
|
+
search: search || void 0,
|
|
1283
|
+
status: statusFilter !== "all" ? statusFilter : void 0
|
|
1284
|
+
});
|
|
1285
|
+
const cancelMutation = useCancelBillingSubscription();
|
|
1286
|
+
const subscriptions = data?.items || [];
|
|
1287
|
+
const content = /* @__PURE__ */ jsxs15(Fragment6, { children: [
|
|
1288
|
+
header,
|
|
1289
|
+
/* @__PURE__ */ jsxs15("div", { className: "space-y-4", children: [
|
|
1290
|
+
/* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1291
|
+
/* @__PURE__ */ jsx18(CreditCard2, { className: "w-4 h-4" }),
|
|
1292
|
+
/* @__PURE__ */ jsxs15("span", { children: [
|
|
1293
|
+
data?.total || 0,
|
|
1294
|
+
" abbonamenti billing"
|
|
1295
|
+
] })
|
|
1296
|
+
] }),
|
|
1297
|
+
/* @__PURE__ */ jsx18(
|
|
1298
|
+
FilterBar,
|
|
1299
|
+
{
|
|
1300
|
+
searchPlaceholder: "Cerca abbonamenti...",
|
|
1301
|
+
searchValue: search,
|
|
1302
|
+
onSearchChange: setSearch,
|
|
1303
|
+
statusFilter,
|
|
1304
|
+
onStatusFilterChange: setStatusFilter,
|
|
1305
|
+
statusOptions: [
|
|
1306
|
+
{ value: "all", label: "Tutti gli stati" },
|
|
1307
|
+
{ value: "active", label: "Attivi" },
|
|
1308
|
+
{ value: "trialing", label: "In Prova" },
|
|
1309
|
+
{ value: "past_due", label: "Scaduti" },
|
|
1310
|
+
{ value: "canceled", label: "Cancellati" }
|
|
1311
|
+
],
|
|
1312
|
+
onRefresh: () => refetch()
|
|
1313
|
+
}
|
|
1314
|
+
),
|
|
1315
|
+
/* @__PURE__ */ jsx18("div", { className: "rounded-lg border bg-card text-card-foreground shadow-sm", children: /* @__PURE__ */ jsx18("div", { className: "p-0", children: /* @__PURE__ */ jsxs15("table", { className: "w-full", children: [
|
|
1316
|
+
/* @__PURE__ */ jsx18("thead", { children: /* @__PURE__ */ jsxs15("tr", { className: "border-b", children: [
|
|
1317
|
+
/* @__PURE__ */ jsx18("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Subscriber" }),
|
|
1318
|
+
/* @__PURE__ */ jsx18("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Piano" }),
|
|
1319
|
+
/* @__PURE__ */ jsx18("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Ciclo" }),
|
|
1320
|
+
/* @__PURE__ */ jsx18("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Stato" }),
|
|
1321
|
+
/* @__PURE__ */ jsx18("th", { className: "h-12 px-4 text-left align-middle font-medium text-muted-foreground", children: "Periodo Corrente" }),
|
|
1322
|
+
/* @__PURE__ */ jsx18("th", { className: "h-12 px-4 text-right align-middle font-medium text-muted-foreground", children: "Azioni" })
|
|
1323
|
+
] }) }),
|
|
1324
|
+
/* @__PURE__ */ jsx18("tbody", { children: isLoading ? /* @__PURE__ */ jsx18(SkeletonRows, { rows: 5, columns: 6 }) : subscriptions.length === 0 ? /* @__PURE__ */ jsx18("tr", { children: /* @__PURE__ */ jsx18("td", { colSpan: 6, className: "h-24 text-center text-muted-foreground", children: "Nessun abbonamento trovato." }) }) : subscriptions.map((sub) => /* @__PURE__ */ jsxs15("tr", { className: "border-b hover:bg-muted/50", children: [
|
|
1325
|
+
/* @__PURE__ */ jsxs15("td", { className: "p-4", children: [
|
|
1326
|
+
/* @__PURE__ */ jsx18("div", { className: "font-medium", children: sub.subscriberName || sub.subscriberEmail }),
|
|
1327
|
+
/* @__PURE__ */ jsx18("div", { className: "text-xs text-muted-foreground", children: sub.subscriberEmail })
|
|
1328
|
+
] }),
|
|
1329
|
+
/* @__PURE__ */ jsx18("td", { className: "p-4", children: /* @__PURE__ */ jsx18("code", { className: "text-xs bg-muted px-1.5 py-0.5 rounded", children: sub.planSlug }) }),
|
|
1330
|
+
/* @__PURE__ */ jsx18("td", { className: "p-4 text-sm capitalize", children: sub.billingCycle }),
|
|
1331
|
+
/* @__PURE__ */ jsx18("td", { className: "p-4", children: /* @__PURE__ */ jsx18(
|
|
1332
|
+
StatusBadge,
|
|
1333
|
+
{
|
|
1334
|
+
label: billingSubscriptionStatusLabels[sub.status],
|
|
1335
|
+
colorClass: billingSubscriptionStatusColors2[sub.status]
|
|
1336
|
+
}
|
|
1337
|
+
) }),
|
|
1338
|
+
/* @__PURE__ */ jsx18("td", { className: "p-4 text-sm text-muted-foreground", children: sub.currentPeriodStart && sub.currentPeriodEnd ? `${formatDate4(sub.currentPeriodStart)} - ${formatDate4(sub.currentPeriodEnd)}` : "-" }),
|
|
1339
|
+
/* @__PURE__ */ jsx18("td", { className: "p-4 text-right", children: sub.status === "active" && /* @__PURE__ */ jsx18(
|
|
1340
|
+
"button",
|
|
1341
|
+
{
|
|
1342
|
+
className: "inline-flex items-center justify-center rounded-md text-sm h-8 px-2 hover:bg-accent text-destructive disabled:opacity-50",
|
|
1343
|
+
onClick: () => cancelMutation.mutate({ id: sub.id, atPeriodEnd: true }, {
|
|
1344
|
+
onSuccess: () => toast5.success("Abbonamento cancellato a fine periodo"),
|
|
1345
|
+
onError: (err) => toast5.error(`Errore: ${err.message}`)
|
|
1346
|
+
}),
|
|
1347
|
+
disabled: cancelMutation.isPending,
|
|
1348
|
+
children: "Cancella"
|
|
1349
|
+
}
|
|
1350
|
+
) })
|
|
1351
|
+
] }, sub.id)) })
|
|
1352
|
+
] }) }) })
|
|
1353
|
+
] })
|
|
1354
|
+
] });
|
|
1355
|
+
return Wrapper ? /* @__PURE__ */ jsx18(Wrapper, { children: content }) : content;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// src/pages/SettingsPage.tsx
|
|
1359
|
+
import { useState as useState8 } from "react";
|
|
1360
|
+
import { toast as toast6 } from "sonner";
|
|
1361
|
+
import { Settings, Loader2 as Loader25, CheckCircle2, XCircle } from "lucide-react";
|
|
1362
|
+
import {
|
|
1363
|
+
useBillingSettings,
|
|
1364
|
+
useUpdateBillingSettings,
|
|
1365
|
+
useTestBillingConnection
|
|
1366
|
+
} from "@growflowstudio/growflowbooking-admin-core";
|
|
1367
|
+
import { Fragment as Fragment7, jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1368
|
+
function SettingsPage({ wrapper: Wrapper, header }) {
|
|
1369
|
+
const { data: settings, isLoading } = useBillingSettings();
|
|
1370
|
+
const updateMutation = useUpdateBillingSettings();
|
|
1371
|
+
const testMutation = useTestBillingConnection();
|
|
1372
|
+
const [apiKey, setApiKey] = useState8("");
|
|
1373
|
+
const [webhookSecret, setWebhookSecret] = useState8("");
|
|
1374
|
+
const [mode, setMode] = useState8("test");
|
|
1375
|
+
const [billingUrlTest, setBillingUrlTest] = useState8("");
|
|
1376
|
+
const [billingUrlProduction, setBillingUrlProduction] = useState8("");
|
|
1377
|
+
const handleSave = () => {
|
|
1378
|
+
const data = {};
|
|
1379
|
+
if (apiKey) data.apiKey = apiKey;
|
|
1380
|
+
if (webhookSecret) data.webhookSecret = webhookSecret;
|
|
1381
|
+
if (mode) data.mode = mode;
|
|
1382
|
+
if (billingUrlTest) data.billingUrlTest = billingUrlTest;
|
|
1383
|
+
if (billingUrlProduction) data.billingUrlProduction = billingUrlProduction;
|
|
1384
|
+
updateMutation.mutate(data, {
|
|
1385
|
+
onSuccess: () => {
|
|
1386
|
+
toast6.success("Impostazioni salvate");
|
|
1387
|
+
setApiKey("");
|
|
1388
|
+
setWebhookSecret("");
|
|
1389
|
+
},
|
|
1390
|
+
onError: (err) => toast6.error(`Errore: ${err.message}`)
|
|
1391
|
+
});
|
|
1392
|
+
};
|
|
1393
|
+
const handleTest = () => {
|
|
1394
|
+
testMutation.mutate(void 0, {
|
|
1395
|
+
onSuccess: (result) => {
|
|
1396
|
+
if (result.success) {
|
|
1397
|
+
toast6.success(`Connessione OK \u2014 ${result.plansCount} piani trovati`);
|
|
1398
|
+
} else {
|
|
1399
|
+
toast6.error(`Connessione fallita: ${result.message}`);
|
|
1400
|
+
}
|
|
1401
|
+
},
|
|
1402
|
+
onError: (err) => toast6.error(`Errore: ${err.message}`)
|
|
1403
|
+
});
|
|
1404
|
+
};
|
|
1405
|
+
const content = /* @__PURE__ */ jsxs16(Fragment7, { children: [
|
|
1406
|
+
header,
|
|
1407
|
+
/* @__PURE__ */ jsxs16("div", { className: "space-y-6", children: [
|
|
1408
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1409
|
+
/* @__PURE__ */ jsx19(Settings, { className: "w-4 h-4" }),
|
|
1410
|
+
/* @__PURE__ */ jsx19("span", { children: "Impostazioni GrowFlow Billing" })
|
|
1411
|
+
] }),
|
|
1412
|
+
isLoading ? /* @__PURE__ */ jsx19("div", { className: "space-y-4", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsx19("div", { className: "h-12 w-full animate-pulse rounded-md bg-muted" }, i)) }) : /* @__PURE__ */ jsxs16("div", { className: "space-y-6", children: [
|
|
1413
|
+
/* @__PURE__ */ jsxs16("div", { className: "rounded-lg border bg-card p-6 space-y-4", children: [
|
|
1414
|
+
/* @__PURE__ */ jsx19("h3", { className: "font-medium", children: "Stato Connessione" }),
|
|
1415
|
+
/* @__PURE__ */ jsxs16("div", { className: "grid grid-cols-2 gap-4 text-sm", children: [
|
|
1416
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2", children: [
|
|
1417
|
+
settings?.apiKeyConfigured ? /* @__PURE__ */ jsx19(CheckCircle2, { className: "w-4 h-4 text-green-600" }) : /* @__PURE__ */ jsx19(XCircle, { className: "w-4 h-4 text-red-500" }),
|
|
1418
|
+
/* @__PURE__ */ jsxs16("span", { children: [
|
|
1419
|
+
"API Key: ",
|
|
1420
|
+
settings?.apiKeyMasked || "Non configurata"
|
|
1421
|
+
] })
|
|
1422
|
+
] }),
|
|
1423
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2", children: [
|
|
1424
|
+
settings?.webhookSecretConfigured ? /* @__PURE__ */ jsx19(CheckCircle2, { className: "w-4 h-4 text-green-600" }) : /* @__PURE__ */ jsx19(XCircle, { className: "w-4 h-4 text-red-500" }),
|
|
1425
|
+
/* @__PURE__ */ jsxs16("span", { children: [
|
|
1426
|
+
"Webhook: ",
|
|
1427
|
+
settings?.webhookSecretMasked || "Non configurato"
|
|
1428
|
+
] })
|
|
1429
|
+
] }),
|
|
1430
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
1431
|
+
/* @__PURE__ */ jsx19("span", { className: "text-muted-foreground", children: "Modalit\xE0:" }),
|
|
1432
|
+
" ",
|
|
1433
|
+
settings?.mode || "-"
|
|
1434
|
+
] }),
|
|
1435
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
1436
|
+
/* @__PURE__ */ jsx19("span", { className: "text-muted-foreground", children: "Client ID:" }),
|
|
1437
|
+
" ",
|
|
1438
|
+
settings?.clientId || "-"
|
|
1439
|
+
] })
|
|
1440
|
+
] }),
|
|
1441
|
+
/* @__PURE__ */ jsxs16(
|
|
1442
|
+
"button",
|
|
1443
|
+
{
|
|
1444
|
+
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-9 px-3 border border-input bg-background hover:bg-accent disabled:opacity-50",
|
|
1445
|
+
onClick: handleTest,
|
|
1446
|
+
disabled: testMutation.isPending,
|
|
1447
|
+
children: [
|
|
1448
|
+
testMutation.isPending && /* @__PURE__ */ jsx19(Loader25, { className: "w-4 h-4 animate-spin mr-2" }),
|
|
1449
|
+
"Testa Connessione"
|
|
1450
|
+
]
|
|
1451
|
+
}
|
|
1452
|
+
)
|
|
1453
|
+
] }),
|
|
1454
|
+
/* @__PURE__ */ jsxs16("div", { className: "rounded-lg border bg-card p-6 space-y-4", children: [
|
|
1455
|
+
/* @__PURE__ */ jsx19("h3", { className: "font-medium", children: "Aggiorna Impostazioni" }),
|
|
1456
|
+
/* @__PURE__ */ jsxs16("div", { className: "grid gap-4", children: [
|
|
1457
|
+
/* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
|
|
1458
|
+
/* @__PURE__ */ jsx19("label", { className: "text-sm font-medium", children: "API Key" }),
|
|
1459
|
+
/* @__PURE__ */ jsx19("input", { type: "password", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: apiKey, onChange: (e) => setApiKey(e.target.value), placeholder: "sk_live_..." })
|
|
1460
|
+
] }),
|
|
1461
|
+
/* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
|
|
1462
|
+
/* @__PURE__ */ jsx19("label", { className: "text-sm font-medium", children: "Webhook Secret" }),
|
|
1463
|
+
/* @__PURE__ */ jsx19("input", { type: "password", className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: webhookSecret, onChange: (e) => setWebhookSecret(e.target.value), placeholder: "whsec_..." })
|
|
1464
|
+
] }),
|
|
1465
|
+
/* @__PURE__ */ jsxs16("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
1466
|
+
/* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
|
|
1467
|
+
/* @__PURE__ */ jsx19("label", { className: "text-sm font-medium", children: "URL Test" }),
|
|
1468
|
+
/* @__PURE__ */ jsx19("input", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: billingUrlTest, onChange: (e) => setBillingUrlTest(e.target.value), placeholder: "http://localhost:8008" })
|
|
1469
|
+
] }),
|
|
1470
|
+
/* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
|
|
1471
|
+
/* @__PURE__ */ jsx19("label", { className: "text-sm font-medium", children: "URL Produzione" }),
|
|
1472
|
+
/* @__PURE__ */ jsx19("input", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: billingUrlProduction, onChange: (e) => setBillingUrlProduction(e.target.value), placeholder: "https://billing.growflow.studio" })
|
|
1473
|
+
] })
|
|
1474
|
+
] }),
|
|
1475
|
+
/* @__PURE__ */ jsxs16("div", { className: "space-y-2", children: [
|
|
1476
|
+
/* @__PURE__ */ jsx19("label", { className: "text-sm font-medium", children: "Modalit\xE0" }),
|
|
1477
|
+
/* @__PURE__ */ jsxs16("select", { className: "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", value: mode, onChange: (e) => setMode(e.target.value), children: [
|
|
1478
|
+
/* @__PURE__ */ jsx19("option", { value: "test", children: "Test" }),
|
|
1479
|
+
/* @__PURE__ */ jsx19("option", { value: "production", children: "Produzione" })
|
|
1480
|
+
] })
|
|
1481
|
+
] })
|
|
1482
|
+
] }),
|
|
1483
|
+
/* @__PURE__ */ jsxs16(
|
|
1484
|
+
"button",
|
|
1485
|
+
{
|
|
1486
|
+
className: "inline-flex items-center justify-center rounded-md text-sm font-medium h-10 px-4 py-2 bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-50",
|
|
1487
|
+
onClick: handleSave,
|
|
1488
|
+
disabled: updateMutation.isPending,
|
|
1489
|
+
children: [
|
|
1490
|
+
updateMutation.isPending && /* @__PURE__ */ jsx19(Loader25, { className: "w-4 h-4 animate-spin mr-2" }),
|
|
1491
|
+
"Salva Impostazioni"
|
|
1492
|
+
]
|
|
1493
|
+
}
|
|
1494
|
+
)
|
|
1495
|
+
] })
|
|
1496
|
+
] })
|
|
1497
|
+
] })
|
|
1498
|
+
] });
|
|
1499
|
+
return Wrapper ? /* @__PURE__ */ jsx19(Wrapper, { children: content }) : content;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// src/nav/bookingNavItems.ts
|
|
1503
|
+
var ALL_NAV_ITEMS = [
|
|
1504
|
+
{ key: "tenants", label: "Tenant", icon: "Building2", href: "/admin/tenants" },
|
|
1505
|
+
{ key: "customers", label: "Clienti", icon: "Users", href: "/admin/customers" },
|
|
1506
|
+
{ key: "subscriptionPlans", label: "Piani", icon: "LayoutGrid", href: "/admin/subscription-plans" },
|
|
1507
|
+
{ key: "billingPlans", label: "Billing Piani", icon: "CreditCard", href: "/admin/billing-plans" },
|
|
1508
|
+
{ key: "billingSubscriptions", label: "Abbonamenti", icon: "Receipt", href: "/admin/billing-subscriptions" },
|
|
1509
|
+
{ key: "settings", label: "Impostazioni", icon: "Settings", href: "/admin/settings" }
|
|
1510
|
+
];
|
|
1511
|
+
function getBookingNavItems(modules, basePath) {
|
|
1512
|
+
const enabled = {
|
|
1513
|
+
tenants: true,
|
|
1514
|
+
customers: true,
|
|
1515
|
+
subscriptionPlans: true,
|
|
1516
|
+
billingPlans: true,
|
|
1517
|
+
billingSubscriptions: true,
|
|
1518
|
+
settings: true,
|
|
1519
|
+
...modules
|
|
1520
|
+
};
|
|
1521
|
+
return ALL_NAV_ITEMS.filter((item) => enabled[item.key] !== false).map((item) => ({
|
|
1522
|
+
...item,
|
|
1523
|
+
href: basePath ? `${basePath}${item.href}` : item.href
|
|
1524
|
+
}));
|
|
1525
|
+
}
|
|
1526
|
+
export {
|
|
1527
|
+
BillingPlansPage,
|
|
1528
|
+
BillingSubscriptionsPage,
|
|
1529
|
+
BookingAdminProvider,
|
|
1530
|
+
CustomerFormDialog,
|
|
1531
|
+
CustomersPage,
|
|
1532
|
+
CustomersTable,
|
|
1533
|
+
DeleteConfirmDialog,
|
|
1534
|
+
FilterBar,
|
|
1535
|
+
Pagination,
|
|
1536
|
+
SettingsPage,
|
|
1537
|
+
SkeletonRows,
|
|
1538
|
+
StatusBadge,
|
|
1539
|
+
SubscriptionPlanFormDialog,
|
|
1540
|
+
SubscriptionPlansPage,
|
|
1541
|
+
SubscriptionPlansTable,
|
|
1542
|
+
TenantDetailDialog,
|
|
1543
|
+
TenantFormDialog,
|
|
1544
|
+
TenantsPage,
|
|
1545
|
+
TenantsTable,
|
|
1546
|
+
getBookingNavItems,
|
|
1547
|
+
useBookingAdminConfig,
|
|
1548
|
+
useEnabledModules
|
|
1549
|
+
};
|
|
1550
|
+
//# sourceMappingURL=index.mjs.map
|