@flowselections/mailbox-orders 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/_core-safelist.d.ts +2 -0
- package/dist-lib/_core-safelist.d.ts.map +1 -0
- package/dist-lib/_core-safelist.js +15 -0
- package/dist-lib/components/MailboxInboxPage.d.ts +2 -0
- package/dist-lib/components/MailboxInboxPage.d.ts.map +1 -0
- package/dist-lib/components/MailboxInboxPage.js +61 -0
- package/dist-lib/components/MailboxProcessedPage.d.ts +2 -0
- package/dist-lib/components/MailboxProcessedPage.d.ts.map +1 -0
- package/dist-lib/components/MailboxProcessedPage.js +21 -0
- package/dist-lib/components/MailboxProposalsPage.d.ts +2 -0
- package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -0
- package/dist-lib/components/MailboxProposalsPage.js +198 -0
- package/dist-lib/components/SearchableSelect.d.ts +18 -0
- package/dist-lib/components/SearchableSelect.d.ts.map +1 -0
- package/dist-lib/components/SearchableSelect.js +20 -0
- package/dist-lib/components/settings/ImapAccountsCard.d.ts +2 -0
- package/dist-lib/components/settings/ImapAccountsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/ImapAccountsCard.js +136 -0
- package/dist-lib/components/settings/ImapProfilesCard.d.ts +2 -0
- package/dist-lib/components/settings/ImapProfilesCard.d.ts.map +1 -0
- package/dist-lib/components/settings/ImapProfilesCard.js +101 -0
- package/dist-lib/components/settings/MailboxOrderTemplateCard.d.ts +2 -0
- package/dist-lib/components/settings/MailboxOrderTemplateCard.d.ts.map +1 -0
- package/dist-lib/components/settings/MailboxOrderTemplateCard.js +98 -0
- package/dist-lib/components/settings/MailboxSettingsCard.d.ts +2 -0
- package/dist-lib/components/settings/MailboxSettingsCard.d.ts.map +1 -0
- package/dist-lib/components/settings/MailboxSettingsCard.js +85 -0
- package/dist-lib/index.d.ts +12 -0
- package/dist-lib/index.d.ts.map +1 -0
- package/dist-lib/index.js +34 -0
- package/dist-lib/integrations/supabase/auth-attacher.d.ts +2 -0
- package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/auth-attacher.js +15 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +2978 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/auth-middleware.js +52 -0
- package/dist-lib/integrations/supabase/client.d.ts +2974 -0
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/client.js +13 -0
- package/dist-lib/integrations/supabase/client.server.d.ts +2974 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/client.server.js +30 -0
- package/dist-lib/integrations/supabase/types.d.ts +3119 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -0
- package/dist-lib/integrations/supabase/types.js +8 -0
- package/dist-lib/lib/imap.functions.d.ts +32852 -0
- package/dist-lib/lib/imap.functions.d.ts.map +1 -0
- package/dist-lib/lib/imap.functions.js +235 -0
- package/dist-lib/lib/mailbox-ai.server.d.ts +32 -0
- package/dist-lib/lib/mailbox-ai.server.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-ai.server.js +107 -0
- package/dist-lib/lib/mailbox-auto.server.d.ts +9 -0
- package/dist-lib/lib/mailbox-auto.server.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-auto.server.js +198 -0
- package/dist-lib/lib/mailbox-template.functions.d.ts +17913 -0
- package/dist-lib/lib/mailbox-template.functions.d.ts.map +1 -0
- package/dist-lib/lib/mailbox-template.functions.js +106 -0
- package/dist-lib/lib/mailbox.functions.d.ts +32888 -0
- package/dist-lib/lib/mailbox.functions.d.ts.map +1 -0
- package/dist-lib/lib/mailbox.functions.js +334 -0
- package/dist-lib/lib/utils.d.ts +3 -0
- package/dist-lib/lib/utils.d.ts.map +1 -0
- package/dist-lib/lib/utils.js +5 -0
- package/dist-lib/lib/validationSchemas.d.ts +15 -0
- package/dist-lib/lib/validationSchemas.d.ts.map +1 -0
- package/dist-lib/lib/validationSchemas.js +25 -0
- package/dist-lib/styles.css +1 -0
- package/package.json +96 -0
- package/public/flowselections-assets/template-module/README.md +15 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
// Server functions voor de Mailbox-bestellingen module
|
|
2
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
3
|
+
import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
// ─── Settings ────────────────────────────────────────────────────────────────
|
|
6
|
+
export const getMailboxSettings = createServerFn({ method: "GET" })
|
|
7
|
+
.middleware([requireSupabaseAuth])
|
|
8
|
+
.handler(async ({ context }) => {
|
|
9
|
+
const { data, error } = await context.supabase
|
|
10
|
+
.from("mailbox_settings")
|
|
11
|
+
.select("*")
|
|
12
|
+
.limit(1)
|
|
13
|
+
.maybeSingle();
|
|
14
|
+
if (error)
|
|
15
|
+
throw new Error(error.message);
|
|
16
|
+
return { settings: data };
|
|
17
|
+
});
|
|
18
|
+
const SettingsInput = z.object({
|
|
19
|
+
imap_host: z.string().max(255),
|
|
20
|
+
imap_port: z.number().int().min(1).max(65535),
|
|
21
|
+
imap_username: z.string().max(255),
|
|
22
|
+
imap_use_tls: z.boolean(),
|
|
23
|
+
folder: z.string().max(255),
|
|
24
|
+
polling_enabled: z.boolean(),
|
|
25
|
+
});
|
|
26
|
+
export const saveMailboxSettings = createServerFn({ method: "POST" })
|
|
27
|
+
.middleware([requireSupabaseAuth])
|
|
28
|
+
.inputValidator((input) => SettingsInput.parse(input))
|
|
29
|
+
.handler(async ({ data, context }) => {
|
|
30
|
+
const { data: existing } = await context.supabase
|
|
31
|
+
.from("mailbox_settings").select("id").limit(1).maybeSingle();
|
|
32
|
+
if (existing?.id) {
|
|
33
|
+
const { error } = await context.supabase
|
|
34
|
+
.from("mailbox_settings").update(data).eq("id", existing.id);
|
|
35
|
+
if (error)
|
|
36
|
+
throw new Error(error.message);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const { error } = await context.supabase
|
|
40
|
+
.from("mailbox_settings").insert({ ...data, singleton: true });
|
|
41
|
+
if (error)
|
|
42
|
+
throw new Error(error.message);
|
|
43
|
+
}
|
|
44
|
+
return { ok: true };
|
|
45
|
+
});
|
|
46
|
+
async function invokeMailboxImap(supabase, action) {
|
|
47
|
+
const { data, error } = await supabase.functions.invoke("mailbox-imap", {
|
|
48
|
+
body: { action },
|
|
49
|
+
});
|
|
50
|
+
if (error)
|
|
51
|
+
throw new Error(error.message ?? "Edge function aanroep mislukt");
|
|
52
|
+
return data;
|
|
53
|
+
}
|
|
54
|
+
export const testMailboxConnection = createServerFn({ method: "POST" })
|
|
55
|
+
.middleware([requireSupabaseAuth])
|
|
56
|
+
.handler(async ({ context }) => {
|
|
57
|
+
try {
|
|
58
|
+
return await invokeMailboxImap(context.supabase, "test");
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
return { ok: false, error: e?.message ?? "Onbekende fout" };
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// ─── Polling ─────────────────────────────────────────────────────────────────
|
|
65
|
+
export const pollMailbox = createServerFn({ method: "POST" })
|
|
66
|
+
.middleware([requireSupabaseAuth])
|
|
67
|
+
.handler(async ({ context }) => {
|
|
68
|
+
try {
|
|
69
|
+
const res = await invokeMailboxImap(context.supabase, "poll");
|
|
70
|
+
let autoProcessed = 0;
|
|
71
|
+
let autoApproved = 0;
|
|
72
|
+
if (res.ok && (res.inserted ?? 0) > 0) {
|
|
73
|
+
const { autoProcessNewMessagesInternal } = await import("./mailbox-auto.server");
|
|
74
|
+
const r = await autoProcessNewMessagesInternal();
|
|
75
|
+
autoProcessed = r.processed;
|
|
76
|
+
autoApproved = r.approved;
|
|
77
|
+
}
|
|
78
|
+
return { ok: !!res.ok, inserted: res.inserted ?? 0, autoProcessed, autoApproved, error: res.error };
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return { ok: false, error: e?.message ?? "Onbekende fout", inserted: 0 };
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// ─── Messages & Proposals ────────────────────────────────────────────────────
|
|
85
|
+
export const listMessages = createServerFn({ method: "GET" })
|
|
86
|
+
.middleware([requireSupabaseAuth])
|
|
87
|
+
.handler(async ({ context }) => {
|
|
88
|
+
const { data, error } = await context.supabase
|
|
89
|
+
.from("mailbox_messages")
|
|
90
|
+
.select("id, uid, from_email, from_name, subject, received_at, status, error_message, created_at")
|
|
91
|
+
.order("received_at", { ascending: false })
|
|
92
|
+
.limit(200);
|
|
93
|
+
if (error)
|
|
94
|
+
throw new Error(error.message);
|
|
95
|
+
return { items: data ?? [] };
|
|
96
|
+
});
|
|
97
|
+
export const parseMessage = createServerFn({ method: "POST" })
|
|
98
|
+
.middleware([requireSupabaseAuth])
|
|
99
|
+
.inputValidator((input) => z.object({ messageId: z.string().uuid() }).parse(input))
|
|
100
|
+
.handler(async ({ data }) => {
|
|
101
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server");
|
|
102
|
+
const { data: msg, error: msgErr } = await supabaseAdmin
|
|
103
|
+
.from("mailbox_messages").select("*").eq("id", data.messageId).single();
|
|
104
|
+
if (msgErr || !msg)
|
|
105
|
+
throw new Error("Bericht niet gevonden");
|
|
106
|
+
await supabaseAdmin.from("mailbox_messages")
|
|
107
|
+
.update({ status: "parsing", error_message: null }).eq("id", msg.id);
|
|
108
|
+
try {
|
|
109
|
+
// Templatevelden ophalen
|
|
110
|
+
const { data: fields } = await supabaseAdmin
|
|
111
|
+
.from("mailbox_order_template_fields")
|
|
112
|
+
.select("*")
|
|
113
|
+
.order("sort_order", { ascending: true });
|
|
114
|
+
const templateFields = (fields ?? []).filter((f) => f.ai_enabled);
|
|
115
|
+
// Databron-catalogi
|
|
116
|
+
const [{ data: products }, { data: customers }, { data: suppliers }] = await Promise.all([
|
|
117
|
+
supabaseAdmin.from("products").select("id, product, product_type").order("product").range(0, 1999),
|
|
118
|
+
supabaseAdmin.from("customers").select("id, company_name, email").eq("is_active", true).order("company_name").range(0, 4999),
|
|
119
|
+
supabaseAdmin.from("suppliers").select("id, name").order("name").range(0, 999),
|
|
120
|
+
]);
|
|
121
|
+
const catalog = (products ?? []).map((p) => ({ id: p.id, name: p.product }));
|
|
122
|
+
const plantTypeSet = new Set();
|
|
123
|
+
(products ?? []).forEach((p) => { if (p.product_type)
|
|
124
|
+
plantTypeSet.add(p.product_type); });
|
|
125
|
+
const plantTypes = Array.from(plantTypeSet).sort().map((v) => ({ id: v, name: v }));
|
|
126
|
+
// Bouw spec met allowed values per database-veld
|
|
127
|
+
const spec = templateFields.map((f) => {
|
|
128
|
+
let allowed;
|
|
129
|
+
if (f.source === "database") {
|
|
130
|
+
switch (f.field_type) {
|
|
131
|
+
case "customer":
|
|
132
|
+
allowed = (customers ?? []).map((c) => ({ id: c.id, name: c.company_name }));
|
|
133
|
+
break;
|
|
134
|
+
case "product":
|
|
135
|
+
allowed = catalog;
|
|
136
|
+
break;
|
|
137
|
+
case "plant_type":
|
|
138
|
+
allowed = plantTypes;
|
|
139
|
+
break;
|
|
140
|
+
case "supplier":
|
|
141
|
+
allowed = (suppliers ?? []).map((s) => ({ id: s.id, name: s.name }));
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
key: f.key,
|
|
147
|
+
label: f.label,
|
|
148
|
+
field_type: f.field_type,
|
|
149
|
+
ai_hint: f.ai_hint,
|
|
150
|
+
allowed,
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
const { parseOrderEmail } = await import("./mailbox-ai.server");
|
|
154
|
+
const parsed = await parseOrderEmail(msg.body_text ?? msg.body_html ?? "", msg.subject ?? "", spec, catalog);
|
|
155
|
+
// Backwards-compat: leid klant/leverdatum/notes af voor de bestaande kolommen
|
|
156
|
+
const customerId = parsed.fields["customer"]?.value;
|
|
157
|
+
const deliveryDate = parsed.fields["delivery_date"]?.value;
|
|
158
|
+
const notesValue = parsed.fields["notes"]?.value;
|
|
159
|
+
const matchedCustomerId = typeof customerId === "string" && customerId.length === 36 ? customerId : null;
|
|
160
|
+
// Fallback: match op e-mail als AI niets vond
|
|
161
|
+
let finalCustomerId = matchedCustomerId;
|
|
162
|
+
if (!finalCustomerId && msg.from_email) {
|
|
163
|
+
const { data: customer } = await supabaseAdmin
|
|
164
|
+
.from("customers").select("id").eq("email", msg.from_email).maybeSingle();
|
|
165
|
+
finalCustomerId = customer?.id ?? null;
|
|
166
|
+
}
|
|
167
|
+
const { data: proposal, error: propErr } = await supabaseAdmin
|
|
168
|
+
.from("mailbox_order_proposals").insert({
|
|
169
|
+
message_id: msg.id,
|
|
170
|
+
matched_customer_id: finalCustomerId,
|
|
171
|
+
confidence: parsed.overall_confidence,
|
|
172
|
+
delivery_date: typeof deliveryDate === "string" ? deliveryDate : null,
|
|
173
|
+
notes: typeof notesValue === "string" ? notesValue : null,
|
|
174
|
+
parsed_payload: parsed,
|
|
175
|
+
status: "pending",
|
|
176
|
+
}).select("id").single();
|
|
177
|
+
if (propErr)
|
|
178
|
+
throw new Error(propErr.message);
|
|
179
|
+
// Sla alle veldwaarden op
|
|
180
|
+
const fieldRows = Object.entries(parsed.fields).map(([key, v]) => ({
|
|
181
|
+
proposal_id: proposal.id,
|
|
182
|
+
field_key: key,
|
|
183
|
+
value: v.value === undefined ? null : v.value,
|
|
184
|
+
ai_confidence: v.confidence,
|
|
185
|
+
ai_filled: v.value !== null && v.value !== undefined,
|
|
186
|
+
needs_review: v.needs_review,
|
|
187
|
+
}));
|
|
188
|
+
if (fieldRows.length > 0) {
|
|
189
|
+
await supabaseAdmin.from("mailbox_proposal_field_values").insert(fieldRows);
|
|
190
|
+
}
|
|
191
|
+
if (parsed.lines.length > 0) {
|
|
192
|
+
const linesToInsert = parsed.lines.map((l, idx) => ({
|
|
193
|
+
proposal_id: proposal.id,
|
|
194
|
+
product_id: l.product_id,
|
|
195
|
+
raw_product_text: l.raw_product_text,
|
|
196
|
+
quantity: l.quantity,
|
|
197
|
+
unit: l.unit,
|
|
198
|
+
match_confidence: l.match_confidence,
|
|
199
|
+
sort_order: idx,
|
|
200
|
+
}));
|
|
201
|
+
await supabaseAdmin.from("mailbox_proposal_lines").insert(linesToInsert);
|
|
202
|
+
}
|
|
203
|
+
await supabaseAdmin.from("mailbox_messages")
|
|
204
|
+
.update({ status: "parsed" }).eq("id", msg.id);
|
|
205
|
+
return { ok: true, proposalId: proposal.id };
|
|
206
|
+
}
|
|
207
|
+
catch (e) {
|
|
208
|
+
await supabaseAdmin.from("mailbox_messages")
|
|
209
|
+
.update({ status: "failed", error_message: e?.message ?? "Parsefout" }).eq("id", msg.id);
|
|
210
|
+
return { ok: false, error: e?.message ?? "Parsefout" };
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
export const listProposals = createServerFn({ method: "GET" })
|
|
214
|
+
.middleware([requireSupabaseAuth])
|
|
215
|
+
.inputValidator((input) => z.object({ status: z.enum(["pending", "approved", "rejected"]).optional() }).parse(input ?? {}))
|
|
216
|
+
.handler(async ({ data, context }) => {
|
|
217
|
+
let q = context.supabase
|
|
218
|
+
.from("mailbox_order_proposals")
|
|
219
|
+
.select("*, mailbox_messages(subject, from_email, from_name, body_text, received_at), mailbox_proposal_lines(*), mailbox_proposal_field_values(*)")
|
|
220
|
+
.order("created_at", { ascending: false })
|
|
221
|
+
.limit(100);
|
|
222
|
+
if (data.status)
|
|
223
|
+
q = q.eq("status", data.status);
|
|
224
|
+
const { data: rows, error } = await q;
|
|
225
|
+
if (error)
|
|
226
|
+
throw new Error(error.message);
|
|
227
|
+
return { items: rows ?? [] };
|
|
228
|
+
});
|
|
229
|
+
const LineEdit = z.object({
|
|
230
|
+
id: z.string().uuid().optional(),
|
|
231
|
+
product_id: z.string().uuid().nullable(),
|
|
232
|
+
raw_product_text: z.string().max(500),
|
|
233
|
+
quantity: z.number().nullable(),
|
|
234
|
+
unit: z.string().max(50).nullable(),
|
|
235
|
+
});
|
|
236
|
+
const FieldValueInput = z.object({
|
|
237
|
+
field_key: z.string().min(1).max(64),
|
|
238
|
+
value: z.any().nullable(),
|
|
239
|
+
});
|
|
240
|
+
export const approveProposal = createServerFn({ method: "POST" })
|
|
241
|
+
.middleware([requireSupabaseAuth])
|
|
242
|
+
.inputValidator((input) => z.object({
|
|
243
|
+
proposalId: z.string().uuid(),
|
|
244
|
+
customerId: z.string().uuid().nullable(),
|
|
245
|
+
customerName: z.string().min(1).max(255),
|
|
246
|
+
deliveryDate: z.string().min(1).max(50),
|
|
247
|
+
notes: z.string().max(2000).nullable(),
|
|
248
|
+
lines: z.array(LineEdit).min(1),
|
|
249
|
+
fieldValues: z.array(FieldValueInput).max(100).optional(),
|
|
250
|
+
}).parse(input))
|
|
251
|
+
.handler(async ({ data, context }) => {
|
|
252
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server");
|
|
253
|
+
// Bouw custom_fields map vanuit alle templatevelden
|
|
254
|
+
const customFields = {};
|
|
255
|
+
(data.fieldValues ?? []).forEach((fv) => {
|
|
256
|
+
customFields[fv.field_key] = fv.value;
|
|
257
|
+
});
|
|
258
|
+
const orderNumber = `MB-${Date.now()}`;
|
|
259
|
+
const { data: order, error: orderErr } = await supabaseAdmin
|
|
260
|
+
.from("orders").insert({
|
|
261
|
+
order_number: orderNumber,
|
|
262
|
+
customer_name: data.customerName,
|
|
263
|
+
customer_id: data.customerId,
|
|
264
|
+
order_date: new Date().toISOString().slice(0, 10),
|
|
265
|
+
delivery_date: data.deliveryDate,
|
|
266
|
+
status: "nieuw",
|
|
267
|
+
total: "0",
|
|
268
|
+
notes: data.notes,
|
|
269
|
+
custom_fields: { ...customFields, source: "mailbox" },
|
|
270
|
+
}).select("id").single();
|
|
271
|
+
if (orderErr)
|
|
272
|
+
throw new Error(orderErr.message);
|
|
273
|
+
const items = data.lines.map((l) => ({
|
|
274
|
+
order_id: order.id,
|
|
275
|
+
product_id: l.product_id,
|
|
276
|
+
product_name: l.raw_product_text || "Onbekend product",
|
|
277
|
+
quantity: Math.max(1, Math.round(l.quantity ?? 1)),
|
|
278
|
+
unit: l.unit ?? "stuks",
|
|
279
|
+
}));
|
|
280
|
+
const { error: itemsErr } = await supabaseAdmin.from("order_items").insert(items);
|
|
281
|
+
if (itemsErr)
|
|
282
|
+
throw new Error(itemsErr.message);
|
|
283
|
+
// Sla finale veldwaarden op (overschrijft AI-waarden)
|
|
284
|
+
if (data.fieldValues && data.fieldValues.length > 0) {
|
|
285
|
+
for (const fv of data.fieldValues) {
|
|
286
|
+
await supabaseAdmin.from("mailbox_proposal_field_values").upsert({
|
|
287
|
+
proposal_id: data.proposalId,
|
|
288
|
+
field_key: fv.field_key,
|
|
289
|
+
value: fv.value === undefined ? null : fv.value,
|
|
290
|
+
ai_filled: false,
|
|
291
|
+
needs_review: false,
|
|
292
|
+
}, { onConflict: "proposal_id,field_key" });
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
await supabaseAdmin.from("mailbox_order_proposals").update({
|
|
296
|
+
status: "approved",
|
|
297
|
+
reviewed_by: context.userId,
|
|
298
|
+
reviewed_at: new Date().toISOString(),
|
|
299
|
+
created_order_id: order.id,
|
|
300
|
+
}).eq("id", data.proposalId);
|
|
301
|
+
const { data: prop } = await supabaseAdmin
|
|
302
|
+
.from("mailbox_order_proposals").select("message_id").eq("id", data.proposalId).single();
|
|
303
|
+
if (prop?.message_id) {
|
|
304
|
+
await supabaseAdmin.from("mailbox_messages").update({ status: "converted" }).eq("id", prop.message_id);
|
|
305
|
+
}
|
|
306
|
+
return { ok: true, orderId: order.id };
|
|
307
|
+
});
|
|
308
|
+
export const rejectProposal = createServerFn({ method: "POST" })
|
|
309
|
+
.middleware([requireSupabaseAuth])
|
|
310
|
+
.inputValidator((input) => z.object({ proposalId: z.string().uuid(), reason: z.string().max(500).optional() }).parse(input))
|
|
311
|
+
.handler(async ({ data, context }) => {
|
|
312
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server");
|
|
313
|
+
await supabaseAdmin.from("mailbox_order_proposals").update({
|
|
314
|
+
status: "rejected",
|
|
315
|
+
reviewed_by: context.userId,
|
|
316
|
+
reviewed_at: new Date().toISOString(),
|
|
317
|
+
notes: data.reason ?? null,
|
|
318
|
+
}).eq("id", data.proposalId);
|
|
319
|
+
return { ok: true };
|
|
320
|
+
});
|
|
321
|
+
export const listCustomersForPicker = createServerFn({ method: "GET" })
|
|
322
|
+
.middleware([requireSupabaseAuth])
|
|
323
|
+
.handler(async ({ context }) => {
|
|
324
|
+
const { data } = await context.supabase
|
|
325
|
+
.from("customers").select("id, company_name, email").eq("is_active", true).order("company_name").range(0, 4999);
|
|
326
|
+
return { items: data ?? [] };
|
|
327
|
+
});
|
|
328
|
+
export const listProductsForPicker = createServerFn({ method: "GET" })
|
|
329
|
+
.middleware([requireSupabaseAuth])
|
|
330
|
+
.handler(async ({ context }) => {
|
|
331
|
+
const { data } = await context.supabase
|
|
332
|
+
.from("products").select("id, product, unit").order("product").range(0, 4999);
|
|
333
|
+
return { items: data ?? [] };
|
|
334
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,MAAM,CAAA;AAG5C,wBAAgB,EAAE,CAAC,GAAG,MAAM,EAAE,UAAU,EAAE,UAEzC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const loginSchema: z.ZodObject<{
|
|
3
|
+
email: z.ZodString;
|
|
4
|
+
password: z.ZodString;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
export declare const bugReportSchema: z.ZodObject<{
|
|
7
|
+
title: z.ZodString;
|
|
8
|
+
description: z.ZodString;
|
|
9
|
+
}, z.core.$strip>;
|
|
10
|
+
export declare const ALLOWED_FILE_TYPES: string[];
|
|
11
|
+
export declare function validateFileUpload(file: File): {
|
|
12
|
+
valid: boolean;
|
|
13
|
+
error?: string;
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=validationSchemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validationSchemas.d.ts","sourceRoot":"","sources":["../../src/lib/validationSchemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,WAAW;;;iBAGtB,CAAC;AAEH,eAAO,MAAM,eAAe;;;iBAG1B,CAAC;AAEH,eAAO,MAAM,kBAAkB,UAK9B,CAAC;AAIF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAQjF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const loginSchema = z.object({
|
|
3
|
+
email: z.string().email("Ongeldig e-mailadres"),
|
|
4
|
+
password: z.string().min(6, "Wachtwoord moet minimaal 6 tekens bevatten"),
|
|
5
|
+
});
|
|
6
|
+
export const bugReportSchema = z.object({
|
|
7
|
+
title: z.string().min(1, "Titel is verplicht").max(200),
|
|
8
|
+
description: z.string().min(1, "Beschrijving is verplicht").max(5000),
|
|
9
|
+
});
|
|
10
|
+
export const ALLOWED_FILE_TYPES = [
|
|
11
|
+
"image/png",
|
|
12
|
+
"image/jpeg",
|
|
13
|
+
"image/gif",
|
|
14
|
+
"application/pdf",
|
|
15
|
+
];
|
|
16
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
17
|
+
export function validateFileUpload(file) {
|
|
18
|
+
if (!ALLOWED_FILE_TYPES.includes(file.type)) {
|
|
19
|
+
return { valid: false, error: "Bestandstype niet toegestaan" };
|
|
20
|
+
}
|
|
21
|
+
if (file.size > MAX_FILE_SIZE) {
|
|
22
|
+
return { valid: false, error: "Bestand is te groot (max 10MB)" };
|
|
23
|
+
}
|
|
24
|
+
return { valid: true };
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@source "./";
|
package/package.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flowselections/mailbox-orders",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist-lib/index.js",
|
|
8
|
+
"types": "./dist-lib/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist-lib/index.d.ts",
|
|
12
|
+
"default": "./dist-lib/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./styles": "./dist-lib/styles.css"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist-lib",
|
|
18
|
+
"public"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"dev": "vite dev",
|
|
25
|
+
"build": "vite build",
|
|
26
|
+
"build:lib": "tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && node -e \"require('fs').writeFileSync('dist-lib/styles.css', '@source \\\"./\\\";\\n')\"",
|
|
27
|
+
"build:lib:watch": "tsc -p tsconfig.build.json --watch",
|
|
28
|
+
"build:dev": "vite build --mode development",
|
|
29
|
+
"preview": "vite preview",
|
|
30
|
+
"lint": "eslint ."
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@flowselections/core": "*",
|
|
34
|
+
"@tanstack/react-query": "^5.0.0",
|
|
35
|
+
"@tanstack/react-router": "^1.0.0",
|
|
36
|
+
"@tanstack/react-start": "^1.0.0",
|
|
37
|
+
"lucide-react": "^0.400.0",
|
|
38
|
+
"react": "^18 || ^19",
|
|
39
|
+
"react-dom": "^18 || ^19",
|
|
40
|
+
"sonner": "^2.0.0"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@supabase/supabase-js": "^2.108.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@cloudflare/vite-plugin": "^1.25.5",
|
|
47
|
+
"@eslint/js": "^9.32.0",
|
|
48
|
+
"@flowselections/core": "^1.0.11",
|
|
49
|
+
"@lovable.dev/vite-tanstack-config": "^1.2.0",
|
|
50
|
+
"@rsbuild/core": "^1.0.0",
|
|
51
|
+
"@tailwindcss/vite": "^4.2.1",
|
|
52
|
+
"@tanstack/react-query": "^5.83.0",
|
|
53
|
+
"@tanstack/react-router": "^1.168.0",
|
|
54
|
+
"@tanstack/react-start": "^1.167.14",
|
|
55
|
+
"@tanstack/router-plugin": "^1.167.10",
|
|
56
|
+
"@types/node": "^22.16.5",
|
|
57
|
+
"@types/react": "^19.2.0",
|
|
58
|
+
"@types/react-dom": "^19.2.0",
|
|
59
|
+
"@vitejs/plugin-react": "^5.0.4",
|
|
60
|
+
"eslint": "^9.32.0",
|
|
61
|
+
"eslint-plugin-react-hooks": "^5.2.0",
|
|
62
|
+
"eslint-plugin-react-refresh": "^0.4.20",
|
|
63
|
+
"globals": "^15.15.0",
|
|
64
|
+
"lucide-react": "^0.575.0",
|
|
65
|
+
"react": "^19.2.0",
|
|
66
|
+
"react-dom": "^19.2.0",
|
|
67
|
+
"sonner": "^2.0.7",
|
|
68
|
+
"tailwindcss": "^4.2.1",
|
|
69
|
+
"tsc-alias": "^1.8.10",
|
|
70
|
+
"tw-animate-css": "^1.3.4",
|
|
71
|
+
"typescript": "^5.8.3",
|
|
72
|
+
"typescript-eslint": "^8.56.1",
|
|
73
|
+
"vite": "^7.3.1",
|
|
74
|
+
"vite-tsconfig-paths": "^6.0.2"
|
|
75
|
+
},
|
|
76
|
+
"flowselections": {
|
|
77
|
+
"moduleId": "Mailbox",
|
|
78
|
+
"pages": [
|
|
79
|
+
{
|
|
80
|
+
"export": "MailboxInboxPage",
|
|
81
|
+
"route": "mail-box-voor-bestellingen-erp-kwekers/inbox",
|
|
82
|
+
"title": "Inbox"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"export": "MailboxProposalsPage",
|
|
86
|
+
"route": "mail-box-voor-bestellingen-erp-kwekers/voorstellen",
|
|
87
|
+
"title": "Voorstellen"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"export": "MailboxProcessedPage",
|
|
91
|
+
"route": "mail-box-voor-bestellingen-erp-kwekers/verwerkt",
|
|
92
|
+
"title": "Verwerkt"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Module assets
|
|
2
|
+
|
|
3
|
+
Raster-afbeeldingen (PNG, JPEG) die deze module nodig heeft, worden hier geplaatst.
|
|
4
|
+
|
|
5
|
+
De setup CLI kopieert de inhoud van `public/` naar:
|
|
6
|
+
`shell/public/flowselections-assets/<module-id>/`
|
|
7
|
+
|
|
8
|
+
In de module-code verwijs je naar de afbeelding via een pad-string (GEEN import):
|
|
9
|
+
```ts
|
|
10
|
+
const MODULE_LOGO = '/flowselections-assets/<module-id>/logo.png'
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Vervang `<module-id>` door de id die je in `src/index.ts` als moduleConfig-id hebt opgegeven.
|
|
14
|
+
|
|
15
|
+
Zie ook: AGENTS.md sectie "Afbeeldingen"
|