@fayz-ai/plugin-courses 0.10.50 → 0.10.51
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/CourseEditorPage-GEBTVW7G.js +939 -0
- package/dist/CourseEditorPage-GEBTVW7G.js.map +1 -0
- package/dist/CourseOrderDetailView-LWHDQNZ7.js +122 -0
- package/dist/CourseOrderDetailView-LWHDQNZ7.js.map +1 -0
- package/dist/CoursesListPage-WDG3YHF4.js +95 -0
- package/dist/CoursesListPage-WDG3YHF4.js.map +1 -0
- package/dist/CoursesSettings-RLLYRP2R.js +56 -0
- package/dist/CoursesSettings-RLLYRP2R.js.map +1 -0
- package/dist/FinancialPage-UXD25JYA.js +53 -0
- package/dist/FinancialPage-UXD25JYA.js.map +1 -0
- package/dist/ReportsPage-ZC3QUISB.js +50 -0
- package/dist/ReportsPage-ZC3QUISB.js.map +1 -0
- package/dist/SalesPage-VNYJXPTU.js +85 -0
- package/dist/SalesPage-VNYJXPTU.js.map +1 -0
- package/dist/SubscriptionsPage-IGV723DM.js +66 -0
- package/dist/SubscriptionsPage-IGV723DM.js.map +1 -0
- package/dist/chunk-4DMJIPDN.js +11 -0
- package/dist/chunk-4DMJIPDN.js.map +1 -0
- package/dist/chunk-A4BEN6VV.js +15 -0
- package/dist/chunk-A4BEN6VV.js.map +1 -0
- package/dist/chunk-U5LQDCGG.js +92 -0
- package/dist/chunk-U5LQDCGG.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -1478
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { PageContainer, StatGrid, StatCard, SimpleTable } from './chunk-U5LQDCGG.js';
|
|
2
|
+
import { formatDate, formatPercent } from './chunk-A4BEN6VV.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Badge } from '@fayz-ai/ui';
|
|
5
|
+
import { useTranslation, formatCurrency } from '@fayz-ai/core';
|
|
6
|
+
import { getCoursesProvider } from '@fayz-ai/courses';
|
|
7
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
8
|
+
|
|
9
|
+
function FinancialPage() {
|
|
10
|
+
const t = useTranslation();
|
|
11
|
+
const [summary, setSummary] = React.useState(null);
|
|
12
|
+
const [payouts, setPayouts] = React.useState(null);
|
|
13
|
+
React.useEffect(() => {
|
|
14
|
+
const p = getCoursesProvider();
|
|
15
|
+
void p.getFinancialSummary().then(setSummary);
|
|
16
|
+
void p.listPayouts().then(setPayouts);
|
|
17
|
+
}, []);
|
|
18
|
+
const currency = summary?.currency ?? "BRL";
|
|
19
|
+
const columns = [
|
|
20
|
+
{ key: "date", header: t("courses.financial.date") || "Date", render: (p) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: formatDate(p.createdAt) }) },
|
|
21
|
+
{ key: "amount", header: t("courses.financial.amount") || "Amount", align: "right", render: (p) => /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: formatCurrency(p.amount, p.currency) }) },
|
|
22
|
+
{ key: "status", header: t("courses.financial.status") || "Status", align: "right", render: (p) => /* @__PURE__ */ jsx(Badge, { variant: p.status === "paid" ? "success" : p.status === "pending" ? "secondary" : "destructive", children: t(`courses.payoutStatus.${p.status}`) || p.status }) }
|
|
23
|
+
];
|
|
24
|
+
return /* @__PURE__ */ jsxs(
|
|
25
|
+
PageContainer,
|
|
26
|
+
{
|
|
27
|
+
title: t("courses.financial.title") || "Financial settings",
|
|
28
|
+
subtitle: t("courses.financial.subtitle") || "Balance and payouts.",
|
|
29
|
+
children: [
|
|
30
|
+
/* @__PURE__ */ jsxs(StatGrid, { children: [
|
|
31
|
+
/* @__PURE__ */ jsx(StatCard, { label: t("courses.financial.available") || "Available balance", value: formatCurrency(summary?.availableBalance ?? 0, currency) }),
|
|
32
|
+
/* @__PURE__ */ jsx(StatCard, { label: t("courses.financial.pending") || "Pending balance", value: formatCurrency(summary?.pendingBalance ?? 0, currency) }),
|
|
33
|
+
/* @__PURE__ */ jsx(StatCard, { label: t("courses.financial.revenue") || "Total revenue", value: formatCurrency(summary?.totalRevenue ?? 0, currency) }),
|
|
34
|
+
/* @__PURE__ */ jsx(StatCard, { label: t("courses.financial.feeCollected") || "Platform fees", value: formatCurrency(summary?.platformFeeTotal ?? 0, currency), hint: summary ? formatPercent(summary.refundRate, 1) + " " + (t("courses.financial.refundRate") || "refund rate") : void 0 })
|
|
35
|
+
] }),
|
|
36
|
+
/* @__PURE__ */ jsx("h2", { className: "mb-3 text-sm font-semibold text-foreground", children: t("courses.financial.payouts") || "Payouts" }),
|
|
37
|
+
/* @__PURE__ */ jsx(
|
|
38
|
+
SimpleTable,
|
|
39
|
+
{
|
|
40
|
+
columns,
|
|
41
|
+
rows: payouts ?? [],
|
|
42
|
+
empty: t("courses.financial.noPayouts") || "No payouts yet."
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
FinancialPage.displayName = "FinancialPage";
|
|
50
|
+
|
|
51
|
+
export { FinancialPage };
|
|
52
|
+
//# sourceMappingURL=FinancialPage-UXD25JYA.js.map
|
|
53
|
+
//# sourceMappingURL=FinancialPage-UXD25JYA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/views/FinancialPage.tsx"],"names":[],"mappings":"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","file":"FinancialPage-UXD25JYA.js","sourcesContent":["import * as React from 'react'\nimport { Badge } from '@fayz-ai/ui'\nimport { useTranslation, formatCurrency } from '@fayz-ai/core'\nimport { getCoursesProvider, type Payout, type FinancialSummary } from '@fayz-ai/courses'\nimport { PageContainer, StatCard, StatGrid, SimpleTable, type SimpleColumn } from '../components/CommerceUI'\nimport { formatDate, formatPercent } from '../lib/format'\n\n// No plan gate here on purpose. The page is registered as a shell ROUTE with\n// `permission: read('courses.finance')`, so the route guard already resolves\n// role AND plan for it — and whether `courses.finance` is a paid feature is the\n// APP's call (its billing.ts), never the plugin's. Hardcoding a gate here would\n// bake one product's packaging into the SDK.\nexport function FinancialPage() {\n const t = useTranslation()\n const [summary, setSummary] = React.useState<FinancialSummary | null>(null)\n const [payouts, setPayouts] = React.useState<Payout[] | null>(null)\n\n React.useEffect(() => {\n const p = getCoursesProvider()\n void p.getFinancialSummary().then(setSummary)\n void p.listPayouts().then(setPayouts)\n }, [])\n\n const currency = summary?.currency ?? 'BRL'\n\n const columns: SimpleColumn<Payout>[] = [\n { key: 'date', header: t('courses.financial.date') || 'Date', render: (p) => <span className=\"text-muted-foreground\">{formatDate(p.createdAt)}</span> },\n { key: 'amount', header: t('courses.financial.amount') || 'Amount', align: 'right', render: (p) => <span className=\"font-medium text-foreground\">{formatCurrency(p.amount, p.currency)}</span> },\n { key: 'status', header: t('courses.financial.status') || 'Status', align: 'right', render: (p) => (\n <Badge variant={p.status === 'paid' ? 'success' : p.status === 'pending' ? 'secondary' : 'destructive'}>\n {t(`courses.payoutStatus.${p.status}`) || p.status}\n </Badge>\n ) },\n ]\n\n return (\n <PageContainer\n title={t('courses.financial.title') || 'Financial settings'}\n subtitle={t('courses.financial.subtitle') || 'Balance and payouts.'}\n >\n {/* The \"Connect Stripe\" action that used to sit here only ever raised a\n toast saying it would be wired later. It is gone rather than fixed:\n Cursos does not collect money — it produces the receivable and\n Financeiro receives it, which is the dependency the manifest now\n declares. One module charges, and it is not this one. */}\n <StatGrid>\n <StatCard label={t('courses.financial.available') || 'Available balance'} value={formatCurrency(summary?.availableBalance ?? 0, currency)} />\n <StatCard label={t('courses.financial.pending') || 'Pending balance'} value={formatCurrency(summary?.pendingBalance ?? 0, currency)} />\n <StatCard label={t('courses.financial.revenue') || 'Total revenue'} value={formatCurrency(summary?.totalRevenue ?? 0, currency)} />\n <StatCard label={t('courses.financial.feeCollected') || 'Platform fees'} value={formatCurrency(summary?.platformFeeTotal ?? 0, currency)} hint={summary ? formatPercent(summary.refundRate, 1) + ' ' + (t('courses.financial.refundRate') || 'refund rate') : undefined} />\n </StatGrid>\n\n <h2 className=\"mb-3 text-sm font-semibold text-foreground\">{t('courses.financial.payouts') || 'Payouts'}</h2>\n <SimpleTable\n columns={columns}\n rows={payouts ?? []}\n empty={t('courses.financial.noPayouts') || 'No payouts yet.'}\n />\n </PageContainer>\n )\n}\nFinancialPage.displayName = 'FinancialPage'\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { PageContainer, SimpleTable } from './chunk-U5LQDCGG.js';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { useTranslation, formatCurrency } from '@fayz-ai/core';
|
|
4
|
+
import { getCoursesProvider } from '@fayz-ai/courses';
|
|
5
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
function ReportsPage() {
|
|
8
|
+
const t = useTranslation();
|
|
9
|
+
const [rows, setRows] = React.useState(null);
|
|
10
|
+
React.useEffect(() => {
|
|
11
|
+
const p = getCoursesProvider();
|
|
12
|
+
void Promise.all([p.listCourses(), p.listOrders({ financialStatus: "paid" })]).then(([courses, orders]) => {
|
|
13
|
+
setRows(buildRevenueByCourse(courses, orders));
|
|
14
|
+
});
|
|
15
|
+
}, []);
|
|
16
|
+
const columns = [
|
|
17
|
+
{ key: "title", header: t("courses.reports.course") || "Course", render: (r) => /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: r.title }) },
|
|
18
|
+
{ key: "sales", header: t("courses.reports.sales") || "Sales", align: "right", render: (r) => /* @__PURE__ */ jsx("span", { children: r.sales }) },
|
|
19
|
+
{ key: "revenue", header: t("courses.reports.revenue") || "Revenue", align: "right", render: (r) => /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: formatCurrency(r.revenue, r.currency) }) }
|
|
20
|
+
];
|
|
21
|
+
return /* @__PURE__ */ jsxs(PageContainer, { title: t("courses.reports.title") || "Reports", subtitle: t("courses.reports.subtitle") || "Revenue by course.", children: [
|
|
22
|
+
/* @__PURE__ */ jsx("h2", { className: "mb-3 text-sm font-semibold text-foreground", children: t("courses.reports.byCourse") || "Revenue by course" }),
|
|
23
|
+
/* @__PURE__ */ jsx(
|
|
24
|
+
SimpleTable,
|
|
25
|
+
{
|
|
26
|
+
columns,
|
|
27
|
+
rows: rows ?? [],
|
|
28
|
+
empty: t("courses.reports.empty") || "No revenue yet."
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
] });
|
|
32
|
+
}
|
|
33
|
+
ReportsPage.displayName = "ReportsPage";
|
|
34
|
+
function buildRevenueByCourse(courses, orders) {
|
|
35
|
+
const byCourse = /* @__PURE__ */ new Map();
|
|
36
|
+
for (const o of orders) {
|
|
37
|
+
const agg = byCourse.get(o.courseId) ?? { sales: 0, revenue: 0, currency: o.currency };
|
|
38
|
+
agg.sales += 1;
|
|
39
|
+
agg.revenue = Math.round((agg.revenue + o.total) * 100) / 100;
|
|
40
|
+
byCourse.set(o.courseId, agg);
|
|
41
|
+
}
|
|
42
|
+
return courses.map((c) => {
|
|
43
|
+
const agg = byCourse.get(c.id);
|
|
44
|
+
return { courseId: c.id, title: c.title, sales: agg?.sales ?? 0, revenue: agg?.revenue ?? 0, currency: agg?.currency ?? c.currency };
|
|
45
|
+
}).sort((a, b) => b.revenue - a.revenue);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { ReportsPage };
|
|
49
|
+
//# sourceMappingURL=ReportsPage-ZC3QUISB.js.map
|
|
50
|
+
//# sourceMappingURL=ReportsPage-ZC3QUISB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/views/ReportsPage.tsx"],"names":[],"mappings":"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","file":"ReportsPage-ZC3QUISB.js","sourcesContent":["import * as React from 'react'\nimport { useTranslation, formatCurrency } from '@fayz-ai/core'\nimport { getCoursesProvider, type Order, type Course } from '@fayz-ai/courses'\nimport { PageContainer, SimpleTable, type SimpleColumn } from '../components/CommerceUI'\n\ninterface Row {\n courseId: string\n title: string\n sales: number\n revenue: number\n currency: string\n}\n\nexport function ReportsPage() {\n const t = useTranslation()\n const [rows, setRows] = React.useState<Row[] | null>(null)\n\n React.useEffect(() => {\n const p = getCoursesProvider()\n void Promise.all([p.listCourses(), p.listOrders({ financialStatus: 'paid' })]).then(([courses, orders]) => {\n setRows(buildRevenueByCourse(courses, orders))\n })\n }, [])\n\n const columns: SimpleColumn<Row>[] = [\n { key: 'title', header: t('courses.reports.course') || 'Course', render: (r) => <span className=\"font-medium text-foreground\">{r.title}</span> },\n { key: 'sales', header: t('courses.reports.sales') || 'Sales', align: 'right', render: (r) => <span>{r.sales}</span> },\n { key: 'revenue', header: t('courses.reports.revenue') || 'Revenue', align: 'right', render: (r) => <span className=\"font-medium text-foreground\">{formatCurrency(r.revenue, r.currency)}</span> },\n ]\n\n return (\n <PageContainer title={t('courses.reports.title') || 'Reports'} subtitle={t('courses.reports.subtitle') || 'Revenue by course.'}>\n <h2 className=\"mb-3 text-sm font-semibold text-foreground\">{t('courses.reports.byCourse') || 'Revenue by course'}</h2>\n <SimpleTable\n columns={columns}\n rows={rows ?? []}\n empty={t('courses.reports.empty') || 'No revenue yet.'}\n />\n </PageContainer>\n )\n}\nReportsPage.displayName = 'ReportsPage'\n\nfunction buildRevenueByCourse(courses: Course[], orders: Order[]): Row[] {\n const byCourse = new Map<string, { sales: number; revenue: number; currency: string }>()\n for (const o of orders) {\n const agg = byCourse.get(o.courseId) ?? { sales: 0, revenue: 0, currency: o.currency }\n agg.sales += 1\n agg.revenue = Math.round((agg.revenue + o.total) * 100) / 100\n byCourse.set(o.courseId, agg)\n }\n return courses\n .map((c) => {\n const agg = byCourse.get(c.id)\n return { courseId: c.id, title: c.title, sales: agg?.sales ?? 0, revenue: agg?.revenue ?? 0, currency: agg?.currency ?? c.currency }\n })\n .sort((a, b) => b.revenue - a.revenue)\n}\n"]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { navigateTo } from './chunk-4DMJIPDN.js';
|
|
2
|
+
import { formatDate } from './chunk-A4BEN6VV.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Badge } from '@fayz-ai/ui';
|
|
5
|
+
import { useTranslation, formatCurrency } from '@fayz-ai/core';
|
|
6
|
+
import { OrderListView } from '@fayz-ai/admin';
|
|
7
|
+
import { getCoursesProvider } from '@fayz-ai/courses';
|
|
8
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
9
|
+
|
|
10
|
+
function statusVariant(s) {
|
|
11
|
+
if (s === "paid") return "success";
|
|
12
|
+
if (s === "pending") return "secondary";
|
|
13
|
+
if (s === "refunded" || s === "chargeback") return "destructive";
|
|
14
|
+
return "outline";
|
|
15
|
+
}
|
|
16
|
+
var salesCache = null;
|
|
17
|
+
function SalesPage({ navTabs, activeNavTab, ordersRoute = "/courses/sales" } = {}) {
|
|
18
|
+
const t = useTranslation();
|
|
19
|
+
const [orders, setOrders] = React.useState(salesCache);
|
|
20
|
+
const tabs = navTabs?.();
|
|
21
|
+
React.useEffect(() => {
|
|
22
|
+
if (salesCache) return;
|
|
23
|
+
let alive = true;
|
|
24
|
+
const p = getCoursesProvider();
|
|
25
|
+
void Promise.all([
|
|
26
|
+
p.listCoreSales().catch(() => []),
|
|
27
|
+
p.listOrders().catch(() => [])
|
|
28
|
+
]).then(([core, store]) => {
|
|
29
|
+
if (!alive) return;
|
|
30
|
+
const byId = /* @__PURE__ */ new Map();
|
|
31
|
+
for (const o of store) byId.set(o.id, o);
|
|
32
|
+
for (const o of core) byId.set(o.id, o);
|
|
33
|
+
const list = [...byId.values()].sort((a, b) => a.createdAt < b.createdAt ? 1 : -1);
|
|
34
|
+
salesCache = list;
|
|
35
|
+
setOrders(list);
|
|
36
|
+
});
|
|
37
|
+
return () => {
|
|
38
|
+
alive = false;
|
|
39
|
+
};
|
|
40
|
+
}, []);
|
|
41
|
+
const paid = (orders ?? []).filter((o) => o.financialStatus === "paid");
|
|
42
|
+
const revenue = paid.reduce((s, o) => s + o.total, 0);
|
|
43
|
+
const currency = orders?.[0]?.currency ?? "BRL";
|
|
44
|
+
const columns = [
|
|
45
|
+
{ key: "date", header: t("courses.sales.date") || "Data", render: (o) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: formatDate(o.createdAt) }) },
|
|
46
|
+
{ key: "customer", header: t("courses.sales.customer") || "Cliente", render: (o) => /* @__PURE__ */ jsxs("div", { children: [
|
|
47
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-foreground", children: o.customerName ?? "\u2014" }),
|
|
48
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: o.customerEmail })
|
|
49
|
+
] }) },
|
|
50
|
+
{ key: "method", header: t("courses.sales.method") || "Forma", render: (o) => /* @__PURE__ */ jsxs("span", { className: "capitalize", children: [
|
|
51
|
+
o.paymentMethod ?? "\u2014",
|
|
52
|
+
o.installmentCount && o.installmentCount > 1 ? ` \xB7 ${o.installmentCount}x` : ""
|
|
53
|
+
] }) },
|
|
54
|
+
{ key: "status", header: t("courses.sales.status") || "Status", render: (o) => /* @__PURE__ */ jsx(Badge, { variant: statusVariant(o.financialStatus), children: t(`courses.financialStatus.${o.financialStatus}`) || o.financialStatus }) },
|
|
55
|
+
{ key: "fee", header: t("courses.sales.fee") || "Taxa", align: "right", render: (o) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: formatCurrency(o.platformFee, o.currency) }) },
|
|
56
|
+
{ key: "total", header: t("courses.sales.amount") || "Valor", align: "right", render: (o) => /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: formatCurrency(o.total, o.currency) }) }
|
|
57
|
+
];
|
|
58
|
+
return /* @__PURE__ */ jsx(
|
|
59
|
+
OrderListView,
|
|
60
|
+
{
|
|
61
|
+
title: t("courses.sales.title") || "Vendas",
|
|
62
|
+
subtitle: t("courses.sales.subtitle") || "Todas as transa\xE7\xF5es dos seus cursos.",
|
|
63
|
+
navTabs: tabs?.map((tb) => ({ value: tb.value, label: tb.label })),
|
|
64
|
+
activeNavTab,
|
|
65
|
+
onNavTabChange: (v) => {
|
|
66
|
+
const target = tabs?.find((tb) => tb.value === v);
|
|
67
|
+
if (target) navigateTo(target.route);
|
|
68
|
+
},
|
|
69
|
+
stats: [
|
|
70
|
+
{ label: t("courses.sales.count") || "Vendas", value: String(paid.length) },
|
|
71
|
+
{ label: t("courses.sales.revenue") || "Receita", value: formatCurrency(revenue, currency) }
|
|
72
|
+
],
|
|
73
|
+
columns,
|
|
74
|
+
rows: orders ?? [],
|
|
75
|
+
loading: orders === null,
|
|
76
|
+
empty: t("courses.sales.empty") || "Nenhuma venda ainda.",
|
|
77
|
+
onRowClick: (o) => navigateTo(`${ordersRoute}/${o.id}`)
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
SalesPage.displayName = "SalesPage";
|
|
82
|
+
|
|
83
|
+
export { SalesPage };
|
|
84
|
+
//# sourceMappingURL=SalesPage-VNYJXPTU.js.map
|
|
85
|
+
//# sourceMappingURL=SalesPage-VNYJXPTU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/views/SalesPage.tsx"],"names":[],"mappings":"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","file":"SalesPage-VNYJXPTU.js","sourcesContent":["import * as React from 'react'\nimport { Badge } from '@fayz-ai/ui'\nimport { useTranslation, formatCurrency } from '@fayz-ai/core'\nimport { OrderListView, type OrderListColumn } from '@fayz-ai/admin'\nimport { getCoursesProvider, type Order, type FinancialStatus } from '@fayz-ai/courses'\nimport { formatDate } from '../lib/format'\nimport { navigateTo } from '../lib/nav'\n\nfunction statusVariant(s: FinancialStatus): 'success' | 'secondary' | 'destructive' | 'outline' {\n if (s === 'paid') return 'success'\n if (s === 'pending') return 'secondary'\n if (s === 'refunded' || s === 'chargeback') return 'destructive'\n return 'outline'\n}\n\n// Cache (fora do componente) — trocar de aba remonta a página; sem cache refaz a\n// consulta toda vez. Cache-first evita o refetch/skeleton ao voltar pra aba.\nlet salesCache: Order[] | null = null\n\n/** Uma aba da faixa que Pedidos e Assinaturas dividem (ver `salesTabs`). */\nexport interface SalesTab {\n value: string\n label: string\n route: string\n}\n\nexport interface SalesTabProps {\n /** Construída por chamada: navegar depende do roteador. */\n navTabs?: () => SalesTab[]\n activeNavTab?: string\n /** Base do detalhe do pedido (`${ordersRoute}/:id`). */\n ordersRoute?: string\n}\n\nexport function SalesPage({ navTabs, activeNavTab, ordersRoute = '/courses/sales' }: SalesTabProps = {}) {\n const t = useTranslation()\n const [orders, setOrders] = React.useState<Order[] | null>(salesCache)\n const tabs = navTabs?.()\n\n React.useEffect(() => {\n if (salesCache) return // cache hit → sem request\n let alive = true\n const p = getCoursesProvider()\n // public.orders (via listCoreSales) é a fonte da verdade; listOrders é a\n // projeção nativa do courses. Dedupe por id: uma venda espelhada aparece 1x,\n // com a linha canônica (orders) ganhando.\n void Promise.all([\n p.listCoreSales().catch(() => [] as Order[]),\n p.listOrders().catch(() => [] as Order[]),\n ]).then(([core, store]) => {\n if (!alive) return\n const byId = new Map<string, Order>()\n for (const o of store) byId.set(o.id, o)\n for (const o of core) byId.set(o.id, o)\n const list = [...byId.values()].sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1))\n salesCache = list\n setOrders(list)\n })\n return () => { alive = false }\n }, [])\n\n const paid = (orders ?? []).filter((o) => o.financialStatus === 'paid')\n const revenue = paid.reduce((s, o) => s + o.total, 0)\n const currency = orders?.[0]?.currency ?? 'BRL'\n\n const columns: OrderListColumn<Order>[] = [\n { key: 'date', header: t('courses.sales.date') || 'Data', render: (o) => <span className=\"text-muted-foreground\">{formatDate(o.createdAt)}</span> },\n { key: 'customer', header: t('courses.sales.customer') || 'Cliente', render: (o) => (\n <div>\n <div className=\"font-medium text-foreground\">{o.customerName ?? '—'}</div>\n <div className=\"text-xs text-muted-foreground\">{o.customerEmail}</div>\n </div>\n ) },\n { key: 'method', header: t('courses.sales.method') || 'Forma', render: (o) => (\n <span className=\"capitalize\">\n {o.paymentMethod ?? '—'}{o.installmentCount && o.installmentCount > 1 ? ` · ${o.installmentCount}x` : ''}\n </span>\n ) },\n { key: 'status', header: t('courses.sales.status') || 'Status', render: (o) => (\n <Badge variant={statusVariant(o.financialStatus)}>{t(`courses.financialStatus.${o.financialStatus}`) || o.financialStatus}</Badge>\n ) },\n { key: 'fee', header: t('courses.sales.fee') || 'Taxa', align: 'right', render: (o) => <span className=\"text-muted-foreground\">{formatCurrency(o.platformFee, o.currency)}</span> },\n { key: 'total', header: t('courses.sales.amount') || 'Valor', align: 'right', render: (o) => <span className=\"font-medium text-foreground\">{formatCurrency(o.total, o.currency)}</span> },\n ]\n\n return (\n <OrderListView<Order>\n title={t('courses.sales.title') || 'Vendas'}\n subtitle={t('courses.sales.subtitle') || 'Todas as transações dos seus cursos.'}\n navTabs={tabs?.map((tb) => ({ value: tb.value, label: tb.label }))}\n activeNavTab={activeNavTab}\n onNavTabChange={(v) => {\n const target = tabs?.find((tb) => tb.value === v)\n if (target) navigateTo(target.route)\n }}\n stats={[\n { label: t('courses.sales.count') || 'Vendas', value: String(paid.length) },\n { label: t('courses.sales.revenue') || 'Receita', value: formatCurrency(revenue, currency) },\n ]}\n columns={columns}\n rows={orders ?? []}\n loading={orders === null}\n empty={t('courses.sales.empty') || 'Nenhuma venda ainda.'}\n onRowClick={(o) => navigateTo(`${ordersRoute}/${o.id}`)}\n />\n )\n}\nSalesPage.displayName = 'SalesPage'\n"]}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { navigateTo } from './chunk-4DMJIPDN.js';
|
|
2
|
+
import { formatDate } from './chunk-A4BEN6VV.js';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { Badge } from '@fayz-ai/ui';
|
|
5
|
+
import { useTranslation, formatCurrency } from '@fayz-ai/core';
|
|
6
|
+
import { OrderListView } from '@fayz-ai/admin';
|
|
7
|
+
import { getCoursesProvider } from '@fayz-ai/courses';
|
|
8
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
9
|
+
|
|
10
|
+
function statusVariant(s) {
|
|
11
|
+
if (s === "active") return "success";
|
|
12
|
+
if (s === "past_due") return "secondary";
|
|
13
|
+
return "destructive";
|
|
14
|
+
}
|
|
15
|
+
function SubscriptionsPage({ navTabs, activeNavTab } = {}) {
|
|
16
|
+
const t = useTranslation();
|
|
17
|
+
const [subs, setSubs] = React.useState(null);
|
|
18
|
+
const tabs = navTabs?.();
|
|
19
|
+
React.useEffect(() => {
|
|
20
|
+
void getCoursesProvider().listSubscriptions().then(setSubs);
|
|
21
|
+
}, []);
|
|
22
|
+
const active = (subs ?? []).filter((s) => s.status === "active");
|
|
23
|
+
const mrr = active.reduce((sum, s) => sum + (s.interval === "year" ? s.netValue / 12 : s.netValue), 0);
|
|
24
|
+
const currency = subs?.[0]?.currency ?? "BRL";
|
|
25
|
+
const columns = [
|
|
26
|
+
{ key: "start", header: t("courses.subs.start") || "Start date", render: (s) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: formatDate(s.startedAt) }) },
|
|
27
|
+
{ key: "customer", header: t("courses.subs.customer") || "Customer", render: (s) => /* @__PURE__ */ jsxs("div", { children: [
|
|
28
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-foreground", children: s.customerName ?? "\u2014" }),
|
|
29
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: s.customerEmail })
|
|
30
|
+
] }) },
|
|
31
|
+
{ key: "status", header: t("courses.subs.status") || "Status", render: (s) => /* @__PURE__ */ jsx(Badge, { variant: statusVariant(s.status), children: t(`courses.subStatus.${s.status}`) || s.status }) },
|
|
32
|
+
{ key: "value", header: t("courses.subs.value") || "Net value", align: "right", render: (s) => /* @__PURE__ */ jsxs("span", { className: "font-medium text-foreground", children: [
|
|
33
|
+
formatCurrency(s.netValue, s.currency),
|
|
34
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
35
|
+
" / ",
|
|
36
|
+
t(`courses.interval.${s.interval}`) || s.interval
|
|
37
|
+
] })
|
|
38
|
+
] }) }
|
|
39
|
+
];
|
|
40
|
+
return /* @__PURE__ */ jsx(
|
|
41
|
+
OrderListView,
|
|
42
|
+
{
|
|
43
|
+
title: t("courses.subs.title") || "Subscriptions",
|
|
44
|
+
subtitle: t("courses.subs.subtitle") || "Recurring plans and MRR.",
|
|
45
|
+
navTabs: tabs?.map((tb) => ({ value: tb.value, label: tb.label })),
|
|
46
|
+
activeNavTab,
|
|
47
|
+
onNavTabChange: (v) => {
|
|
48
|
+
const target = tabs?.find((tb) => tb.value === v);
|
|
49
|
+
if (target) navigateTo(target.route);
|
|
50
|
+
},
|
|
51
|
+
stats: [
|
|
52
|
+
{ label: t("courses.subs.active") || "Active subscriptions", value: String(active.length) },
|
|
53
|
+
{ label: t("courses.subs.mrr") || "Monthly recurring revenue", value: formatCurrency(mrr, currency) }
|
|
54
|
+
],
|
|
55
|
+
columns,
|
|
56
|
+
rows: subs ?? [],
|
|
57
|
+
loading: subs === null,
|
|
58
|
+
empty: t("courses.subs.empty") || "No subscriptions yet."
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
SubscriptionsPage.displayName = "SubscriptionsPage";
|
|
63
|
+
|
|
64
|
+
export { SubscriptionsPage };
|
|
65
|
+
//# sourceMappingURL=SubscriptionsPage-IGV723DM.js.map
|
|
66
|
+
//# sourceMappingURL=SubscriptionsPage-IGV723DM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/views/SubscriptionsPage.tsx"],"names":[],"mappings":"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","file":"SubscriptionsPage-IGV723DM.js","sourcesContent":["import * as React from 'react'\nimport { Badge } from '@fayz-ai/ui'\nimport { useTranslation, formatCurrency } from '@fayz-ai/core'\nimport { OrderListView, type OrderListColumn } from '@fayz-ai/admin'\nimport { getCoursesProvider, type Subscription, type SubscriptionStatus } from '@fayz-ai/courses'\nimport { formatDate } from '../lib/format'\nimport { navigateTo } from '../lib/nav'\nimport type { SalesTabProps } from './SalesPage'\n\n// Assinaturas é a OUTRA aba de Pedidos, então desenha a mesma lista: o mesmo\n// arquétipo (OrderListView), com a faixa de abas, a fileira de indicadores, a\n// busca dentro do cartão e o mesmo paginador. Ela usava um conjunto próprio\n// (PageContainer + StatGrid + SimpleTable) e as duas metades de uma tela só\n// liam como dois produtos — trocar de aba trocava a gramática da página.\n\nfunction statusVariant(s: SubscriptionStatus): 'success' | 'secondary' | 'destructive' {\n if (s === 'active') return 'success'\n if (s === 'past_due') return 'secondary'\n return 'destructive'\n}\n\nexport function SubscriptionsPage({ navTabs, activeNavTab }: SalesTabProps = {}) {\n const t = useTranslation()\n const [subs, setSubs] = React.useState<Subscription[] | null>(null)\n const tabs = navTabs?.()\n\n React.useEffect(() => {\n void getCoursesProvider().listSubscriptions().then(setSubs)\n }, [])\n\n const active = (subs ?? []).filter((s) => s.status === 'active')\n const mrr = active.reduce((sum, s) => sum + (s.interval === 'year' ? s.netValue / 12 : s.netValue), 0)\n const currency = subs?.[0]?.currency ?? 'BRL'\n\n const columns: OrderListColumn<Subscription>[] = [\n { key: 'start', header: t('courses.subs.start') || 'Start date', render: (s) => <span className=\"text-muted-foreground\">{formatDate(s.startedAt)}</span> },\n { key: 'customer', header: t('courses.subs.customer') || 'Customer', render: (s) => (\n <div>\n <div className=\"font-medium text-foreground\">{s.customerName ?? '—'}</div>\n <div className=\"text-xs text-muted-foreground\">{s.customerEmail}</div>\n </div>\n ) },\n { key: 'status', header: t('courses.subs.status') || 'Status', render: (s) => (\n <Badge variant={statusVariant(s.status)}>{t(`courses.subStatus.${s.status}`) || s.status}</Badge>\n ) },\n { key: 'value', header: t('courses.subs.value') || 'Net value', align: 'right', render: (s) => (\n <span className=\"font-medium text-foreground\">{formatCurrency(s.netValue, s.currency)}<span className=\"text-xs text-muted-foreground\"> / {t(`courses.interval.${s.interval}`) || s.interval}</span></span>\n ) },\n ]\n\n return (\n <OrderListView<Subscription>\n title={t('courses.subs.title') || 'Subscriptions'}\n subtitle={t('courses.subs.subtitle') || 'Recurring plans and MRR.'}\n navTabs={tabs?.map((tb) => ({ value: tb.value, label: tb.label }))}\n activeNavTab={activeNavTab}\n onNavTabChange={(v) => {\n const target = tabs?.find((tb) => tb.value === v)\n if (target) navigateTo(target.route)\n }}\n stats={[\n { label: t('courses.subs.active') || 'Active subscriptions', value: String(active.length) },\n { label: t('courses.subs.mrr') || 'Monthly recurring revenue', value: formatCurrency(mrr, currency) },\n ]}\n columns={columns}\n rows={subs ?? []}\n loading={subs === null}\n empty={t('courses.subs.empty') || 'No subscriptions yet.'}\n />\n )\n}\nSubscriptionsPage.displayName = 'SubscriptionsPage'\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { hashRouterAdapter } from '@fayz-ai/core';
|
|
2
|
+
|
|
3
|
+
// src/lib/nav.ts
|
|
4
|
+
var adapter = hashRouterAdapter();
|
|
5
|
+
function navigateTo(to) {
|
|
6
|
+
adapter.navigate(to);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export { navigateTo };
|
|
10
|
+
//# sourceMappingURL=chunk-4DMJIPDN.js.map
|
|
11
|
+
//# sourceMappingURL=chunk-4DMJIPDN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/nav.ts"],"names":[],"mappings":";;;AAIA,IAAM,UAAU,iBAAA,EAAkB;AAE3B,SAAS,WAAW,EAAA,EAAkB;AAC3C,EAAA,OAAA,CAAQ,SAAS,EAAE,CAAA;AACrB","file":"chunk-4DMJIPDN.js","sourcesContent":["import { hashRouterAdapter } from '@fayz-ai/core'\n\n// Shared hash-router adapter — the same singleton the admin shell drives, so\n// navigateTo here updates the shell's active route.\nconst adapter = hashRouterAdapter()\n\nexport function navigateTo(to: string): void {\n adapter.navigate(to)\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// src/lib/format.ts
|
|
2
|
+
function formatDate(iso) {
|
|
3
|
+
try {
|
|
4
|
+
return new Intl.DateTimeFormat("pt-BR", { day: "2-digit", month: "2-digit", year: "numeric" }).format(new Date(iso));
|
|
5
|
+
} catch {
|
|
6
|
+
return iso.slice(0, 10);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function formatPercent(fraction, digits = 0) {
|
|
10
|
+
return `${(fraction * 100).toFixed(digits)}%`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { formatDate, formatPercent };
|
|
14
|
+
//# sourceMappingURL=chunk-A4BEN6VV.js.map
|
|
15
|
+
//# sourceMappingURL=chunk-A4BEN6VV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/format.ts"],"names":[],"mappings":";AAGO,SAAS,WAAW,GAAA,EAAqB;AAC9C,EAAA,IAAI;AACF,IAAA,OAAO,IAAI,IAAA,CAAK,cAAA,CAAe,OAAA,EAAS,EAAE,KAAK,SAAA,EAAW,KAAA,EAAO,SAAA,EAAW,IAAA,EAAM,WAAW,CAAA,CAAE,OAAO,IAAI,IAAA,CAAK,GAAG,CAAC,CAAA;AAAA,EACrH,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,GAAA,CAAI,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AAAA,EACxB;AACF;AAEO,SAAS,aAAA,CAAc,QAAA,EAAkB,MAAA,GAAS,CAAA,EAAW;AAClE,EAAA,OAAO,CAAA,EAAA,CAAI,QAAA,GAAW,GAAA,EAAK,OAAA,CAAQ,MAAM,CAAC,CAAA,CAAA,CAAA;AAC5C","file":"chunk-A4BEN6VV.js","sourcesContent":["// Small display helpers shared across the courses admin pages. Kept local so the\n// plugin depends only on @fayz-ai/core + @fayz-ai/ui + @fayz-ai/courses.\n\nexport function formatDate(iso: string): string {\n try {\n return new Intl.DateTimeFormat('pt-BR', { day: '2-digit', month: '2-digit', year: 'numeric' }).format(new Date(iso))\n } catch {\n return iso.slice(0, 10)\n }\n}\n\nexport function formatPercent(fraction: number, digits = 0): string {\n return `${(fraction * 100).toFixed(digits)}%`\n}\n"]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { HeaderTabs, Card, DataTable } from '@fayz-ai/ui';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/components/CommerceUI.tsx
|
|
6
|
+
function PageContainer({ title, subtitle, actions, navTabs, activeNavTab, onNavTabChange, children }) {
|
|
7
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
8
|
+
navTabs && navTabs.length > 0 ? (
|
|
9
|
+
// Mesma linha de CrudListView/OrderListView: abas à esquerda, ações à
|
|
10
|
+
// direita. Com abas o <h1> sai: a aba ativa já nomeia a tela, e a barra
|
|
11
|
+
// de topo nomeia o grupo.
|
|
12
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex flex-wrap items-center justify-between gap-3", children: [
|
|
13
|
+
/* @__PURE__ */ jsx(
|
|
14
|
+
HeaderTabs,
|
|
15
|
+
{
|
|
16
|
+
tabs: navTabs,
|
|
17
|
+
value: activeNavTab ?? navTabs[0].value,
|
|
18
|
+
onChange: (v) => onNavTabChange?.(v)
|
|
19
|
+
}
|
|
20
|
+
),
|
|
21
|
+
actions
|
|
22
|
+
] })
|
|
23
|
+
) : /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-start justify-between gap-4", children: [
|
|
24
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
25
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-foreground", children: title }),
|
|
26
|
+
subtitle && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: subtitle })
|
|
27
|
+
] }),
|
|
28
|
+
actions
|
|
29
|
+
] }),
|
|
30
|
+
children
|
|
31
|
+
] });
|
|
32
|
+
}
|
|
33
|
+
function StatCard({ label, value, hint }) {
|
|
34
|
+
return /* @__PURE__ */ jsxs(Card, { className: "p-5", children: [
|
|
35
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: label }),
|
|
36
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-2xl font-bold text-foreground", children: value }),
|
|
37
|
+
hint && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: hint })
|
|
38
|
+
] });
|
|
39
|
+
}
|
|
40
|
+
function StatGrid({ children }) {
|
|
41
|
+
return /* @__PURE__ */ jsx("div", { className: "mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", children });
|
|
42
|
+
}
|
|
43
|
+
function EmptyState({ icon, title, description, action }) {
|
|
44
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center rounded-xl border border-dashed border-border bg-card/40 px-6 py-12 text-center", children: [
|
|
45
|
+
icon && /* @__PURE__ */ jsx("div", { className: "mb-3 flex h-11 w-11 items-center justify-center rounded-full bg-muted text-muted-foreground", children: icon }),
|
|
46
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: title }),
|
|
47
|
+
description && /* @__PURE__ */ jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: description }),
|
|
48
|
+
action && /* @__PURE__ */ jsx("div", { className: "mt-4", children: action })
|
|
49
|
+
] });
|
|
50
|
+
}
|
|
51
|
+
function InfoNote({ icon, children, className }) {
|
|
52
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2.5 rounded-lg border border-border bg-muted/40 px-3.5 py-3 text-sm text-muted-foreground " + (className ?? ""), children: [
|
|
53
|
+
icon && /* @__PURE__ */ jsx("span", { className: "mt-0.5 shrink-0 text-muted-foreground/80", children: icon }),
|
|
54
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 [&_strong]:font-medium [&_strong]:text-foreground", children })
|
|
55
|
+
] });
|
|
56
|
+
}
|
|
57
|
+
function SectionCard({ title, description, actions, children, className }) {
|
|
58
|
+
return /* @__PURE__ */ jsxs(Card, { className: "p-5 md:p-6 " + (className ?? ""), children: [
|
|
59
|
+
(title || actions) && /* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-start justify-between gap-4", children: [
|
|
60
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
61
|
+
title && /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-foreground", children: title }),
|
|
62
|
+
description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: description })
|
|
63
|
+
] }),
|
|
64
|
+
actions
|
|
65
|
+
] }),
|
|
66
|
+
children
|
|
67
|
+
] });
|
|
68
|
+
}
|
|
69
|
+
function SimpleTable({ columns, rows, empty, loading, onRowClick }) {
|
|
70
|
+
const cols = React.useMemo(
|
|
71
|
+
() => columns.map((c) => ({
|
|
72
|
+
id: c.key,
|
|
73
|
+
header: () => /* @__PURE__ */ jsx("div", { className: c.align === "right" ? "text-right" : "", children: c.header }),
|
|
74
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("div", { className: c.align === "right" ? "text-right" : "", children: c.render(row.original) })
|
|
75
|
+
})),
|
|
76
|
+
[columns]
|
|
77
|
+
);
|
|
78
|
+
return /* @__PURE__ */ jsx(
|
|
79
|
+
DataTable,
|
|
80
|
+
{
|
|
81
|
+
columns: cols,
|
|
82
|
+
data: rows,
|
|
83
|
+
emptyMessage: empty,
|
|
84
|
+
loading,
|
|
85
|
+
onRowClick: onRowClick ? (row) => onRowClick(row) : void 0
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { EmptyState, InfoNote, PageContainer, SectionCard, SimpleTable, StatCard, StatGrid };
|
|
91
|
+
//# sourceMappingURL=chunk-U5LQDCGG.js.map
|
|
92
|
+
//# sourceMappingURL=chunk-U5LQDCGG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/CommerceUI.tsx"],"names":[],"mappings":"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","file":"chunk-U5LQDCGG.js","sourcesContent":["import * as React from 'react'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Card, DataTable, HeaderTabs, type HeaderTab } from '@fayz-ai/ui'\n\n/**\n * Cabeçalho padrão das telas de comércio (Assinaturas, Relatórios, Financeiro\n * de cursos).\n *\n * Sem padding e sem max-width próprios: a shell já envolve toda página em\n * `p-3 md:p-6` (AdminShell). Reaplicar `p-6 md:p-8` aqui empurrava estas telas\n * 32px à direita do título na barra de topo e de todas as outras, e o\n * `mx-auto max-w-6xl` ainda as centralizava enquanto a barra ficava à esquerda.\n *\n * O <h1> continua: a barra de topo nomeia o item de menu e estas páginas não\n * têm outro cabeçalho. Vira duplicata quando o título repete o rótulo do menu —\n * é o que `subtitle` existe para desfazer.\n */\nexport function PageContainer({ title, subtitle, actions, navTabs, activeNavTab, onNavTabChange, children }: {\n title: string\n subtitle?: string\n actions?: React.ReactNode\n /** Listas irmãs com que esta divide uma faixa de abas (ver OrderListView). */\n navTabs?: HeaderTab[]\n activeNavTab?: string\n onNavTabChange?: (id: string) => void\n children: React.ReactNode\n}) {\n return (\n <div>\n {navTabs && navTabs.length > 0 ? (\n // Mesma linha de CrudListView/OrderListView: abas à esquerda, ações à\n // direita. Com abas o <h1> sai: a aba ativa já nomeia a tela, e a barra\n // de topo nomeia o grupo.\n <div className=\"mb-6 flex flex-wrap items-center justify-between gap-3\">\n <HeaderTabs\n tabs={navTabs}\n value={activeNavTab ?? navTabs[0]!.value}\n onChange={(v) => onNavTabChange?.(v)}\n />\n {actions}\n </div>\n ) : (\n <div className=\"mb-6 flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">{title}</h1>\n {subtitle && <p className=\"mt-1 text-sm text-muted-foreground\">{subtitle}</p>}\n </div>\n {actions}\n </div>\n )}\n {children}\n </div>\n )\n}\n\n/** A single KPI tile (revenue, sales, MRR…). */\nexport function StatCard({ label, value, hint }: { label: string; value: string; hint?: string }) {\n return (\n <Card className=\"p-5\">\n <p className=\"text-sm text-muted-foreground\">{label}</p>\n <p className=\"mt-1 text-2xl font-bold text-foreground\">{value}</p>\n {hint && <p className=\"mt-1 text-xs text-muted-foreground\">{hint}</p>}\n </Card>\n )\n}\n\nexport function StatGrid({ children }: { children: React.ReactNode }) {\n return <div className=\"mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">{children}</div>\n}\n\n/** Empty/loading state for the tables. */\nexport function TableEmpty({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"rounded-xl border border-dashed border-border p-12 text-center text-sm text-muted-foreground\">\n {children}\n </div>\n )\n}\n\n/**\n * Standard empty state — an icon chip, a title, an optional description and a\n * call-to-action. Use anywhere a list/section can be legitimately empty so the\n * screen never shows a bare dashed box.\n */\nexport function EmptyState({ icon, title, description, action }: {\n icon?: React.ReactNode\n title: string\n description?: string\n action?: React.ReactNode\n}) {\n return (\n <div className=\"flex flex-col items-center justify-center rounded-xl border border-dashed border-border bg-card/40 px-6 py-12 text-center\">\n {icon && (\n <div className=\"mb-3 flex h-11 w-11 items-center justify-center rounded-full bg-muted text-muted-foreground\">\n {icon}\n </div>\n )}\n <p className=\"text-sm font-medium text-foreground\">{title}</p>\n {description && <p className=\"mt-1 max-w-sm text-sm text-muted-foreground\">{description}</p>}\n {action && <div className=\"mt-4\">{action}</div>}\n </div>\n )\n}\n\n/**\n * A muted, low-emphasis informational callout. Use to set honest expectations\n * (e.g. \"this applies to a feature that isn't shipped yet\") without dressing it\n * up as an interactive control. Wrap key phrases in <strong> for emphasis.\n */\nexport function InfoNote({ icon, children, className }: {\n icon?: React.ReactNode\n children: React.ReactNode\n className?: string\n}) {\n return (\n <div className={'flex items-start gap-2.5 rounded-lg border border-border bg-muted/40 px-3.5 py-3 text-sm text-muted-foreground ' + (className ?? '')}>\n {icon && <span className=\"mt-0.5 shrink-0 text-muted-foreground/80\">{icon}</span>}\n <div className=\"min-w-0 [&_strong]:font-medium [&_strong]:text-foreground\">{children}</div>\n </div>\n )\n}\n\n/** A titled card section — the house pattern for grouping form fields. */\nexport function SectionCard({ title, description, actions, children, className }: {\n title?: string\n description?: string\n actions?: React.ReactNode\n children: React.ReactNode\n className?: string\n}) {\n return (\n <Card className={'p-5 md:p-6 ' + (className ?? '')}>\n {(title || actions) && (\n <div className=\"mb-4 flex items-start justify-between gap-4\">\n <div>\n {title && <h2 className=\"text-sm font-semibold text-foreground\">{title}</h2>}\n {description && <p className=\"mt-0.5 text-sm text-muted-foreground\">{description}</p>}\n </div>\n {actions}\n </div>\n )}\n {children}\n </Card>\n )\n}\n\nexport interface SimpleColumn<T> {\n key: string\n header: string\n render: (row: T) => React.ReactNode\n align?: 'left' | 'right'\n}\n\n/**\n * Thin adapter over the design-system DataTable (@fayz-ai/ui): the commerce\n * pages describe columns declaratively; this maps them to tanstack ColumnDef so\n * we reuse DataTable's sorting/skeleton/empty chrome instead of a bespoke table.\n */\nexport function SimpleTable<T>({ columns, rows, empty, loading, onRowClick }: {\n columns: SimpleColumn<T>[]\n rows: T[]\n empty: string\n loading?: boolean\n onRowClick?: (row: T) => void\n}) {\n const cols = React.useMemo<ColumnDef<T>[]>(\n () => columns.map((c) => ({\n id: c.key,\n header: () => <div className={c.align === 'right' ? 'text-right' : ''}>{c.header}</div>,\n cell: ({ row }) => <div className={c.align === 'right' ? 'text-right' : ''}>{c.render(row.original)}</div>,\n })),\n [columns],\n )\n return (\n <DataTable\n columns={cols}\n data={rows}\n emptyMessage={empty}\n loading={loading}\n onRowClick={onRowClick ? (row: T) => onRowClick(row) : undefined}\n />\n )\n}\n"]}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAoC5E,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2DAA2D;IAC3D,MAAM,CAAC,EAAE;QACP,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;KACjB,CAAA;IACD,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,EAAE,OAAO,CAAA;QACrB,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,aAAa,CAAC,EAAE,OAAO,CAAA;QACvB,SAAS,CAAC,EAAE,OAAO,CAAA;QACnB,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB,CAAA;IACD;;;;;OAKG;IACH,MAAM,CAAC,EAAE;QACP,mFAAmF;QACnF,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,qDAAqD;QACrD,aAAa,CAAC,EAAE,MAAM,CAAA;KACvB,CAAA;IACD;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AA6BD,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,cAAc,CAoKlF;AAOD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA"}
|