@codemind.ec/medusa-plugin-invoice 1.0.6 → 1.1.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/.medusa/server/src/admin/index.js +806 -378
- package/.medusa/server/src/admin/index.mjs +809 -381
- package/.medusa/server/src/api/admin/invoice-config/[id]/route.js +31 -0
- package/.medusa/server/src/api/admin/invoice-config/[id]/set-default/route.js +19 -0
- package/.medusa/server/src/api/admin/invoice-config/route.js +6 -8
- package/.medusa/server/src/api/admin/invoice-templates/route.js +2 -2
- package/.medusa/server/src/api/admin/invoice-templates/validators.js +3 -1
- package/.medusa/server/src/modules/invoice-generator/migrations/Migration20260321120000.js +29 -0
- package/.medusa/server/src/modules/invoice-generator/models/invoice-config.js +2 -1
- package/.medusa/server/src/modules/invoice-generator/models/invoice-template.js +2 -1
- package/.medusa/server/src/modules/invoice-generator/service.js +14 -2
- package/package.json +86 -87
|
@@ -5,10 +5,9 @@ const ui = require("@medusajs/ui");
|
|
|
5
5
|
const Medusa = require("@medusajs/js-sdk");
|
|
6
6
|
const react = require("react");
|
|
7
7
|
const reactQuery = require("@tanstack/react-query");
|
|
8
|
-
const reactHookForm = require("react-hook-form");
|
|
9
|
-
const zod = require("@medusajs/framework/zod");
|
|
10
8
|
const reactRouterDom = require("react-router-dom");
|
|
11
9
|
const icons = require("@medusajs/icons");
|
|
10
|
+
const reactHookForm = require("react-hook-form");
|
|
12
11
|
const CodeMirror = require("@uiw/react-codemirror");
|
|
13
12
|
const langHtml = require("@codemirror/lang-html");
|
|
14
13
|
const themeOneDark = require("@codemirror/theme-one-dark");
|
|
@@ -72,202 +71,167 @@ const OrderInvoiceWidget = ({ data: order }) => {
|
|
|
72
71
|
adminSdk.defineWidgetConfig({
|
|
73
72
|
zone: "order.details.side.before"
|
|
74
73
|
});
|
|
75
|
-
zod.z.object({
|
|
76
|
-
company_name: zod.z.string().optional(),
|
|
77
|
-
company_ruc: zod.z.string().optional(),
|
|
78
|
-
company_address: zod.z.string().optional(),
|
|
79
|
-
company_phone: zod.z.string().optional(),
|
|
80
|
-
company_email: zod.z.string().email().optional().or(zod.z.literal("")),
|
|
81
|
-
company_logo: zod.z.string().optional(),
|
|
82
|
-
notes: zod.z.string().optional(),
|
|
83
|
-
admin_notification_email: zod.z.string().email().optional().or(zod.z.literal(""))
|
|
84
|
-
});
|
|
85
74
|
const InvoiceConfigPage = () => {
|
|
86
75
|
const navigate = reactRouterDom.useNavigate();
|
|
87
|
-
const { data, isLoading
|
|
76
|
+
const { data: configData, isLoading: loadingConfigs } = reactQuery.useQuery({
|
|
88
77
|
queryFn: () => sdk.client.fetch("/admin/invoice-config"),
|
|
89
|
-
queryKey: ["invoice-
|
|
78
|
+
queryKey: ["invoice-configs"]
|
|
90
79
|
});
|
|
91
|
-
const {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
80
|
+
const { data: templateData, isLoading: loadingTemplates } = reactQuery.useQuery({
|
|
81
|
+
queryFn: () => sdk.client.fetch("/admin/invoice-templates"),
|
|
82
|
+
queryKey: ["invoice-templates"]
|
|
83
|
+
});
|
|
84
|
+
const configs = (configData == null ? void 0 : configData.invoice_configs) ?? [];
|
|
85
|
+
const templates = (templateData == null ? void 0 : templateData.invoice_templates) ?? [];
|
|
86
|
+
const defaultCompany = configs.find((c) => c.is_default);
|
|
87
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
88
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 py-4", children: [
|
|
89
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Comprobantes y Cotizaciones" }),
|
|
90
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle mt-1", children: "Gestiona empresas emisoras y plantillas de documentos." })
|
|
91
|
+
] }),
|
|
92
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 p-6", children: [
|
|
93
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border border-ui-border-base rounded-lg p-5 flex flex-col gap-3", children: [
|
|
94
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
95
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Empresas" }),
|
|
96
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: configs.length > 0 ? "blue" : "grey", children: configs.length })
|
|
97
|
+
] }),
|
|
98
|
+
loadingConfigs ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-sm", children: "Cargando..." }) : defaultCompany ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-ui-bg-subtle rounded-md p-3", children: [
|
|
99
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm font-medium", children: defaultCompany.company_name }),
|
|
100
|
+
defaultCompany.company_ruc && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle text-xs", children: [
|
|
101
|
+
"RUC: ",
|
|
102
|
+
defaultCompany.company_ruc
|
|
103
|
+
] }),
|
|
104
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: "green", className: "mt-1", children: "Default" })
|
|
105
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-sm", children: "No hay empresas configuradas." }),
|
|
106
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
107
|
+
ui.Button,
|
|
108
|
+
{
|
|
109
|
+
variant: "secondary",
|
|
110
|
+
className: "mt-auto",
|
|
111
|
+
onClick: () => navigate("/invoice-config/companies"),
|
|
112
|
+
children: "Gestionar Empresas"
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
] }),
|
|
116
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border border-ui-border-base rounded-lg p-5 flex flex-col gap-3", children: [
|
|
117
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
118
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Plantillas" }),
|
|
119
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: templates.length > 0 ? "blue" : "grey", children: templates.length })
|
|
120
|
+
] }),
|
|
121
|
+
loadingTemplates ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-sm", children: "Cargando..." }) : templates.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-ui-bg-subtle rounded-md p-3 flex flex-col gap-1", children: [
|
|
122
|
+
templates.slice(0, 3).map((t) => /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm", children: t.name }, t.id)),
|
|
123
|
+
templates.length > 3 && /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-subtle text-xs", children: [
|
|
124
|
+
"+",
|
|
125
|
+
templates.length - 3,
|
|
126
|
+
" más..."
|
|
127
|
+
] })
|
|
128
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-sm", children: "No hay plantillas. Se crearán al reiniciar el servidor." }),
|
|
129
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
130
|
+
ui.Button,
|
|
131
|
+
{
|
|
132
|
+
variant: "secondary",
|
|
133
|
+
className: "mt-auto",
|
|
134
|
+
onClick: () => navigate("/invoice-config/invoice-templates"),
|
|
135
|
+
children: "Gestionar Plantillas"
|
|
136
|
+
}
|
|
137
|
+
)
|
|
138
|
+
] })
|
|
139
|
+
] })
|
|
140
|
+
] });
|
|
141
|
+
};
|
|
142
|
+
const config$1 = adminSdk.defineRouteConfig({
|
|
143
|
+
label: "Comprobantes y Cotizaciones"
|
|
144
|
+
});
|
|
145
|
+
const CompaniesPage = () => {
|
|
146
|
+
const navigate = reactRouterDom.useNavigate();
|
|
147
|
+
const queryClient = reactQuery.useQueryClient();
|
|
148
|
+
const { data, isLoading } = reactQuery.useQuery({
|
|
149
|
+
queryFn: () => sdk.client.fetch("/admin/invoice-config"),
|
|
150
|
+
queryKey: ["invoice-configs"]
|
|
151
|
+
});
|
|
152
|
+
const setDefaultMutation = reactQuery.useMutation({
|
|
153
|
+
mutationFn: (id) => sdk.client.fetch(`/admin/invoice-config/${id}/set-default`, { method: "POST" }),
|
|
96
154
|
onSuccess: () => {
|
|
97
|
-
|
|
98
|
-
ui.toast.success("
|
|
155
|
+
queryClient.invalidateQueries({ queryKey: ["invoice-configs"] });
|
|
156
|
+
ui.toast.success("Empresa marcada como default");
|
|
99
157
|
},
|
|
100
|
-
onError: () =>
|
|
101
|
-
ui.toast.error("Error al guardar la configuración");
|
|
102
|
-
}
|
|
158
|
+
onError: () => ui.toast.error("Error al cambiar empresa default")
|
|
103
159
|
});
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
company_logo: (data == null ? void 0 : data.invoice_config.company_logo) || "",
|
|
112
|
-
notes: (data == null ? void 0 : data.invoice_config.notes) || "",
|
|
113
|
-
admin_notification_email: (data == null ? void 0 : data.invoice_config.admin_notification_email) || ""
|
|
114
|
-
};
|
|
115
|
-
}, [data]);
|
|
116
|
-
const form = reactHookForm.useForm({
|
|
117
|
-
defaultValues: getFormDefaultValues()
|
|
160
|
+
const deleteMutation = reactQuery.useMutation({
|
|
161
|
+
mutationFn: (id) => sdk.client.fetch(`/admin/invoice-config/${id}`, { method: "DELETE" }),
|
|
162
|
+
onSuccess: () => {
|
|
163
|
+
queryClient.invalidateQueries({ queryKey: ["invoice-configs"] });
|
|
164
|
+
ui.toast.success("Empresa eliminada");
|
|
165
|
+
},
|
|
166
|
+
onError: () => ui.toast.error("No se pudo eliminar la empresa")
|
|
118
167
|
});
|
|
119
|
-
const
|
|
120
|
-
const uploadLogo = async (event) => {
|
|
121
|
-
var _a;
|
|
122
|
-
const file = (_a = event.target.files) == null ? void 0 : _a[0];
|
|
123
|
-
if (!file) {
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
try {
|
|
127
|
-
const { files } = await sdk.admin.upload.create({
|
|
128
|
-
files: [file]
|
|
129
|
-
});
|
|
130
|
-
form.setValue("company_logo", files[0].url);
|
|
131
|
-
} catch (error) {
|
|
132
|
-
ui.toast.error("Error al subir el logo. Verifica que el archivo sea una imagen válida.");
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
react.useEffect(() => {
|
|
136
|
-
form.reset(getFormDefaultValues());
|
|
137
|
-
}, [getFormDefaultValues]);
|
|
168
|
+
const configs = (data == null ? void 0 : data.invoice_configs) ?? [];
|
|
138
169
|
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
139
170
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
140
|
-
/* @__PURE__ */ jsxRuntime.
|
|
141
|
-
|
|
171
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
172
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Empresas" }),
|
|
173
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-sm mt-1", children: "Empresas emisoras de comprobantes y cotizaciones." })
|
|
174
|
+
] }),
|
|
175
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
176
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/invoice-config"), children: "← Panel" }),
|
|
177
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => navigate("/invoice-config/companies/new"), children: "+ Nueva Empresa" })
|
|
178
|
+
] })
|
|
142
179
|
] }),
|
|
143
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
children:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
] });
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
),
|
|
189
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
190
|
-
reactHookForm.Controller,
|
|
191
|
-
{
|
|
192
|
-
control: form.control,
|
|
193
|
-
name: "company_address",
|
|
194
|
-
render: ({ field }) => {
|
|
195
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col space-y-2", children: [
|
|
196
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-x-1", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Dirección de la Empresa" }) }),
|
|
197
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { ...field })
|
|
198
|
-
] });
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
),
|
|
202
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
203
|
-
reactHookForm.Controller,
|
|
204
|
-
{
|
|
205
|
-
control: form.control,
|
|
206
|
-
name: "company_phone",
|
|
207
|
-
render: ({ field }) => {
|
|
208
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col space-y-2", children: [
|
|
209
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-x-1", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Teléfono de la Empresa" }) }),
|
|
210
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field })
|
|
211
|
-
] });
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
),
|
|
215
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
216
|
-
reactHookForm.Controller,
|
|
217
|
-
{
|
|
218
|
-
control: form.control,
|
|
219
|
-
name: "company_email",
|
|
220
|
-
render: ({ field }) => {
|
|
221
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col space-y-2", children: [
|
|
222
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-x-1", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Email de la Empresa" }) }),
|
|
223
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field })
|
|
224
|
-
] });
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
),
|
|
228
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
229
|
-
reactHookForm.Controller,
|
|
230
|
-
{
|
|
231
|
-
control: form.control,
|
|
232
|
-
name: "notes",
|
|
233
|
-
render: ({ field }) => {
|
|
234
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col space-y-2", children: [
|
|
235
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-x-1", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Notas" }) }),
|
|
236
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { ...field })
|
|
237
|
-
] });
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
),
|
|
241
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
242
|
-
reactHookForm.Controller,
|
|
243
|
-
{
|
|
244
|
-
control: form.control,
|
|
245
|
-
name: "company_logo",
|
|
246
|
-
render: ({ field }) => {
|
|
247
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col space-y-2", children: [
|
|
248
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-x-1", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Logo de la Empresa" }) }),
|
|
249
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { type: "file", onChange: uploadLogo, className: "py-1" }),
|
|
250
|
-
field.value && /* @__PURE__ */ jsxRuntime.jsx(
|
|
251
|
-
"img",
|
|
252
|
-
{
|
|
253
|
-
src: field.value,
|
|
254
|
-
alt: "Logo de la Empresa",
|
|
255
|
-
className: "mt-2 h-24 w-24"
|
|
180
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 pb-4", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10 text-ui-fg-subtle", children: "Cargando..." }) : configs.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10 text-ui-fg-subtle", children: "No hay empresas configuradas." }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
181
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
182
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Nombre" }),
|
|
183
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "RUC" }),
|
|
184
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Email" }),
|
|
185
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Teléfono" }),
|
|
186
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Estado" }),
|
|
187
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { style: { textAlign: "right" }, children: "Acciones" })
|
|
188
|
+
] }) }),
|
|
189
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Body, { children: configs.map((cfg) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
190
|
+
ui.Table.Row,
|
|
191
|
+
{
|
|
192
|
+
onClick: () => navigate(`/invoice-config/companies/${cfg.id}`),
|
|
193
|
+
style: { cursor: "pointer" },
|
|
194
|
+
children: [
|
|
195
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { className: "font-medium", children: cfg.company_name }),
|
|
196
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: "text-xs", children: cfg.company_ruc || "—" }) }),
|
|
197
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: cfg.company_email }),
|
|
198
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: cfg.company_phone }),
|
|
199
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: cfg.is_default ? /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: "green", children: "Default" }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: "grey", children: "—" }) }),
|
|
200
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { style: { textAlign: "right" }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2 justify-end", children: !cfg.is_default && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
201
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
202
|
+
ui.Button,
|
|
203
|
+
{
|
|
204
|
+
variant: "secondary",
|
|
205
|
+
size: "small",
|
|
206
|
+
onClick: (e) => {
|
|
207
|
+
e.stopPropagation();
|
|
208
|
+
setDefaultMutation.mutate(cfg.id);
|
|
209
|
+
},
|
|
210
|
+
children: "Default"
|
|
211
|
+
}
|
|
212
|
+
),
|
|
213
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
214
|
+
ui.Button,
|
|
215
|
+
{
|
|
216
|
+
variant: "danger",
|
|
217
|
+
size: "small",
|
|
218
|
+
onClick: (e) => {
|
|
219
|
+
e.stopPropagation();
|
|
220
|
+
if (confirm("¿Eliminar esta empresa?")) {
|
|
221
|
+
deleteMutation.mutate(cfg.id);
|
|
256
222
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
223
|
+
},
|
|
224
|
+
children: "Eliminar"
|
|
225
|
+
}
|
|
226
|
+
)
|
|
227
|
+
] }) }) })
|
|
228
|
+
]
|
|
229
|
+
},
|
|
230
|
+
cfg.id
|
|
231
|
+
)) })
|
|
232
|
+
] }) })
|
|
266
233
|
] });
|
|
267
234
|
};
|
|
268
|
-
const config$1 = adminSdk.defineRouteConfig({
|
|
269
|
-
label: "Comprobante de Pedido"
|
|
270
|
-
});
|
|
271
235
|
const TYPE_LABELS = {
|
|
272
236
|
order_invoice: "Comprobante de Pedido",
|
|
273
237
|
quote_proforma: "Cotización Proforma"
|
|
@@ -279,6 +243,10 @@ const InvoiceTemplatesPage = () => {
|
|
|
279
243
|
queryFn: () => sdk.client.fetch("/admin/invoice-templates"),
|
|
280
244
|
queryKey: ["invoice-templates"]
|
|
281
245
|
});
|
|
246
|
+
const { data: companiesData } = reactQuery.useQuery({
|
|
247
|
+
queryFn: () => sdk.client.fetch("/admin/invoice-config"),
|
|
248
|
+
queryKey: ["invoice-configs"]
|
|
249
|
+
});
|
|
282
250
|
const deleteMutation = reactQuery.useMutation({
|
|
283
251
|
mutationFn: (id) => sdk.client.fetch(`/admin/invoice-templates/${id}`, { method: "DELETE" }),
|
|
284
252
|
onSuccess: () => {
|
|
@@ -290,19 +258,21 @@ const InvoiceTemplatesPage = () => {
|
|
|
290
258
|
}
|
|
291
259
|
});
|
|
292
260
|
const templates = (data == null ? void 0 : data.invoice_templates) ?? [];
|
|
293
|
-
|
|
294
|
-
|
|
261
|
+
const companiesMap = new Map(((companiesData == null ? void 0 : companiesData.invoice_configs) ?? []).map((c) => [c.id, c.company_name]));
|
|
262
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
263
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
295
264
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Plantillas de Documentos" }),
|
|
296
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
297
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/invoice-config"), children: "
|
|
265
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
266
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/invoice-config"), children: "← Panel" }),
|
|
298
267
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: () => navigate("/invoice-config/invoice-templates/new"), children: "+ Nueva Plantilla" })
|
|
299
268
|
] })
|
|
300
269
|
] }),
|
|
301
|
-
|
|
270
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 pb-4", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10 text-ui-fg-subtle", children: "Cargando..." }) : templates.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10 text-ui-fg-subtle", children: "No hay plantillas. Se crearán automáticamente al reiniciar el servidor." }) : /* @__PURE__ */ jsxRuntime.jsxs(ui.Table, { children: [
|
|
302
271
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Header, { children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Table.Row, { children: [
|
|
303
272
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Nombre" }),
|
|
304
273
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Slug" }),
|
|
305
274
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Tipo" }),
|
|
275
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Empresa" }),
|
|
306
276
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Estado" }),
|
|
307
277
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { children: "Actualizado" }),
|
|
308
278
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.HeaderCell, { style: { textAlign: "right" }, children: "Acciones" })
|
|
@@ -313,9 +283,10 @@ const InvoiceTemplatesPage = () => {
|
|
|
313
283
|
onClick: () => navigate(`/invoice-config/invoice-templates/${tpl.id}`),
|
|
314
284
|
style: { cursor: "pointer" },
|
|
315
285
|
children: [
|
|
316
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: tpl.name }),
|
|
317
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx("code", {
|
|
286
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { className: "font-medium", children: tpl.name }),
|
|
287
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx("code", { className: "text-xs", children: tpl.slug }) }),
|
|
318
288
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: TYPE_LABELS[tpl.type] ?? tpl.type }),
|
|
289
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-sm", children: tpl.company_id ? companiesMap.get(tpl.company_id) ?? "—" : "—" }) }),
|
|
319
290
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: tpl.is_default ? /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: "blue", children: "Por defecto" }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { color: "grey", children: "Personalizada" }) }),
|
|
320
291
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { children: new Date(tpl.updated_at).toLocaleDateString("es-ES") }),
|
|
321
292
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Table.Cell, { style: { textAlign: "right" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -337,163 +308,491 @@ const InvoiceTemplatesPage = () => {
|
|
|
337
308
|
},
|
|
338
309
|
tpl.id
|
|
339
310
|
)) })
|
|
340
|
-
] })
|
|
311
|
+
] }) })
|
|
341
312
|
] });
|
|
342
313
|
};
|
|
343
314
|
const config = adminSdk.defineRouteConfig({
|
|
344
315
|
label: "Plantillas PDF",
|
|
345
316
|
icon: icons.DocumentText
|
|
346
317
|
});
|
|
347
|
-
const
|
|
318
|
+
const NewCompanyPage = () => {
|
|
348
319
|
const navigate = reactRouterDom.useNavigate();
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
320
|
+
const form = reactHookForm.useForm({
|
|
321
|
+
defaultValues: {
|
|
322
|
+
company_name: "",
|
|
323
|
+
company_ruc: "",
|
|
324
|
+
company_address: "",
|
|
325
|
+
company_phone: "",
|
|
326
|
+
company_email: "",
|
|
327
|
+
company_logo: "",
|
|
328
|
+
notes: "",
|
|
329
|
+
admin_notification_email: "",
|
|
330
|
+
is_default: false
|
|
331
|
+
}
|
|
332
|
+
});
|
|
352
333
|
const createMutation = reactQuery.useMutation({
|
|
353
|
-
mutationFn: (payload) => sdk.client.fetch("/admin/invoice-
|
|
334
|
+
mutationFn: (payload) => sdk.client.fetch("/admin/invoice-config", {
|
|
354
335
|
method: "POST",
|
|
355
336
|
body: payload
|
|
356
337
|
}),
|
|
357
|
-
onSuccess: (
|
|
358
|
-
ui.toast.success("
|
|
359
|
-
navigate(
|
|
338
|
+
onSuccess: () => {
|
|
339
|
+
ui.toast.success("Empresa creada exitosamente");
|
|
340
|
+
navigate("/invoice-config/companies");
|
|
360
341
|
},
|
|
361
|
-
onError: () =>
|
|
362
|
-
ui.toast.error("Error al crear la plantilla");
|
|
363
|
-
}
|
|
342
|
+
onError: () => ui.toast.error("Error al crear la empresa")
|
|
364
343
|
});
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
344
|
+
const uploadLogo = async (event) => {
|
|
345
|
+
var _a;
|
|
346
|
+
const file = (_a = event.target.files) == null ? void 0 : _a[0];
|
|
347
|
+
if (!file) return;
|
|
348
|
+
try {
|
|
349
|
+
const { files } = await sdk.admin.upload.create({ files: [file] });
|
|
350
|
+
form.setValue("company_logo", files[0].url);
|
|
351
|
+
} catch {
|
|
352
|
+
ui.toast.error("Error al subir el logo.");
|
|
369
353
|
}
|
|
370
|
-
createMutation.mutate({
|
|
371
|
-
name,
|
|
372
|
-
slug,
|
|
373
|
-
type,
|
|
374
|
-
html_content: getDefaultHtml(type)
|
|
375
|
-
});
|
|
376
354
|
};
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
380
|
-
/* @__PURE__ */ jsxRuntime.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
/* @__PURE__ */ jsxRuntime.
|
|
385
|
-
|
|
355
|
+
const handleSubmit = form.handleSubmit((data) => createMutation.mutate(data));
|
|
356
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
357
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
358
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Nueva Empresa" }),
|
|
359
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/invoice-config/companies"), children: "← Volver" })
|
|
360
|
+
] }),
|
|
361
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactHookForm.FormProvider, { ...form, children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4 p-6 max-w-2xl", children: [
|
|
362
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-ui-bg-subtle border border-ui-border-base rounded-lg p-4 mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
363
|
+
reactHookForm.Controller,
|
|
364
|
+
{
|
|
365
|
+
control: form.control,
|
|
366
|
+
name: "admin_notification_email",
|
|
367
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
368
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "📧 Email de Notificaciones (Admin)" }),
|
|
369
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field, placeholder: "admin@empresa.com" }),
|
|
370
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: "Notificaciones de nuevos pedidos a este email." })
|
|
371
|
+
] })
|
|
372
|
+
}
|
|
373
|
+
) }),
|
|
374
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
375
|
+
reactHookForm.Controller,
|
|
376
|
+
{
|
|
377
|
+
control: form.control,
|
|
378
|
+
name: "company_name",
|
|
379
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
380
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Nombre de la Empresa *" }),
|
|
381
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field, placeholder: "Mi Empresa S.A.S." })
|
|
382
|
+
] })
|
|
383
|
+
}
|
|
384
|
+
),
|
|
385
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
386
|
+
reactHookForm.Controller,
|
|
387
|
+
{
|
|
388
|
+
control: form.control,
|
|
389
|
+
name: "company_ruc",
|
|
390
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
391
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "RUC" }),
|
|
392
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field, placeholder: "1234567890001" })
|
|
393
|
+
] })
|
|
394
|
+
}
|
|
395
|
+
),
|
|
396
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
397
|
+
reactHookForm.Controller,
|
|
398
|
+
{
|
|
399
|
+
control: form.control,
|
|
400
|
+
name: "company_address",
|
|
401
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
402
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Dirección" }),
|
|
403
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { ...field, placeholder: "Av. Principal 123, Quito" })
|
|
404
|
+
] })
|
|
405
|
+
}
|
|
406
|
+
),
|
|
407
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
386
408
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
387
|
-
|
|
409
|
+
reactHookForm.Controller,
|
|
388
410
|
{
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
411
|
+
control: form.control,
|
|
412
|
+
name: "company_phone",
|
|
413
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
414
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Teléfono" }),
|
|
415
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field, placeholder: "+593 99 123 4567" })
|
|
416
|
+
] })
|
|
417
|
+
}
|
|
418
|
+
),
|
|
419
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
420
|
+
reactHookForm.Controller,
|
|
421
|
+
{
|
|
422
|
+
control: form.control,
|
|
423
|
+
name: "company_email",
|
|
424
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
425
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Email de la Empresa" }),
|
|
426
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field, placeholder: "info@empresa.com" })
|
|
427
|
+
] })
|
|
393
428
|
}
|
|
394
429
|
)
|
|
395
430
|
] }),
|
|
396
|
-
/* @__PURE__ */ jsxRuntime.
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.
|
|
431
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
432
|
+
reactHookForm.Controller,
|
|
433
|
+
{
|
|
434
|
+
control: form.control,
|
|
435
|
+
name: "notes",
|
|
436
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
437
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Notas" }),
|
|
438
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { ...field, placeholder: "Notas internas o pie de página del documento" })
|
|
403
439
|
] })
|
|
404
|
-
|
|
440
|
+
}
|
|
441
|
+
),
|
|
442
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
443
|
+
reactHookForm.Controller,
|
|
444
|
+
{
|
|
445
|
+
control: form.control,
|
|
446
|
+
name: "company_logo",
|
|
447
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
448
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Logo de la Empresa" }),
|
|
449
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { type: "file", onChange: uploadLogo, className: "py-1" }),
|
|
450
|
+
field.value && /* @__PURE__ */ jsxRuntime.jsx("img", { src: field.value, alt: "Logo", className: "mt-2 h-24 w-24 object-contain" })
|
|
451
|
+
] })
|
|
452
|
+
}
|
|
453
|
+
),
|
|
454
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
455
|
+
reactHookForm.Controller,
|
|
456
|
+
{
|
|
457
|
+
control: form.control,
|
|
458
|
+
name: "is_default",
|
|
459
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 bg-ui-bg-subtle rounded-lg p-3", children: [
|
|
460
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
461
|
+
ui.Switch,
|
|
462
|
+
{
|
|
463
|
+
checked: field.value,
|
|
464
|
+
onCheckedChange: field.onChange
|
|
465
|
+
}
|
|
466
|
+
),
|
|
467
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
468
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Empresa por defecto" }),
|
|
469
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: "Se usará en plantillas que no tengan empresa asignada." })
|
|
470
|
+
] })
|
|
471
|
+
] })
|
|
472
|
+
}
|
|
473
|
+
),
|
|
474
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-2", children: [
|
|
475
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/invoice-config/companies"), children: "Cancelar" }),
|
|
476
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", isLoading: createMutation.isPending, children: "Crear Empresa" })
|
|
477
|
+
] })
|
|
478
|
+
] }) })
|
|
479
|
+
] });
|
|
480
|
+
};
|
|
481
|
+
const EditCompanyPage = () => {
|
|
482
|
+
const { id } = reactRouterDom.useParams();
|
|
483
|
+
const navigate = reactRouterDom.useNavigate();
|
|
484
|
+
const queryClient = reactQuery.useQueryClient();
|
|
485
|
+
const { data, isLoading } = reactQuery.useQuery({
|
|
486
|
+
queryFn: () => sdk.client.fetch(`/admin/invoice-config/${id}`),
|
|
487
|
+
queryKey: ["invoice-config", id],
|
|
488
|
+
enabled: !!id
|
|
489
|
+
});
|
|
490
|
+
const config2 = data == null ? void 0 : data.invoice_config;
|
|
491
|
+
const getDefaults = react.useCallback(() => ({
|
|
492
|
+
company_name: (config2 == null ? void 0 : config2.company_name) || "",
|
|
493
|
+
company_ruc: (config2 == null ? void 0 : config2.company_ruc) || "",
|
|
494
|
+
company_address: (config2 == null ? void 0 : config2.company_address) || "",
|
|
495
|
+
company_phone: (config2 == null ? void 0 : config2.company_phone) || "",
|
|
496
|
+
company_email: (config2 == null ? void 0 : config2.company_email) || "",
|
|
497
|
+
company_logo: (config2 == null ? void 0 : config2.company_logo) || "",
|
|
498
|
+
notes: (config2 == null ? void 0 : config2.notes) || "",
|
|
499
|
+
admin_notification_email: (config2 == null ? void 0 : config2.admin_notification_email) || "",
|
|
500
|
+
is_default: (config2 == null ? void 0 : config2.is_default) || false
|
|
501
|
+
}), [config2]);
|
|
502
|
+
const form = reactHookForm.useForm({ defaultValues: getDefaults() });
|
|
503
|
+
react.useEffect(() => {
|
|
504
|
+
form.reset(getDefaults());
|
|
505
|
+
}, [getDefaults]);
|
|
506
|
+
const saveMutation = reactQuery.useMutation({
|
|
507
|
+
mutationFn: (payload) => sdk.client.fetch(`/admin/invoice-config/${id}`, {
|
|
508
|
+
method: "POST",
|
|
509
|
+
body: payload
|
|
510
|
+
}),
|
|
511
|
+
onSuccess: () => {
|
|
512
|
+
queryClient.invalidateQueries({ queryKey: ["invoice-config", id] });
|
|
513
|
+
queryClient.invalidateQueries({ queryKey: ["invoice-configs"] });
|
|
514
|
+
ui.toast.success("Empresa actualizada");
|
|
515
|
+
},
|
|
516
|
+
onError: () => ui.toast.error("Error al guardar")
|
|
517
|
+
});
|
|
518
|
+
const uploadLogo = async (event) => {
|
|
519
|
+
var _a;
|
|
520
|
+
const file = (_a = event.target.files) == null ? void 0 : _a[0];
|
|
521
|
+
if (!file) return;
|
|
522
|
+
try {
|
|
523
|
+
const { files } = await sdk.admin.upload.create({ files: [file] });
|
|
524
|
+
form.setValue("company_logo", files[0].url);
|
|
525
|
+
} catch {
|
|
526
|
+
ui.toast.error("Error al subir el logo.");
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
const handleSubmit = form.handleSubmit((data2) => saveMutation.mutate(data2));
|
|
530
|
+
if (isLoading) {
|
|
531
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10", children: "Cargando..." }) });
|
|
532
|
+
}
|
|
533
|
+
if (!config2) {
|
|
534
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10", children: "Empresa no encontrada" }) });
|
|
535
|
+
}
|
|
536
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
537
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
538
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Editar Empresa" }),
|
|
539
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/invoice-config/companies"), children: "← Volver" })
|
|
540
|
+
] }),
|
|
541
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactHookForm.FormProvider, { ...form, children: /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "flex flex-col gap-4 p-6 max-w-2xl", children: [
|
|
542
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-ui-bg-subtle border border-ui-border-base rounded-lg p-4 mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
543
|
+
reactHookForm.Controller,
|
|
544
|
+
{
|
|
545
|
+
control: form.control,
|
|
546
|
+
name: "admin_notification_email",
|
|
547
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
548
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "📧 Email de Notificaciones (Admin)" }),
|
|
549
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field, placeholder: "admin@empresa.com" }),
|
|
550
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: "Notificaciones de nuevos pedidos a este email." })
|
|
551
|
+
] })
|
|
552
|
+
}
|
|
553
|
+
) }),
|
|
554
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
555
|
+
reactHookForm.Controller,
|
|
556
|
+
{
|
|
557
|
+
control: form.control,
|
|
558
|
+
name: "company_name",
|
|
559
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
560
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Nombre de la Empresa *" }),
|
|
561
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field })
|
|
562
|
+
] })
|
|
563
|
+
}
|
|
564
|
+
),
|
|
565
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
566
|
+
reactHookForm.Controller,
|
|
567
|
+
{
|
|
568
|
+
control: form.control,
|
|
569
|
+
name: "company_ruc",
|
|
570
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
571
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "RUC" }),
|
|
572
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field, placeholder: "1234567890001" })
|
|
573
|
+
] })
|
|
574
|
+
}
|
|
575
|
+
),
|
|
576
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
577
|
+
reactHookForm.Controller,
|
|
578
|
+
{
|
|
579
|
+
control: form.control,
|
|
580
|
+
name: "company_address",
|
|
581
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
582
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Dirección" }),
|
|
583
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { ...field })
|
|
584
|
+
] })
|
|
585
|
+
}
|
|
586
|
+
),
|
|
587
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
588
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
589
|
+
reactHookForm.Controller,
|
|
590
|
+
{
|
|
591
|
+
control: form.control,
|
|
592
|
+
name: "company_phone",
|
|
593
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
594
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Teléfono" }),
|
|
595
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field })
|
|
596
|
+
] })
|
|
597
|
+
}
|
|
598
|
+
),
|
|
599
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
600
|
+
reactHookForm.Controller,
|
|
601
|
+
{
|
|
602
|
+
control: form.control,
|
|
603
|
+
name: "company_email",
|
|
604
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
605
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Email de la Empresa" }),
|
|
606
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field })
|
|
607
|
+
] })
|
|
608
|
+
}
|
|
609
|
+
)
|
|
405
610
|
] }),
|
|
406
|
-
/* @__PURE__ */ jsxRuntime.
|
|
407
|
-
|
|
408
|
-
|
|
611
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
612
|
+
reactHookForm.Controller,
|
|
613
|
+
{
|
|
614
|
+
control: form.control,
|
|
615
|
+
name: "notes",
|
|
616
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
617
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Notas" }),
|
|
618
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { ...field })
|
|
619
|
+
] })
|
|
620
|
+
}
|
|
621
|
+
),
|
|
622
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
623
|
+
reactHookForm.Controller,
|
|
624
|
+
{
|
|
625
|
+
control: form.control,
|
|
626
|
+
name: "company_logo",
|
|
627
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
628
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Logo de la Empresa" }),
|
|
629
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { type: "file", onChange: uploadLogo, className: "py-1" }),
|
|
630
|
+
field.value && /* @__PURE__ */ jsxRuntime.jsx("img", { src: field.value, alt: "Logo", className: "mt-2 h-24 w-24 object-contain" })
|
|
631
|
+
] })
|
|
632
|
+
}
|
|
633
|
+
),
|
|
634
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
635
|
+
reactHookForm.Controller,
|
|
636
|
+
{
|
|
637
|
+
control: form.control,
|
|
638
|
+
name: "is_default",
|
|
639
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 bg-ui-bg-subtle rounded-lg p-3", children: [
|
|
640
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
641
|
+
ui.Switch,
|
|
642
|
+
{
|
|
643
|
+
checked: field.value,
|
|
644
|
+
onCheckedChange: field.onChange
|
|
645
|
+
}
|
|
646
|
+
),
|
|
647
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
648
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Empresa por defecto" }),
|
|
649
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: "Se usará en plantillas que no tengan empresa asignada." })
|
|
650
|
+
] })
|
|
651
|
+
] })
|
|
652
|
+
}
|
|
653
|
+
),
|
|
654
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-2", children: [
|
|
655
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/invoice-config/companies"), children: "Cancelar" }),
|
|
656
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", isLoading: saveMutation.isPending, children: "Guardar Cambios" })
|
|
409
657
|
] })
|
|
410
|
-
] })
|
|
658
|
+
] }) })
|
|
411
659
|
] });
|
|
412
660
|
};
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
661
|
+
const ORDER_INVOICE_CATEGORIES = [
|
|
662
|
+
{
|
|
663
|
+
label: "Empresa",
|
|
664
|
+
icon: "🏢",
|
|
665
|
+
variables: [
|
|
666
|
+
{ name: "company_name" },
|
|
667
|
+
{ name: "company_ruc" },
|
|
668
|
+
{ name: "company_address" },
|
|
669
|
+
{ name: "company_phone" },
|
|
670
|
+
{ name: "company_email" },
|
|
671
|
+
{ name: "company_logo_base64" }
|
|
672
|
+
]
|
|
673
|
+
},
|
|
674
|
+
{
|
|
675
|
+
label: "Documento",
|
|
676
|
+
icon: "📄",
|
|
677
|
+
variables: [
|
|
678
|
+
{ name: "invoice_id" },
|
|
679
|
+
{ name: "invoice_date" },
|
|
680
|
+
{ name: "order_display_id" },
|
|
681
|
+
{ name: "order_date" }
|
|
682
|
+
]
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
label: "Cliente",
|
|
686
|
+
icon: "👤",
|
|
687
|
+
variables: [
|
|
688
|
+
{ name: "billing_address" },
|
|
689
|
+
{ name: "shipping_address" },
|
|
690
|
+
{ name: "cedula" }
|
|
691
|
+
]
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
label: "Productos",
|
|
695
|
+
icon: "📦",
|
|
696
|
+
variables: [
|
|
697
|
+
{ name: "{{#each items}}", isBlock: true },
|
|
698
|
+
{ name: "this.title" },
|
|
699
|
+
{ name: "this.variant_title" },
|
|
700
|
+
{ name: "this.quantity" },
|
|
701
|
+
{ name: "this.unit_price" },
|
|
702
|
+
{ name: "this.total" },
|
|
703
|
+
{ name: "{{/each}}", isBlock: true }
|
|
704
|
+
]
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
label: "Totales",
|
|
708
|
+
icon: "💰",
|
|
709
|
+
variables: [
|
|
710
|
+
{ name: "subtotal" },
|
|
711
|
+
{ name: "tax_total" },
|
|
712
|
+
{ name: "shipping_total" },
|
|
713
|
+
{ name: "discount_total" },
|
|
714
|
+
{ name: "total" }
|
|
715
|
+
]
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
label: "Otros",
|
|
719
|
+
icon: "📝",
|
|
720
|
+
variables: [
|
|
721
|
+
{ name: "is_home_delivery" },
|
|
722
|
+
{ name: "notes" }
|
|
723
|
+
]
|
|
425
724
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
"
|
|
448
|
-
"
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
"
|
|
458
|
-
"
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
"
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
"
|
|
481
|
-
"
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
]
|
|
725
|
+
];
|
|
726
|
+
const QUOTE_PROFORMA_CATEGORIES = [
|
|
727
|
+
{
|
|
728
|
+
label: "Empresa",
|
|
729
|
+
icon: "🏢",
|
|
730
|
+
variables: [
|
|
731
|
+
{ name: "company_name" },
|
|
732
|
+
{ name: "company_website" }
|
|
733
|
+
]
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
label: "Documento",
|
|
737
|
+
icon: "📄",
|
|
738
|
+
variables: [
|
|
739
|
+
{ name: "quote_number" },
|
|
740
|
+
{ name: "date_str" },
|
|
741
|
+
{ name: "service_name" },
|
|
742
|
+
{ name: "is_home_delivery" }
|
|
743
|
+
]
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
label: "Configuración",
|
|
747
|
+
icon: "⚙️",
|
|
748
|
+
variables: [
|
|
749
|
+
{ name: "{{#each config_fields}}", isBlock: true },
|
|
750
|
+
{ name: "this.label" },
|
|
751
|
+
{ name: "this.value" },
|
|
752
|
+
{ name: "{{/each}}", isBlock: true }
|
|
753
|
+
]
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
label: "Desglose",
|
|
757
|
+
icon: "📊",
|
|
758
|
+
variables: [
|
|
759
|
+
{ name: "{{#each breakdown}}", isBlock: true },
|
|
760
|
+
{ name: "this.label" },
|
|
761
|
+
{ name: "this.total_formatted" },
|
|
762
|
+
{ name: "{{/each}}", isBlock: true }
|
|
763
|
+
]
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
label: "Totales",
|
|
767
|
+
icon: "💰",
|
|
768
|
+
variables: [
|
|
769
|
+
{ name: "totals.subtotal_formatted" },
|
|
770
|
+
{ name: "totals.extras_formatted" },
|
|
771
|
+
{ name: "totals.discount_formatted" },
|
|
772
|
+
{ name: "totals.shipping_formatted" },
|
|
773
|
+
{ name: "totals.taxes_provided" },
|
|
774
|
+
{ name: "totals.taxes_formatted" },
|
|
775
|
+
{ name: "totals.total_formatted" }
|
|
776
|
+
]
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
label: "Incluye y Contacto",
|
|
780
|
+
icon: "📋",
|
|
781
|
+
variables: [
|
|
782
|
+
{ name: "{{#each includes_left}}", isBlock: true },
|
|
783
|
+
{ name: "{{#each includes_right}}", isBlock: true },
|
|
784
|
+
{ name: "{{#each contact_rows}}", isBlock: true },
|
|
785
|
+
{ name: "this.label" },
|
|
786
|
+
{ name: "this.value" },
|
|
787
|
+
{ name: "{{/each}}", isBlock: true }
|
|
788
|
+
]
|
|
789
|
+
}
|
|
790
|
+
];
|
|
791
|
+
const CATEGORIES = {
|
|
792
|
+
order_invoice: ORDER_INVOICE_CATEGORIES,
|
|
793
|
+
quote_proforma: QUOTE_PROFORMA_CATEGORIES
|
|
496
794
|
};
|
|
795
|
+
const PLACEHOLDER_LOGO_BASE64 = "data:image/svg+xml;base64," + btoa(`<svg xmlns="http://www.w3.org/2000/svg" width="120" height="40" viewBox="0 0 120 40"><rect width="120" height="40" rx="4" fill="#e2e8f0"/><text x="60" y="24" text-anchor="middle" fill="#64748b" font-family="sans-serif" font-size="11">LOGO</text></svg>`);
|
|
497
796
|
const SAMPLE_DATA = {
|
|
498
797
|
order_invoice: {
|
|
499
798
|
company_name: "Mi Empresa",
|
|
@@ -501,7 +800,7 @@ const SAMPLE_DATA = {
|
|
|
501
800
|
company_address: "Av. Principal 123, Quito",
|
|
502
801
|
company_phone: "+593 99 123 4567",
|
|
503
802
|
company_email: "info@miempresa.com",
|
|
504
|
-
company_logo_base64:
|
|
803
|
+
company_logo_base64: PLACEHOLDER_LOGO_BASE64,
|
|
505
804
|
invoice_id: "INV-000001",
|
|
506
805
|
invoice_date: "19/06/2025",
|
|
507
806
|
order_display_id: "000042",
|
|
@@ -566,14 +865,22 @@ const TemplateEditorPage = () => {
|
|
|
566
865
|
queryKey: ["invoice-template", id],
|
|
567
866
|
enabled: !!id
|
|
568
867
|
});
|
|
868
|
+
const { data: companiesData } = reactQuery.useQuery({
|
|
869
|
+
queryFn: () => sdk.client.fetch("/admin/invoice-config"),
|
|
870
|
+
queryKey: ["invoice-configs"]
|
|
871
|
+
});
|
|
569
872
|
const template = data == null ? void 0 : data.invoice_template;
|
|
873
|
+
const companies = (companiesData == null ? void 0 : companiesData.invoice_configs) ?? [];
|
|
570
874
|
const [name, setName] = react.useState("");
|
|
571
875
|
const [htmlContent, setHtmlContent] = react.useState("");
|
|
876
|
+
const [companyId, setCompanyId] = react.useState(null);
|
|
572
877
|
const [showVariables, setShowVariables] = react.useState(false);
|
|
878
|
+
const [collapsedSections, setCollapsedSections] = react.useState({});
|
|
573
879
|
react.useEffect(() => {
|
|
574
880
|
if (template) {
|
|
575
881
|
setName(template.name);
|
|
576
882
|
setHtmlContent(template.html_content);
|
|
883
|
+
setCompanyId(template.company_id);
|
|
577
884
|
}
|
|
578
885
|
}, [template]);
|
|
579
886
|
const saveMutation = reactQuery.useMutation({
|
|
@@ -590,7 +897,7 @@ const TemplateEditorPage = () => {
|
|
|
590
897
|
}
|
|
591
898
|
});
|
|
592
899
|
const handleSave = () => {
|
|
593
|
-
saveMutation.mutate({ name, html_content: htmlContent });
|
|
900
|
+
saveMutation.mutate({ name, html_content: htmlContent, company_id: companyId });
|
|
594
901
|
};
|
|
595
902
|
const handlePreviewPdf = async () => {
|
|
596
903
|
try {
|
|
@@ -611,7 +918,7 @@ const TemplateEditorPage = () => {
|
|
|
611
918
|
if (!(template == null ? void 0 : template.is_default)) return;
|
|
612
919
|
if (!confirm("¿Restaurar la plantilla a su contenido original?")) return;
|
|
613
920
|
try {
|
|
614
|
-
|
|
921
|
+
await sdk.client.fetch(
|
|
615
922
|
`/admin/invoice-templates/${id}/restore`,
|
|
616
923
|
{ method: "POST" }
|
|
617
924
|
);
|
|
@@ -630,77 +937,102 @@ const TemplateEditorPage = () => {
|
|
|
630
937
|
return `<div style="color:red;padding:20px;">Error en la plantilla Handlebars</div>`;
|
|
631
938
|
}
|
|
632
939
|
}, [htmlContent, template]);
|
|
633
|
-
const insertVariable = react.useCallback((variable) => {
|
|
634
|
-
const isBlock = variable.startsWith("{{#") || variable.startsWith("{{/");
|
|
940
|
+
const insertVariable = react.useCallback((variable, isBlock) => {
|
|
635
941
|
const insertion = isBlock ? variable : `{{${variable}}}`;
|
|
636
942
|
setHtmlContent((prev) => prev + insertion);
|
|
637
943
|
}, []);
|
|
638
|
-
const
|
|
944
|
+
const toggleSection = react.useCallback((label) => {
|
|
945
|
+
setCollapsedSections((prev) => ({ ...prev, [label]: !prev[label] }));
|
|
946
|
+
}, []);
|
|
947
|
+
const categories = template ? CATEGORIES[template.type] ?? [] : [];
|
|
639
948
|
if (isLoading) {
|
|
640
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
949
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10", children: "Cargando..." }) });
|
|
641
950
|
}
|
|
642
951
|
if (!template) {
|
|
643
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
952
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10", children: "Plantilla no encontrada" }) });
|
|
644
953
|
}
|
|
645
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
646
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
647
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
954
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-0", children: [
|
|
955
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-ui-border-base", children: [
|
|
956
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
648
957
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => navigate("/invoice-config/invoice-templates"), children: "← Volver" }),
|
|
649
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
958
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
650
959
|
ui.Input,
|
|
651
960
|
{
|
|
652
961
|
value: name,
|
|
653
962
|
onChange: (e) => setName(e.target.value),
|
|
654
|
-
|
|
963
|
+
className: "font-semibold text-base w-64"
|
|
655
964
|
}
|
|
656
|
-
)
|
|
657
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
965
|
+
),
|
|
966
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-xs", children: [
|
|
658
967
|
template.slug,
|
|
659
968
|
" (",
|
|
660
969
|
template.type,
|
|
661
970
|
")"
|
|
662
971
|
] })
|
|
663
972
|
] }),
|
|
664
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
665
|
-
|
|
973
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
974
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
975
|
+
ui.Select,
|
|
976
|
+
{
|
|
977
|
+
value: companyId ?? "__none__",
|
|
978
|
+
onValueChange: (v) => setCompanyId(v === "__none__" ? null : v),
|
|
979
|
+
children: [
|
|
980
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { className: "w-48", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Empresa" }) }),
|
|
981
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
982
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "__none__", children: "Sin empresa (usa default)" }),
|
|
983
|
+
companies.map((c) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Item, { value: c.id, children: [
|
|
984
|
+
c.company_name,
|
|
985
|
+
c.is_default ? " ★" : ""
|
|
986
|
+
] }, c.id))
|
|
987
|
+
] })
|
|
988
|
+
]
|
|
989
|
+
}
|
|
990
|
+
),
|
|
991
|
+
template.is_default && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: handleRestoreDefault, children: "Restaurar" }),
|
|
666
992
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => setShowVariables(!showVariables), children: showVariables ? "Ocultar Variables" : "Variables" }),
|
|
667
993
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: handlePreviewPdf, children: "Preview PDF" }),
|
|
668
994
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSave, isLoading: saveMutation.isPending, children: "Guardar" })
|
|
669
995
|
] })
|
|
670
996
|
] }),
|
|
671
|
-
showVariables && /* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
997
|
+
showVariables && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-ui-border-base bg-ui-bg-subtle px-4 py-3", children: [
|
|
998
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { className: "block mb-2 text-sm", children: "Variables Handlebars — click para insertar" }),
|
|
999
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-2", children: categories.map((cat) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border border-ui-border-base rounded-lg bg-ui-bg-base overflow-hidden", children: [
|
|
1000
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1001
|
+
"button",
|
|
1002
|
+
{
|
|
1003
|
+
type: "button",
|
|
1004
|
+
className: "w-full flex items-center justify-between px-3 py-2 text-left text-sm font-medium hover:bg-ui-bg-subtle-hover transition-colors",
|
|
1005
|
+
onClick: () => toggleSection(cat.label),
|
|
1006
|
+
children: [
|
|
1007
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1008
|
+
cat.icon,
|
|
1009
|
+
" ",
|
|
1010
|
+
cat.label
|
|
1011
|
+
] }),
|
|
1012
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-ui-fg-muted text-xs", children: [
|
|
1013
|
+
collapsedSections[cat.label] ? "▸" : "▾",
|
|
1014
|
+
" ",
|
|
1015
|
+
cat.variables.length
|
|
1016
|
+
] })
|
|
1017
|
+
]
|
|
1018
|
+
}
|
|
1019
|
+
),
|
|
1020
|
+
!collapsedSections[cat.label] && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3 pb-2 flex flex-wrap gap-1.5", children: cat.variables.map((v, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1021
|
+
ui.Badge,
|
|
1022
|
+
{
|
|
1023
|
+
color: v.isBlock ? "orange" : "blue",
|
|
1024
|
+
className: "cursor-pointer hover:opacity-80 transition-opacity text-xs",
|
|
1025
|
+
onClick: () => insertVariable(v.name, v.isBlock),
|
|
1026
|
+
children: v.isBlock ? v.name : `{{${v.name}}}`
|
|
694
1027
|
},
|
|
695
|
-
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
))
|
|
1028
|
+
`${cat.label}-${i}`
|
|
1029
|
+
)) })
|
|
1030
|
+
] }, cat.label)) })
|
|
699
1031
|
] }),
|
|
700
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 16, height: "calc(100vh - 260px)" }, children: [
|
|
1032
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 16, height: "calc(100vh - 260px)", padding: 16 }, children: [
|
|
701
1033
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }, children: [
|
|
702
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, {
|
|
703
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
1034
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { className: "mb-2", children: "Editor HTML + Handlebars" }),
|
|
1035
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 border border-ui-border-base rounded-lg overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
704
1036
|
CodeMirror__default.default,
|
|
705
1037
|
{
|
|
706
1038
|
ref: editorRef,
|
|
@@ -714,14 +1046,8 @@ const TemplateEditorPage = () => {
|
|
|
714
1046
|
) })
|
|
715
1047
|
] }),
|
|
716
1048
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }, children: [
|
|
717
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, {
|
|
718
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
719
|
-
flex: 1,
|
|
720
|
-
border: "1px solid #e2e8f0",
|
|
721
|
-
borderRadius: 8,
|
|
722
|
-
overflow: "auto",
|
|
723
|
-
background: "#fff"
|
|
724
|
-
}, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1049
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { className: "mb-2", children: "Preview en vivo (datos de ejemplo)" }),
|
|
1050
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 border border-ui-border-base rounded-lg overflow-auto bg-white", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
725
1051
|
"iframe",
|
|
726
1052
|
{
|
|
727
1053
|
srcDoc: previewHtml,
|
|
@@ -734,6 +1060,96 @@ const TemplateEditorPage = () => {
|
|
|
734
1060
|
] })
|
|
735
1061
|
] });
|
|
736
1062
|
};
|
|
1063
|
+
const NewTemplatePage = () => {
|
|
1064
|
+
const navigate = reactRouterDom.useNavigate();
|
|
1065
|
+
const [name, setName] = react.useState("");
|
|
1066
|
+
const [slug, setSlug] = react.useState("");
|
|
1067
|
+
const [type, setType] = react.useState("order_invoice");
|
|
1068
|
+
const createMutation = reactQuery.useMutation({
|
|
1069
|
+
mutationFn: (payload) => sdk.client.fetch("/admin/invoice-templates", {
|
|
1070
|
+
method: "POST",
|
|
1071
|
+
body: payload
|
|
1072
|
+
}),
|
|
1073
|
+
onSuccess: (data) => {
|
|
1074
|
+
ui.toast.success("Plantilla creada");
|
|
1075
|
+
navigate(`/invoice-config/invoice-templates/${data.invoice_template.id}`);
|
|
1076
|
+
},
|
|
1077
|
+
onError: () => {
|
|
1078
|
+
ui.toast.error("Error al crear la plantilla");
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
const handleCreate = () => {
|
|
1082
|
+
if (!name || !slug) {
|
|
1083
|
+
ui.toast.error("Nombre y slug son requeridos");
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
createMutation.mutate({
|
|
1087
|
+
name,
|
|
1088
|
+
slug,
|
|
1089
|
+
type,
|
|
1090
|
+
html_content: getDefaultHtml(type)
|
|
1091
|
+
});
|
|
1092
|
+
};
|
|
1093
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
1094
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", style: { marginBottom: 24 }, children: "Nueva Plantilla" }),
|
|
1095
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 16, maxWidth: 500 }, children: [
|
|
1096
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1097
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "name", children: "Nombre" }),
|
|
1098
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { id: "name", value: name, onChange: (e) => setName(e.target.value), placeholder: "Ej: Mi Comprobante" })
|
|
1099
|
+
] }),
|
|
1100
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1101
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "slug", children: "Slug (identificador único)" }),
|
|
1102
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1103
|
+
ui.Input,
|
|
1104
|
+
{
|
|
1105
|
+
id: "slug",
|
|
1106
|
+
value: slug,
|
|
1107
|
+
onChange: (e) => setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, "_")),
|
|
1108
|
+
placeholder: "Ej: my_custom_invoice"
|
|
1109
|
+
}
|
|
1110
|
+
)
|
|
1111
|
+
] }),
|
|
1112
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1113
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { htmlFor: "type", children: "Tipo de documento" }),
|
|
1114
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value: type, onValueChange: setType, children: [
|
|
1115
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Seleccionar tipo" }) }),
|
|
1116
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
1117
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "order_invoice", children: "Comprobante de Pedido" }),
|
|
1118
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "quote_proforma", children: "Cotización Proforma" })
|
|
1119
|
+
] })
|
|
1120
|
+
] })
|
|
1121
|
+
] }),
|
|
1122
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, marginTop: 16 }, children: [
|
|
1123
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/invoice-config/invoice-templates"), children: "Cancelar" }),
|
|
1124
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleCreate, isLoading: createMutation.isPending, children: "Crear Plantilla" })
|
|
1125
|
+
] })
|
|
1126
|
+
] })
|
|
1127
|
+
] });
|
|
1128
|
+
};
|
|
1129
|
+
function getDefaultHtml(type) {
|
|
1130
|
+
if (type === "quote_proforma") {
|
|
1131
|
+
return `<!DOCTYPE html>
|
|
1132
|
+
<html>
|
|
1133
|
+
<head><style>body { font-family: Helvetica, sans-serif; }</style></head>
|
|
1134
|
+
<body>
|
|
1135
|
+
<h1>COTIZACIÓN {{quote_number}}</h1>
|
|
1136
|
+
<p>Fecha: {{date_str}}</p>
|
|
1137
|
+
<p>Servicio: {{service_name}}</p>
|
|
1138
|
+
<!-- Personaliza tu plantilla aquí -->
|
|
1139
|
+
</body>
|
|
1140
|
+
</html>`;
|
|
1141
|
+
}
|
|
1142
|
+
return `<!DOCTYPE html>
|
|
1143
|
+
<html>
|
|
1144
|
+
<head><style>body { font-family: Helvetica, sans-serif; }</style></head>
|
|
1145
|
+
<body>
|
|
1146
|
+
<h1>COMPROBANTE {{invoice_id}}</h1>
|
|
1147
|
+
<p>Fecha: {{invoice_date}}</p>
|
|
1148
|
+
<p>Empresa: {{company_name}}</p>
|
|
1149
|
+
<!-- Personaliza tu plantilla aquí -->
|
|
1150
|
+
</body>
|
|
1151
|
+
</html>`;
|
|
1152
|
+
}
|
|
737
1153
|
const i18nTranslations0 = {};
|
|
738
1154
|
const widgetModule = { widgets: [
|
|
739
1155
|
{
|
|
@@ -747,17 +1163,29 @@ const routeModule = {
|
|
|
747
1163
|
Component: InvoiceConfigPage,
|
|
748
1164
|
path: "/invoice-config"
|
|
749
1165
|
},
|
|
1166
|
+
{
|
|
1167
|
+
Component: CompaniesPage,
|
|
1168
|
+
path: "/invoice-config/companies"
|
|
1169
|
+
},
|
|
750
1170
|
{
|
|
751
1171
|
Component: InvoiceTemplatesPage,
|
|
752
1172
|
path: "/invoice-config/invoice-templates"
|
|
753
1173
|
},
|
|
754
1174
|
{
|
|
755
|
-
Component:
|
|
756
|
-
path: "/invoice-config/
|
|
1175
|
+
Component: NewCompanyPage,
|
|
1176
|
+
path: "/invoice-config/companies/new"
|
|
1177
|
+
},
|
|
1178
|
+
{
|
|
1179
|
+
Component: EditCompanyPage,
|
|
1180
|
+
path: "/invoice-config/companies/:id"
|
|
757
1181
|
},
|
|
758
1182
|
{
|
|
759
1183
|
Component: TemplateEditorPage,
|
|
760
1184
|
path: "/invoice-config/invoice-templates/:id"
|
|
1185
|
+
},
|
|
1186
|
+
{
|
|
1187
|
+
Component: NewTemplatePage,
|
|
1188
|
+
path: "/invoice-config/invoice-templates/new"
|
|
761
1189
|
}
|
|
762
1190
|
]
|
|
763
1191
|
};
|