@codemind.ec/medusa-plugin-invoice 1.0.6 → 1.2.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 +737 -306
- package/.medusa/server/src/admin/index.mjs +740 -309
- 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/.medusa/server/src/modules/invoice-generator/templates/order-invoice.js +3 -10
- package/.medusa/server/src/modules/invoice-generator/templates/quote-proforma.js +3 -2
- package/.medusa/server/src/modules/invoice-generator/templates/strategy.js +19 -1
- package/README.md +91 -25
- 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,13 +308,356 @@ 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
|
});
|
|
318
|
+
const EditCompanyPage = () => {
|
|
319
|
+
const { id } = reactRouterDom.useParams();
|
|
320
|
+
const navigate = reactRouterDom.useNavigate();
|
|
321
|
+
const queryClient = reactQuery.useQueryClient();
|
|
322
|
+
const { data, isLoading } = reactQuery.useQuery({
|
|
323
|
+
queryFn: () => sdk.client.fetch(`/admin/invoice-config/${id}`),
|
|
324
|
+
queryKey: ["invoice-config", id],
|
|
325
|
+
enabled: !!id
|
|
326
|
+
});
|
|
327
|
+
const config2 = data == null ? void 0 : data.invoice_config;
|
|
328
|
+
const getDefaults = react.useCallback(() => ({
|
|
329
|
+
company_name: (config2 == null ? void 0 : config2.company_name) || "",
|
|
330
|
+
company_ruc: (config2 == null ? void 0 : config2.company_ruc) || "",
|
|
331
|
+
company_address: (config2 == null ? void 0 : config2.company_address) || "",
|
|
332
|
+
company_phone: (config2 == null ? void 0 : config2.company_phone) || "",
|
|
333
|
+
company_email: (config2 == null ? void 0 : config2.company_email) || "",
|
|
334
|
+
company_logo: (config2 == null ? void 0 : config2.company_logo) || "",
|
|
335
|
+
notes: (config2 == null ? void 0 : config2.notes) || "",
|
|
336
|
+
admin_notification_email: (config2 == null ? void 0 : config2.admin_notification_email) || "",
|
|
337
|
+
is_default: (config2 == null ? void 0 : config2.is_default) || false
|
|
338
|
+
}), [config2]);
|
|
339
|
+
const form = reactHookForm.useForm({ defaultValues: getDefaults() });
|
|
340
|
+
react.useEffect(() => {
|
|
341
|
+
form.reset(getDefaults());
|
|
342
|
+
}, [getDefaults]);
|
|
343
|
+
const saveMutation = reactQuery.useMutation({
|
|
344
|
+
mutationFn: (payload) => sdk.client.fetch(`/admin/invoice-config/${id}`, {
|
|
345
|
+
method: "POST",
|
|
346
|
+
body: payload
|
|
347
|
+
}),
|
|
348
|
+
onSuccess: () => {
|
|
349
|
+
queryClient.invalidateQueries({ queryKey: ["invoice-config", id] });
|
|
350
|
+
queryClient.invalidateQueries({ queryKey: ["invoice-configs"] });
|
|
351
|
+
ui.toast.success("Empresa actualizada");
|
|
352
|
+
},
|
|
353
|
+
onError: () => ui.toast.error("Error al guardar")
|
|
354
|
+
});
|
|
355
|
+
const uploadLogo = async (event) => {
|
|
356
|
+
var _a;
|
|
357
|
+
const file = (_a = event.target.files) == null ? void 0 : _a[0];
|
|
358
|
+
if (!file) return;
|
|
359
|
+
try {
|
|
360
|
+
const { files } = await sdk.admin.upload.create({ files: [file] });
|
|
361
|
+
form.setValue("company_logo", files[0].url);
|
|
362
|
+
} catch {
|
|
363
|
+
ui.toast.error("Error al subir el logo.");
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
const handleSubmit = form.handleSubmit((data2) => saveMutation.mutate(data2));
|
|
367
|
+
if (isLoading) {
|
|
368
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10", children: "Cargando..." }) });
|
|
369
|
+
}
|
|
370
|
+
if (!config2) {
|
|
371
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10", children: "Empresa no encontrada" }) });
|
|
372
|
+
}
|
|
373
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "divide-y p-0", children: [
|
|
374
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
|
|
375
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h1", children: "Editar Empresa" }),
|
|
376
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/invoice-config/companies"), children: "← Volver" })
|
|
377
|
+
] }),
|
|
378
|
+
/* @__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: [
|
|
379
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-ui-bg-subtle border border-ui-border-base rounded-lg p-4 mb-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
380
|
+
reactHookForm.Controller,
|
|
381
|
+
{
|
|
382
|
+
control: form.control,
|
|
383
|
+
name: "admin_notification_email",
|
|
384
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
385
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "📧 Email de Notificaciones (Admin)" }),
|
|
386
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field, placeholder: "admin@empresa.com" }),
|
|
387
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: "Notificaciones de nuevos pedidos a este email." })
|
|
388
|
+
] })
|
|
389
|
+
}
|
|
390
|
+
) }),
|
|
391
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
392
|
+
reactHookForm.Controller,
|
|
393
|
+
{
|
|
394
|
+
control: form.control,
|
|
395
|
+
name: "company_name",
|
|
396
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
397
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Nombre de la Empresa *" }),
|
|
398
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field })
|
|
399
|
+
] })
|
|
400
|
+
}
|
|
401
|
+
),
|
|
402
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
403
|
+
reactHookForm.Controller,
|
|
404
|
+
{
|
|
405
|
+
control: form.control,
|
|
406
|
+
name: "company_ruc",
|
|
407
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
408
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "RUC" }),
|
|
409
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field, placeholder: "1234567890001" })
|
|
410
|
+
] })
|
|
411
|
+
}
|
|
412
|
+
),
|
|
413
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
414
|
+
reactHookForm.Controller,
|
|
415
|
+
{
|
|
416
|
+
control: form.control,
|
|
417
|
+
name: "company_address",
|
|
418
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
419
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Dirección" }),
|
|
420
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { ...field })
|
|
421
|
+
] })
|
|
422
|
+
}
|
|
423
|
+
),
|
|
424
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
425
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
426
|
+
reactHookForm.Controller,
|
|
427
|
+
{
|
|
428
|
+
control: form.control,
|
|
429
|
+
name: "company_phone",
|
|
430
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
431
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Teléfono" }),
|
|
432
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field })
|
|
433
|
+
] })
|
|
434
|
+
}
|
|
435
|
+
),
|
|
436
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
437
|
+
reactHookForm.Controller,
|
|
438
|
+
{
|
|
439
|
+
control: form.control,
|
|
440
|
+
name: "company_email",
|
|
441
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
442
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Email de la Empresa" }),
|
|
443
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { ...field })
|
|
444
|
+
] })
|
|
445
|
+
}
|
|
446
|
+
)
|
|
447
|
+
] }),
|
|
448
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
449
|
+
reactHookForm.Controller,
|
|
450
|
+
{
|
|
451
|
+
control: form.control,
|
|
452
|
+
name: "notes",
|
|
453
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
454
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Notas" }),
|
|
455
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Textarea, { ...field })
|
|
456
|
+
] })
|
|
457
|
+
}
|
|
458
|
+
),
|
|
459
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
460
|
+
reactHookForm.Controller,
|
|
461
|
+
{
|
|
462
|
+
control: form.control,
|
|
463
|
+
name: "company_logo",
|
|
464
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
465
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Logo de la Empresa" }),
|
|
466
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Input, { type: "file", onChange: uploadLogo, className: "py-1" }),
|
|
467
|
+
field.value && /* @__PURE__ */ jsxRuntime.jsx("img", { src: field.value, alt: "Logo", className: "mt-2 h-24 w-24 object-contain" })
|
|
468
|
+
] })
|
|
469
|
+
}
|
|
470
|
+
),
|
|
471
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
472
|
+
reactHookForm.Controller,
|
|
473
|
+
{
|
|
474
|
+
control: form.control,
|
|
475
|
+
name: "is_default",
|
|
476
|
+
render: ({ field }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 bg-ui-bg-subtle rounded-lg p-3", children: [
|
|
477
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
478
|
+
ui.Switch,
|
|
479
|
+
{
|
|
480
|
+
checked: field.value,
|
|
481
|
+
onCheckedChange: field.onChange
|
|
482
|
+
}
|
|
483
|
+
),
|
|
484
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
485
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { size: "small", weight: "plus", children: "Empresa por defecto" }),
|
|
486
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { className: "text-ui-fg-subtle text-xs", children: "Se usará en plantillas que no tengan empresa asignada." })
|
|
487
|
+
] })
|
|
488
|
+
] })
|
|
489
|
+
}
|
|
490
|
+
),
|
|
491
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 pt-2", children: [
|
|
492
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", onClick: () => navigate("/invoice-config/companies"), children: "Cancelar" }),
|
|
493
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "submit", isLoading: saveMutation.isPending, children: "Guardar Cambios" })
|
|
494
|
+
] })
|
|
495
|
+
] }) })
|
|
496
|
+
] });
|
|
497
|
+
};
|
|
498
|
+
const NewCompanyPage = () => {
|
|
499
|
+
const navigate = reactRouterDom.useNavigate();
|
|
500
|
+
const form = reactHookForm.useForm({
|
|
501
|
+
defaultValues: {
|
|
502
|
+
company_name: "",
|
|
503
|
+
company_ruc: "",
|
|
504
|
+
company_address: "",
|
|
505
|
+
company_phone: "",
|
|
506
|
+
company_email: "",
|
|
507
|
+
company_logo: "",
|
|
508
|
+
notes: "",
|
|
509
|
+
admin_notification_email: "",
|
|
510
|
+
is_default: false
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
const createMutation = reactQuery.useMutation({
|
|
514
|
+
mutationFn: (payload) => sdk.client.fetch("/admin/invoice-config", {
|
|
515
|
+
method: "POST",
|
|
516
|
+
body: payload
|
|
517
|
+
}),
|
|
518
|
+
onSuccess: () => {
|
|
519
|
+
ui.toast.success("Empresa creada exitosamente");
|
|
520
|
+
navigate("/invoice-config/companies");
|
|
521
|
+
},
|
|
522
|
+
onError: () => ui.toast.error("Error al crear la empresa")
|
|
523
|
+
});
|
|
524
|
+
const uploadLogo = async (event) => {
|
|
525
|
+
var _a;
|
|
526
|
+
const file = (_a = event.target.files) == null ? void 0 : _a[0];
|
|
527
|
+
if (!file) return;
|
|
528
|
+
try {
|
|
529
|
+
const { files } = await sdk.admin.upload.create({ files: [file] });
|
|
530
|
+
form.setValue("company_logo", files[0].url);
|
|
531
|
+
} catch {
|
|
532
|
+
ui.toast.error("Error al subir el logo.");
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
const handleSubmit = form.handleSubmit((data) => createMutation.mutate(data));
|
|
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: "Nueva 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, placeholder: "Mi Empresa S.A.S." })
|
|
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, placeholder: "Av. Principal 123, Quito" })
|
|
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, placeholder: "+593 99 123 4567" })
|
|
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, placeholder: "info@empresa.com" })
|
|
607
|
+
] })
|
|
608
|
+
}
|
|
609
|
+
)
|
|
610
|
+
] }),
|
|
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, placeholder: "Notas internas o pie de página del documento" })
|
|
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: createMutation.isPending, children: "Crear Empresa" })
|
|
657
|
+
] })
|
|
658
|
+
] }) })
|
|
659
|
+
] });
|
|
660
|
+
};
|
|
347
661
|
const NewTemplatePage = () => {
|
|
348
662
|
const navigate = reactRouterDom.useNavigate();
|
|
349
663
|
const [name, setName] = react.useState("");
|
|
@@ -434,66 +748,139 @@ function getDefaultHtml(type) {
|
|
|
434
748
|
</body>
|
|
435
749
|
</html>`;
|
|
436
750
|
}
|
|
437
|
-
const
|
|
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
|
-
|
|
751
|
+
const COMPANY_VARIABLES = [
|
|
752
|
+
{ name: "company_name" },
|
|
753
|
+
{ name: "company_ruc" },
|
|
754
|
+
{ name: "company_address" },
|
|
755
|
+
{ name: "company_phone" },
|
|
756
|
+
{ name: "company_email" },
|
|
757
|
+
{ name: "company_logo_base64" }
|
|
758
|
+
];
|
|
759
|
+
const ORDER_INVOICE_CATEGORIES = [
|
|
760
|
+
{
|
|
761
|
+
label: "Empresa",
|
|
762
|
+
icon: "🏢",
|
|
763
|
+
variables: COMPANY_VARIABLES
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
label: "Documento",
|
|
767
|
+
icon: "📄",
|
|
768
|
+
variables: [
|
|
769
|
+
{ name: "invoice_id" },
|
|
770
|
+
{ name: "invoice_date" },
|
|
771
|
+
{ name: "order_display_id" },
|
|
772
|
+
{ name: "order_date" }
|
|
773
|
+
]
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
label: "Cliente",
|
|
777
|
+
icon: "👤",
|
|
778
|
+
variables: [
|
|
779
|
+
{ name: "billing_address" },
|
|
780
|
+
{ name: "shipping_address" },
|
|
781
|
+
{ name: "cedula" }
|
|
782
|
+
]
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
label: "Productos",
|
|
786
|
+
icon: "📦",
|
|
787
|
+
variables: [
|
|
788
|
+
{ name: "{{#each items}}", isBlock: true },
|
|
789
|
+
{ name: "this.title" },
|
|
790
|
+
{ name: "this.variant_title" },
|
|
791
|
+
{ name: "this.quantity" },
|
|
792
|
+
{ name: "this.unit_price" },
|
|
793
|
+
{ name: "this.total" },
|
|
794
|
+
{ name: "{{/each}}", isBlock: true }
|
|
795
|
+
]
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
label: "Totales",
|
|
799
|
+
icon: "💰",
|
|
800
|
+
variables: [
|
|
801
|
+
{ name: "subtotal" },
|
|
802
|
+
{ name: "tax_total" },
|
|
803
|
+
{ name: "shipping_total" },
|
|
804
|
+
{ name: "discount_total" },
|
|
805
|
+
{ name: "total" }
|
|
806
|
+
]
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
label: "Otros",
|
|
810
|
+
icon: "📝",
|
|
811
|
+
variables: [
|
|
812
|
+
{ name: "is_home_delivery" },
|
|
813
|
+
{ name: "notes" }
|
|
814
|
+
]
|
|
815
|
+
}
|
|
816
|
+
];
|
|
817
|
+
const QUOTE_PROFORMA_CATEGORIES = [
|
|
818
|
+
{
|
|
819
|
+
label: "Empresa",
|
|
820
|
+
icon: "🏢",
|
|
821
|
+
variables: [...COMPANY_VARIABLES, { name: "company_website" }]
|
|
822
|
+
},
|
|
823
|
+
{
|
|
824
|
+
label: "Documento",
|
|
825
|
+
icon: "📄",
|
|
826
|
+
variables: [
|
|
827
|
+
{ name: "quote_number" },
|
|
828
|
+
{ name: "date_str" },
|
|
829
|
+
{ name: "service_name" },
|
|
830
|
+
{ name: "is_home_delivery" }
|
|
831
|
+
]
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
label: "Configuración",
|
|
835
|
+
icon: "⚙️",
|
|
836
|
+
variables: [
|
|
837
|
+
{ name: "{{#each config_fields}}", isBlock: true },
|
|
838
|
+
{ name: "this.label" },
|
|
839
|
+
{ name: "this.value" },
|
|
840
|
+
{ name: "{{/each}}", isBlock: true }
|
|
841
|
+
]
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
label: "Desglose",
|
|
845
|
+
icon: "📊",
|
|
846
|
+
variables: [
|
|
847
|
+
{ name: "{{#each breakdown}}", isBlock: true },
|
|
848
|
+
{ name: "this.label" },
|
|
849
|
+
{ name: "this.total_formatted" },
|
|
850
|
+
{ name: "{{/each}}", isBlock: true }
|
|
851
|
+
]
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
label: "Totales",
|
|
855
|
+
icon: "💰",
|
|
856
|
+
variables: [
|
|
857
|
+
{ name: "totals.subtotal_formatted" },
|
|
858
|
+
{ name: "totals.extras_formatted" },
|
|
859
|
+
{ name: "totals.discount_formatted" },
|
|
860
|
+
{ name: "totals.shipping_formatted" },
|
|
861
|
+
{ name: "totals.taxes_provided" },
|
|
862
|
+
{ name: "totals.taxes_formatted" },
|
|
863
|
+
{ name: "totals.total_formatted" }
|
|
864
|
+
]
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
label: "Incluye y Contacto",
|
|
868
|
+
icon: "📋",
|
|
869
|
+
variables: [
|
|
870
|
+
{ name: "{{#each includes_left}}", isBlock: true },
|
|
871
|
+
{ name: "{{#each includes_right}}", isBlock: true },
|
|
872
|
+
{ name: "{{#each contact_rows}}", isBlock: true },
|
|
873
|
+
{ name: "this.label" },
|
|
874
|
+
{ name: "this.value" },
|
|
875
|
+
{ name: "{{/each}}", isBlock: true }
|
|
876
|
+
]
|
|
877
|
+
}
|
|
878
|
+
];
|
|
879
|
+
const CATEGORIES = {
|
|
880
|
+
order_invoice: ORDER_INVOICE_CATEGORIES,
|
|
881
|
+
quote_proforma: QUOTE_PROFORMA_CATEGORIES
|
|
496
882
|
};
|
|
883
|
+
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
884
|
const SAMPLE_DATA = {
|
|
498
885
|
order_invoice: {
|
|
499
886
|
company_name: "Mi Empresa",
|
|
@@ -501,7 +888,7 @@ const SAMPLE_DATA = {
|
|
|
501
888
|
company_address: "Av. Principal 123, Quito",
|
|
502
889
|
company_phone: "+593 99 123 4567",
|
|
503
890
|
company_email: "info@miempresa.com",
|
|
504
|
-
company_logo_base64:
|
|
891
|
+
company_logo_base64: PLACEHOLDER_LOGO_BASE64,
|
|
505
892
|
invoice_id: "INV-000001",
|
|
506
893
|
invoice_date: "19/06/2025",
|
|
507
894
|
order_display_id: "000042",
|
|
@@ -523,6 +910,11 @@ const SAMPLE_DATA = {
|
|
|
523
910
|
},
|
|
524
911
|
quote_proforma: {
|
|
525
912
|
company_name: "Mi Empresa",
|
|
913
|
+
company_ruc: "1234567890001",
|
|
914
|
+
company_address: "Av. Principal 123, Quito",
|
|
915
|
+
company_phone: "+593 99 123 4567",
|
|
916
|
+
company_email: "info@miempresa.com",
|
|
917
|
+
company_logo_base64: PLACEHOLDER_LOGO_BASE64,
|
|
526
918
|
company_website: "www.miempresa.com",
|
|
527
919
|
quote_number: "QP-2025-0001",
|
|
528
920
|
date_str: "19/06/2025",
|
|
@@ -566,14 +958,22 @@ const TemplateEditorPage = () => {
|
|
|
566
958
|
queryKey: ["invoice-template", id],
|
|
567
959
|
enabled: !!id
|
|
568
960
|
});
|
|
961
|
+
const { data: companiesData } = reactQuery.useQuery({
|
|
962
|
+
queryFn: () => sdk.client.fetch("/admin/invoice-config"),
|
|
963
|
+
queryKey: ["invoice-configs"]
|
|
964
|
+
});
|
|
569
965
|
const template = data == null ? void 0 : data.invoice_template;
|
|
966
|
+
const companies = (companiesData == null ? void 0 : companiesData.invoice_configs) ?? [];
|
|
570
967
|
const [name, setName] = react.useState("");
|
|
571
968
|
const [htmlContent, setHtmlContent] = react.useState("");
|
|
969
|
+
const [companyId, setCompanyId] = react.useState(null);
|
|
572
970
|
const [showVariables, setShowVariables] = react.useState(false);
|
|
971
|
+
const [collapsedSections, setCollapsedSections] = react.useState({});
|
|
573
972
|
react.useEffect(() => {
|
|
574
973
|
if (template) {
|
|
575
974
|
setName(template.name);
|
|
576
975
|
setHtmlContent(template.html_content);
|
|
976
|
+
setCompanyId(template.company_id);
|
|
577
977
|
}
|
|
578
978
|
}, [template]);
|
|
579
979
|
const saveMutation = reactQuery.useMutation({
|
|
@@ -590,7 +990,7 @@ const TemplateEditorPage = () => {
|
|
|
590
990
|
}
|
|
591
991
|
});
|
|
592
992
|
const handleSave = () => {
|
|
593
|
-
saveMutation.mutate({ name, html_content: htmlContent });
|
|
993
|
+
saveMutation.mutate({ name, html_content: htmlContent, company_id: companyId });
|
|
594
994
|
};
|
|
595
995
|
const handlePreviewPdf = async () => {
|
|
596
996
|
try {
|
|
@@ -611,7 +1011,7 @@ const TemplateEditorPage = () => {
|
|
|
611
1011
|
if (!(template == null ? void 0 : template.is_default)) return;
|
|
612
1012
|
if (!confirm("¿Restaurar la plantilla a su contenido original?")) return;
|
|
613
1013
|
try {
|
|
614
|
-
|
|
1014
|
+
await sdk.client.fetch(
|
|
615
1015
|
`/admin/invoice-templates/${id}/restore`,
|
|
616
1016
|
{ method: "POST" }
|
|
617
1017
|
);
|
|
@@ -630,77 +1030,102 @@ const TemplateEditorPage = () => {
|
|
|
630
1030
|
return `<div style="color:red;padding:20px;">Error en la plantilla Handlebars</div>`;
|
|
631
1031
|
}
|
|
632
1032
|
}, [htmlContent, template]);
|
|
633
|
-
const insertVariable = react.useCallback((variable) => {
|
|
634
|
-
const isBlock = variable.startsWith("{{#") || variable.startsWith("{{/");
|
|
1033
|
+
const insertVariable = react.useCallback((variable, isBlock) => {
|
|
635
1034
|
const insertion = isBlock ? variable : `{{${variable}}}`;
|
|
636
1035
|
setHtmlContent((prev) => prev + insertion);
|
|
637
1036
|
}, []);
|
|
638
|
-
const
|
|
1037
|
+
const toggleSection = react.useCallback((label) => {
|
|
1038
|
+
setCollapsedSections((prev) => ({ ...prev, [label]: !prev[label] }));
|
|
1039
|
+
}, []);
|
|
1040
|
+
const categories = template ? CATEGORIES[template.type] ?? [] : [];
|
|
639
1041
|
if (isLoading) {
|
|
640
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
1042
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10", children: "Cargando..." }) });
|
|
641
1043
|
}
|
|
642
1044
|
if (!template) {
|
|
643
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", {
|
|
1045
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-10", children: "Plantilla no encontrada" }) });
|
|
644
1046
|
}
|
|
645
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { children: [
|
|
646
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
647
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
1047
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "p-0", children: [
|
|
1048
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b border-ui-border-base", children: [
|
|
1049
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
648
1050
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => navigate("/invoice-config/invoice-templates"), children: "← Volver" }),
|
|
649
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1051
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
650
1052
|
ui.Input,
|
|
651
1053
|
{
|
|
652
1054
|
value: name,
|
|
653
1055
|
onChange: (e) => setName(e.target.value),
|
|
654
|
-
|
|
1056
|
+
className: "font-semibold text-base w-64"
|
|
655
1057
|
}
|
|
656
|
-
)
|
|
657
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1058
|
+
),
|
|
1059
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { className: "text-ui-fg-muted text-xs", children: [
|
|
658
1060
|
template.slug,
|
|
659
1061
|
" (",
|
|
660
1062
|
template.type,
|
|
661
1063
|
")"
|
|
662
1064
|
] })
|
|
663
1065
|
] }),
|
|
664
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", {
|
|
665
|
-
|
|
1066
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1067
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1068
|
+
ui.Select,
|
|
1069
|
+
{
|
|
1070
|
+
value: companyId ?? "__none__",
|
|
1071
|
+
onValueChange: (v) => setCompanyId(v === "__none__" ? null : v),
|
|
1072
|
+
children: [
|
|
1073
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { className: "w-48", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Empresa" }) }),
|
|
1074
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
1075
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: "__none__", children: "Sin empresa (usa default)" }),
|
|
1076
|
+
companies.map((c) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Item, { value: c.id, children: [
|
|
1077
|
+
c.company_name,
|
|
1078
|
+
c.is_default ? " ★" : ""
|
|
1079
|
+
] }, c.id))
|
|
1080
|
+
] })
|
|
1081
|
+
]
|
|
1082
|
+
}
|
|
1083
|
+
),
|
|
1084
|
+
template.is_default && /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: handleRestoreDefault, children: "Restaurar" }),
|
|
666
1085
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: () => setShowVariables(!showVariables), children: showVariables ? "Ocultar Variables" : "Variables" }),
|
|
667
1086
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "secondary", size: "small", onClick: handlePreviewPdf, children: "Preview PDF" }),
|
|
668
1087
|
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: handleSave, isLoading: saveMutation.isPending, children: "Guardar" })
|
|
669
1088
|
] })
|
|
670
1089
|
] }),
|
|
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
|
-
|
|
1090
|
+
showVariables && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-b border-ui-border-base bg-ui-bg-subtle px-4 py-3", children: [
|
|
1091
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { className: "block mb-2 text-sm", children: "Variables Handlebars — click para insertar" }),
|
|
1092
|
+
/* @__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: [
|
|
1093
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1094
|
+
"button",
|
|
1095
|
+
{
|
|
1096
|
+
type: "button",
|
|
1097
|
+
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",
|
|
1098
|
+
onClick: () => toggleSection(cat.label),
|
|
1099
|
+
children: [
|
|
1100
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
1101
|
+
cat.icon,
|
|
1102
|
+
" ",
|
|
1103
|
+
cat.label
|
|
1104
|
+
] }),
|
|
1105
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-ui-fg-muted text-xs", children: [
|
|
1106
|
+
collapsedSections[cat.label] ? "▸" : "▾",
|
|
1107
|
+
" ",
|
|
1108
|
+
cat.variables.length
|
|
1109
|
+
] })
|
|
1110
|
+
]
|
|
1111
|
+
}
|
|
1112
|
+
),
|
|
1113
|
+
!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(
|
|
1114
|
+
ui.Badge,
|
|
1115
|
+
{
|
|
1116
|
+
color: v.isBlock ? "orange" : "blue",
|
|
1117
|
+
className: "cursor-pointer hover:opacity-80 transition-opacity text-xs",
|
|
1118
|
+
onClick: () => insertVariable(v.name, v.isBlock),
|
|
1119
|
+
children: v.isBlock ? v.name : `{{${v.name}}}`
|
|
694
1120
|
},
|
|
695
|
-
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
))
|
|
1121
|
+
`${cat.label}-${i}`
|
|
1122
|
+
)) })
|
|
1123
|
+
] }, cat.label)) })
|
|
699
1124
|
] }),
|
|
700
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 16, height: "calc(100vh - 260px)" }, children: [
|
|
1125
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 16, height: "calc(100vh - 260px)", padding: 16 }, children: [
|
|
701
1126
|
/* @__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", {
|
|
1127
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { className: "mb-2", children: "Editor HTML + Handlebars" }),
|
|
1128
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 border border-ui-border-base rounded-lg overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
704
1129
|
CodeMirror__default.default,
|
|
705
1130
|
{
|
|
706
1131
|
ref: editorRef,
|
|
@@ -714,14 +1139,8 @@ const TemplateEditorPage = () => {
|
|
|
714
1139
|
) })
|
|
715
1140
|
] }),
|
|
716
1141
|
/* @__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(
|
|
1142
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { className: "mb-2", children: "Preview en vivo (datos de ejemplo)" }),
|
|
1143
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 border border-ui-border-base rounded-lg overflow-auto bg-white", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
725
1144
|
"iframe",
|
|
726
1145
|
{
|
|
727
1146
|
srcDoc: previewHtml,
|
|
@@ -747,10 +1166,22 @@ const routeModule = {
|
|
|
747
1166
|
Component: InvoiceConfigPage,
|
|
748
1167
|
path: "/invoice-config"
|
|
749
1168
|
},
|
|
1169
|
+
{
|
|
1170
|
+
Component: CompaniesPage,
|
|
1171
|
+
path: "/invoice-config/companies"
|
|
1172
|
+
},
|
|
750
1173
|
{
|
|
751
1174
|
Component: InvoiceTemplatesPage,
|
|
752
1175
|
path: "/invoice-config/invoice-templates"
|
|
753
1176
|
},
|
|
1177
|
+
{
|
|
1178
|
+
Component: EditCompanyPage,
|
|
1179
|
+
path: "/invoice-config/companies/:id"
|
|
1180
|
+
},
|
|
1181
|
+
{
|
|
1182
|
+
Component: NewCompanyPage,
|
|
1183
|
+
path: "/invoice-config/companies/new"
|
|
1184
|
+
},
|
|
754
1185
|
{
|
|
755
1186
|
Component: NewTemplatePage,
|
|
756
1187
|
path: "/invoice-config/invoice-templates/new"
|