@fayz-ai/plugin-shop 0.9.0 → 0.9.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/README.md +4 -4
- package/dist/{ShopPage-JP27MZS2.js → ShopPage-PVHFKDF3.js} +7 -7
- package/dist/ShopPage-PVHFKDF3.js.map +1 -0
- package/dist/data/categoryDataProvider.d.ts +5 -0
- package/dist/data/categoryDataProvider.d.ts.map +1 -0
- package/dist/data/collectionDataProvider.d.ts +5 -0
- package/dist/data/collectionDataProvider.d.ts.map +1 -0
- package/dist/data/customerGroupDataProvider.d.ts +5 -0
- package/dist/data/customerGroupDataProvider.d.ts.map +1 -0
- package/dist/data/customerSegmentDataProvider.d.ts +16 -0
- package/dist/data/customerSegmentDataProvider.d.ts.map +1 -0
- package/dist/data/giftCardDataProvider.d.ts +5 -0
- package/dist/data/giftCardDataProvider.d.ts.map +1 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1088 -86
- package/dist/index.js.map +1 -1
- package/dist/views/ChannelsPage.d.ts +1 -1
- package/dist/views/ChannelsPage.d.ts.map +1 -1
- package/dist/views/ContentPage.d.ts +7 -0
- package/dist/views/ContentPage.d.ts.map +1 -0
- package/dist/views/EmailTemplatePage.d.ts +9 -0
- package/dist/views/EmailTemplatePage.d.ts.map +1 -0
- package/dist/views/EmailsPage.d.ts +7 -0
- package/dist/views/EmailsPage.d.ts.map +1 -0
- package/dist/views/ShopPage.d.ts.map +1 -0
- package/dist/views/categoryEntity.d.ts +4 -0
- package/dist/views/categoryEntity.d.ts.map +1 -0
- package/dist/views/collectionEntity.d.ts +4 -0
- package/dist/views/collectionEntity.d.ts.map +1 -0
- package/dist/views/customerGroupEntity.d.ts +4 -0
- package/dist/views/customerGroupEntity.d.ts.map +1 -0
- package/dist/views/customerSegmentEntity.d.ts +13 -0
- package/dist/views/customerSegmentEntity.d.ts.map +1 -0
- package/dist/views/giftCardEntity.d.ts +4 -0
- package/dist/views/giftCardEntity.d.ts.map +1 -0
- package/package.json +6 -8
- package/dist/ShopPage-JP27MZS2.js.map +0 -1
- package/dist/ShopPage.d.ts.map +0 -1
- package/src/ShopPage.tsx +0 -425
- package/src/components/ShopSettings.tsx +0 -108
- package/src/data/customerDataProvider.ts +0 -31
- package/src/data/discountDataProvider.ts +0 -42
- package/src/data/productDataProvider.ts +0 -54
- package/src/data/shared.ts +0 -42
- package/src/data/shippingZoneDataProvider.ts +0 -49
- package/src/index.ts +0 -270
- package/src/views/ChannelsPage.tsx +0 -274
- package/src/views/OrderDetailView.tsx +0 -319
- package/src/views/ProductImagesTab.tsx +0 -183
- package/src/views/customerEntity.tsx +0 -110
- package/src/views/customerLookup.ts +0 -31
- package/src/views/discountEntity.tsx +0 -144
- package/src/views/productEntity.tsx +0 -229
- package/src/views/shippingZoneEntity.tsx +0 -123
- /package/dist/{ShopPage.d.ts → views/ShopPage.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React3, { useState, useRef, useEffect } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { registerMessageDefaults } from '@fayz-ai/core';
|
|
3
|
+
import { setShopTenantResolver, buildShopMessageDefaults, buildShopDomainEvents, getShopProvider } from '@fayz-ai/shop';
|
|
4
|
+
import { useOrganizationStore, registerEntityPath, createCrudPage, adminNavigateTo, ClientOrdersTab, createClientOrdersProvider, SettingsGroup, SelectRow, ToggleRow } from '@fayz-ai/admin';
|
|
5
|
+
import { Package, Eye, FileEdit, PackageX, UsersRound, Users, UserX, Filter, ShoppingBag, Repeat, CircleDollarSign, Tag, TicketCheck, Layers, CreditCard, Upload, ImageOff, Film, FileText, Trash2, Link2, MapPin, Music, Camera, MessageCircle, Globe, ExternalLink, Check, X, Plus } from 'lucide-react';
|
|
4
6
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
5
|
-
import {
|
|
6
|
-
import { Button, Input } from '@fayz-ai/ui';
|
|
7
|
+
import { HeaderTabs, Button, Card, Hint, toast, Input } from '@fayz-ai/ui';
|
|
7
8
|
|
|
8
9
|
// src/index.ts
|
|
9
10
|
function ProductImagesTab({ item, getProvider }) {
|
|
@@ -786,6 +787,491 @@ function buildShopShippingZoneEntity(currency = "BRL") {
|
|
|
786
787
|
facets: [{ field: "active", allLabel: "Todas" }]
|
|
787
788
|
};
|
|
788
789
|
}
|
|
790
|
+
function buildShopCategoryEntity() {
|
|
791
|
+
const fields = [
|
|
792
|
+
{
|
|
793
|
+
key: "name",
|
|
794
|
+
label: "Nome",
|
|
795
|
+
type: "text",
|
|
796
|
+
required: true,
|
|
797
|
+
group: "basic",
|
|
798
|
+
placeholder: "Camisetas",
|
|
799
|
+
searchable: true,
|
|
800
|
+
showInTable: true,
|
|
801
|
+
sortable: true
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
key: "slug",
|
|
805
|
+
label: "Identificador",
|
|
806
|
+
type: "text",
|
|
807
|
+
group: "basic",
|
|
808
|
+
placeholder: "camisetas",
|
|
809
|
+
hint: "Segmento de URL na vitrine. Deixe vazio para gerar a partir do nome.",
|
|
810
|
+
searchable: true,
|
|
811
|
+
showInTable: true,
|
|
812
|
+
sortable: true,
|
|
813
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-muted-foreground", children: String(v ?? "") })
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
key: "parentId",
|
|
817
|
+
label: "Categoria pai",
|
|
818
|
+
type: "text",
|
|
819
|
+
group: "basic",
|
|
820
|
+
placeholder: "sem pai \u2014 n\xEDvel principal",
|
|
821
|
+
hint: "Deixe vazio para uma categoria de primeiro n\xEDvel.",
|
|
822
|
+
showInTable: false
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
key: "description",
|
|
826
|
+
label: "Descri\xE7\xE3o",
|
|
827
|
+
type: "textarea",
|
|
828
|
+
group: "basic",
|
|
829
|
+
span: 2,
|
|
830
|
+
placeholder: "Descri\xE7\xE3o opcional",
|
|
831
|
+
showInTable: false
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
key: "sortOrder",
|
|
835
|
+
label: "Ordem",
|
|
836
|
+
type: "number",
|
|
837
|
+
group: "basic",
|
|
838
|
+
min: 0,
|
|
839
|
+
hint: "Menor aparece primeiro na vitrine.",
|
|
840
|
+
showInTable: true,
|
|
841
|
+
sortable: true,
|
|
842
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: Number(v ?? 0) })
|
|
843
|
+
}
|
|
844
|
+
];
|
|
845
|
+
return {
|
|
846
|
+
name: "Categoria",
|
|
847
|
+
namePlural: "Categorias",
|
|
848
|
+
icon: "FolderTree",
|
|
849
|
+
displayField: "name",
|
|
850
|
+
defaultSort: "sortOrder",
|
|
851
|
+
fields,
|
|
852
|
+
fieldGroups: [
|
|
853
|
+
{ id: "basic", label: "Categoria", columns: 2 }
|
|
854
|
+
]
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
function buildShopCollectionEntity() {
|
|
858
|
+
const fields = [
|
|
859
|
+
{
|
|
860
|
+
key: "title",
|
|
861
|
+
label: "T\xEDtulo",
|
|
862
|
+
type: "text",
|
|
863
|
+
required: true,
|
|
864
|
+
group: "basic",
|
|
865
|
+
placeholder: "Ver\xE3o 2026",
|
|
866
|
+
searchable: true,
|
|
867
|
+
showInTable: true,
|
|
868
|
+
sortable: true
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
key: "handle",
|
|
872
|
+
label: "Identificador",
|
|
873
|
+
type: "text",
|
|
874
|
+
group: "basic",
|
|
875
|
+
placeholder: "verao-2026",
|
|
876
|
+
hint: "Segmento de URL na vitrine. Deixe vazio para gerar a partir do t\xEDtulo.",
|
|
877
|
+
searchable: true,
|
|
878
|
+
showInTable: true,
|
|
879
|
+
sortable: true,
|
|
880
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-muted-foreground", children: String(v ?? "") })
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
key: "description",
|
|
884
|
+
label: "Descri\xE7\xE3o",
|
|
885
|
+
type: "textarea",
|
|
886
|
+
group: "basic",
|
|
887
|
+
span: 2,
|
|
888
|
+
placeholder: "Como esta cole\xE7\xE3o \xE9 apresentada na loja",
|
|
889
|
+
showInTable: false
|
|
890
|
+
},
|
|
891
|
+
{
|
|
892
|
+
key: "productIds",
|
|
893
|
+
label: "Produtos",
|
|
894
|
+
type: "text",
|
|
895
|
+
showInForm: false,
|
|
896
|
+
showInTable: true,
|
|
897
|
+
sortable: false,
|
|
898
|
+
renderCell: (v) => {
|
|
899
|
+
const count = Array.isArray(v) ? v.length : 0;
|
|
900
|
+
return /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: count === 1 ? "1 produto" : `${count} produtos` });
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
key: "published",
|
|
905
|
+
label: "Publicada",
|
|
906
|
+
type: "boolean",
|
|
907
|
+
group: "basic",
|
|
908
|
+
hint: "Cole\xE7\xE3o despublicada some da vitrine, mas continua aqui.",
|
|
909
|
+
// Rotula os dois estados para as abas do cabeçalho: "true"/"false" cru
|
|
910
|
+
// seria o que o lojista leria no topo da própria lista.
|
|
911
|
+
options: [
|
|
912
|
+
{ value: "true", label: "Publicadas" },
|
|
913
|
+
{ value: "false", label: "Rascunhos" }
|
|
914
|
+
],
|
|
915
|
+
showInTable: true,
|
|
916
|
+
sortable: true,
|
|
917
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${v ? "bg-emerald-100 text-emerald-800" : "bg-gray-100 text-gray-700"}`, children: v ? "Publicada" : "Rascunho" })
|
|
918
|
+
},
|
|
919
|
+
{
|
|
920
|
+
key: "sortOrder",
|
|
921
|
+
label: "Ordem",
|
|
922
|
+
type: "number",
|
|
923
|
+
group: "basic",
|
|
924
|
+
min: 0,
|
|
925
|
+
hint: "Menor aparece primeiro.",
|
|
926
|
+
showInTable: false
|
|
927
|
+
},
|
|
928
|
+
// — SEO —
|
|
929
|
+
{
|
|
930
|
+
key: "seoTitle",
|
|
931
|
+
label: "T\xEDtulo da p\xE1gina",
|
|
932
|
+
type: "text",
|
|
933
|
+
group: "seo",
|
|
934
|
+
span: 2,
|
|
935
|
+
placeholder: "T\xEDtulo exibido nos resultados de busca",
|
|
936
|
+
hint: "Vazio usa o t\xEDtulo da cole\xE7\xE3o.",
|
|
937
|
+
showInTable: false
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
key: "seoDescription",
|
|
941
|
+
label: "Meta descri\xE7\xE3o",
|
|
942
|
+
type: "textarea",
|
|
943
|
+
group: "seo",
|
|
944
|
+
span: 2,
|
|
945
|
+
placeholder: "Descreva esta cole\xE7\xE3o para os buscadores",
|
|
946
|
+
showInTable: false
|
|
947
|
+
}
|
|
948
|
+
];
|
|
949
|
+
return {
|
|
950
|
+
name: "Cole\xE7\xE3o",
|
|
951
|
+
namePlural: "Cole\xE7\xF5es",
|
|
952
|
+
icon: "Layers",
|
|
953
|
+
displayField: "title",
|
|
954
|
+
defaultSort: "sortOrder",
|
|
955
|
+
fields,
|
|
956
|
+
fieldGroups: [
|
|
957
|
+
{ id: "basic", label: "Cole\xE7\xE3o", columns: 2 },
|
|
958
|
+
{ id: "seo", label: "Busca (SEO)", description: "Como esta cole\xE7\xE3o aparece nos resultados de busca.", columns: 2 }
|
|
959
|
+
]
|
|
960
|
+
// Sem facet de "publicada": como booleano ele vira uma tira de abas
|
|
961
|
+
// "Todas · Sim · Não", que não diz de QUE é o sim — e uma loja com dez
|
|
962
|
+
// coleções não precisa filtrar por isso; a coluna já mostra o estado.
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
var STATUS_TONE3 = {
|
|
966
|
+
active: "bg-emerald-100 text-emerald-800",
|
|
967
|
+
redeemed: "bg-gray-100 text-gray-700",
|
|
968
|
+
expired: "bg-orange-100 text-orange-700",
|
|
969
|
+
disabled: "bg-red-100 text-red-700"
|
|
970
|
+
};
|
|
971
|
+
var STATUS_LABEL2 = {
|
|
972
|
+
active: "Ativo",
|
|
973
|
+
redeemed: "Resgatado",
|
|
974
|
+
expired: "Expirado",
|
|
975
|
+
disabled: "Desativado"
|
|
976
|
+
};
|
|
977
|
+
function buildShopGiftCardEntity(currencyCode = "BRL") {
|
|
978
|
+
const money3 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(Number(v) || 0);
|
|
979
|
+
const fields = [
|
|
980
|
+
{
|
|
981
|
+
key: "code",
|
|
982
|
+
label: "C\xF3digo",
|
|
983
|
+
type: "text",
|
|
984
|
+
group: "card",
|
|
985
|
+
span: 2,
|
|
986
|
+
placeholder: "gerado automaticamente",
|
|
987
|
+
hint: "O que o cliente digita no checkout. Deixe vazio para gerar um c\xF3digo.",
|
|
988
|
+
searchable: true,
|
|
989
|
+
showInTable: true,
|
|
990
|
+
sortable: true,
|
|
991
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "font-mono text-xs font-medium", children: String(v ?? "") })
|
|
992
|
+
},
|
|
993
|
+
{
|
|
994
|
+
key: "initialBalance",
|
|
995
|
+
label: "Valor",
|
|
996
|
+
type: "currency",
|
|
997
|
+
required: true,
|
|
998
|
+
group: "card",
|
|
999
|
+
min: 0,
|
|
1000
|
+
currency: currencyCode,
|
|
1001
|
+
currencySymbol: "R$",
|
|
1002
|
+
currencyLocale: "pt-BR",
|
|
1003
|
+
hint: "Valor de face do cart\xE3o. N\xE3o muda depois de emitido.",
|
|
1004
|
+
showInTable: true,
|
|
1005
|
+
sortable: true,
|
|
1006
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: money3(v) })
|
|
1007
|
+
},
|
|
1008
|
+
{
|
|
1009
|
+
key: "balance",
|
|
1010
|
+
label: "Saldo",
|
|
1011
|
+
type: "currency",
|
|
1012
|
+
group: "card",
|
|
1013
|
+
min: 0,
|
|
1014
|
+
currency: currencyCode,
|
|
1015
|
+
currencySymbol: "R$",
|
|
1016
|
+
currencyLocale: "pt-BR",
|
|
1017
|
+
hint: "O que ainda pode ser gasto. Cai a cada resgate.",
|
|
1018
|
+
showInTable: true,
|
|
1019
|
+
sortable: true,
|
|
1020
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children: money3(v) })
|
|
1021
|
+
},
|
|
1022
|
+
{
|
|
1023
|
+
key: "status",
|
|
1024
|
+
label: "Status",
|
|
1025
|
+
type: "segmented",
|
|
1026
|
+
group: "card",
|
|
1027
|
+
span: 2,
|
|
1028
|
+
options: [
|
|
1029
|
+
{ label: "Ativo", value: "active", description: "Aceito no checkout." },
|
|
1030
|
+
{ label: "Desativado", value: "disabled", description: "Suspenso sem apagar o saldo." }
|
|
1031
|
+
],
|
|
1032
|
+
showInTable: true,
|
|
1033
|
+
sortable: false,
|
|
1034
|
+
renderCell: (v) => {
|
|
1035
|
+
const s = String(v ?? "active");
|
|
1036
|
+
return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${STATUS_TONE3[s] ?? STATUS_TONE3.active}`, children: STATUS_LABEL2[s] ?? s });
|
|
1037
|
+
}
|
|
1038
|
+
},
|
|
1039
|
+
// — Destinatário —
|
|
1040
|
+
{
|
|
1041
|
+
key: "customerEmail",
|
|
1042
|
+
label: "E-mail do destinat\xE1rio",
|
|
1043
|
+
type: "email",
|
|
1044
|
+
group: "recipient",
|
|
1045
|
+
placeholder: "opcional",
|
|
1046
|
+
searchable: true,
|
|
1047
|
+
showInTable: true,
|
|
1048
|
+
sortable: false,
|
|
1049
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: String(v ?? "\u2014") })
|
|
1050
|
+
},
|
|
1051
|
+
{
|
|
1052
|
+
key: "expiresAt",
|
|
1053
|
+
label: "Validade",
|
|
1054
|
+
type: "date",
|
|
1055
|
+
group: "recipient",
|
|
1056
|
+
hint: "Vazio = n\xE3o expira.",
|
|
1057
|
+
showInTable: false
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
key: "note",
|
|
1061
|
+
label: "Observa\xE7\xE3o",
|
|
1062
|
+
type: "textarea",
|
|
1063
|
+
group: "recipient",
|
|
1064
|
+
span: 2,
|
|
1065
|
+
placeholder: "S\xF3 o admin v\xEA.",
|
|
1066
|
+
showInTable: false
|
|
1067
|
+
}
|
|
1068
|
+
];
|
|
1069
|
+
return {
|
|
1070
|
+
name: "Vale-presente",
|
|
1071
|
+
namePlural: "Gift Cards",
|
|
1072
|
+
icon: "CreditCard",
|
|
1073
|
+
displayField: "code",
|
|
1074
|
+
defaultSort: "createdAt",
|
|
1075
|
+
defaultSortDir: "desc",
|
|
1076
|
+
fields,
|
|
1077
|
+
fieldGroups: [
|
|
1078
|
+
{ id: "card", label: "Cart\xE3o", columns: 2 },
|
|
1079
|
+
{ id: "recipient", label: "Destinat\xE1rio", columns: 2 }
|
|
1080
|
+
],
|
|
1081
|
+
facets: [{ field: "status", allLabel: "Todos", display: "tabs" }]
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
var SWATCH = {
|
|
1085
|
+
slate: "bg-slate-200 text-slate-800",
|
|
1086
|
+
emerald: "bg-emerald-100 text-emerald-800",
|
|
1087
|
+
amber: "bg-amber-100 text-amber-800",
|
|
1088
|
+
rose: "bg-rose-100 text-rose-800",
|
|
1089
|
+
violet: "bg-violet-100 text-violet-800"
|
|
1090
|
+
};
|
|
1091
|
+
function buildShopCustomerGroupEntity() {
|
|
1092
|
+
const fields = [
|
|
1093
|
+
{
|
|
1094
|
+
key: "name",
|
|
1095
|
+
label: "Nome",
|
|
1096
|
+
type: "text",
|
|
1097
|
+
required: true,
|
|
1098
|
+
group: "basic",
|
|
1099
|
+
placeholder: "Atacado",
|
|
1100
|
+
searchable: true,
|
|
1101
|
+
showInTable: true,
|
|
1102
|
+
sortable: true,
|
|
1103
|
+
renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${SWATCH[row.color] ?? SWATCH.slate}`, children: row.name })
|
|
1104
|
+
},
|
|
1105
|
+
{
|
|
1106
|
+
key: "color",
|
|
1107
|
+
label: "Cor",
|
|
1108
|
+
type: "select",
|
|
1109
|
+
group: "basic",
|
|
1110
|
+
options: [
|
|
1111
|
+
{ value: "slate", label: "Neutro" },
|
|
1112
|
+
{ value: "emerald", label: "Verde" },
|
|
1113
|
+
{ value: "amber", label: "\xC2mbar" },
|
|
1114
|
+
{ value: "rose", label: "Rosa" },
|
|
1115
|
+
{ value: "violet", label: "Violeta" }
|
|
1116
|
+
],
|
|
1117
|
+
hint: "S\xF3 para reconhecer o grupo de relance na lista.",
|
|
1118
|
+
showInTable: false
|
|
1119
|
+
},
|
|
1120
|
+
{
|
|
1121
|
+
key: "description",
|
|
1122
|
+
label: "Descri\xE7\xE3o",
|
|
1123
|
+
type: "textarea",
|
|
1124
|
+
group: "basic",
|
|
1125
|
+
span: 2,
|
|
1126
|
+
placeholder: "Para que serve este grupo",
|
|
1127
|
+
searchable: true,
|
|
1128
|
+
showInTable: true,
|
|
1129
|
+
sortable: false,
|
|
1130
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: String(v ?? "\u2014") })
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
key: "customerIds",
|
|
1134
|
+
label: "Clientes",
|
|
1135
|
+
type: "text",
|
|
1136
|
+
showInForm: false,
|
|
1137
|
+
showInTable: true,
|
|
1138
|
+
sortable: false,
|
|
1139
|
+
renderCell: (v) => {
|
|
1140
|
+
const count = Array.isArray(v) ? v.length : 0;
|
|
1141
|
+
return /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: count === 1 ? "1 cliente" : `${count} clientes` });
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
];
|
|
1145
|
+
return {
|
|
1146
|
+
name: "Grupo",
|
|
1147
|
+
namePlural: "Grupos",
|
|
1148
|
+
icon: "UsersRound",
|
|
1149
|
+
displayField: "name",
|
|
1150
|
+
defaultSort: "name",
|
|
1151
|
+
fields,
|
|
1152
|
+
fieldGroups: [{ id: "basic", label: "Grupo", columns: 2 }]
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
function describeSegment(s, money3) {
|
|
1156
|
+
const parts = [];
|
|
1157
|
+
if (s.minOrders != null && s.maxOrders != null) parts.push(`${s.minOrders}\u2013${s.maxOrders} pedidos`);
|
|
1158
|
+
else if (s.minOrders != null) parts.push(`${s.minOrders}+ pedidos`);
|
|
1159
|
+
else if (s.maxOrders != null) parts.push(`at\xE9 ${s.maxOrders} pedidos`);
|
|
1160
|
+
if (s.minSpent != null && s.maxSpent != null) parts.push(`gastou ${money3(s.minSpent)}\u2013${money3(s.maxSpent)}`);
|
|
1161
|
+
else if (s.minSpent != null) parts.push(`gastou ${money3(s.minSpent)}+`);
|
|
1162
|
+
else if (s.maxSpent != null) parts.push(`gastou at\xE9 ${money3(s.maxSpent)}`);
|
|
1163
|
+
if (s.joinedAfter) parts.push(`entrou ap\xF3s ${new Date(s.joinedAfter).toLocaleDateString("pt-BR")}`);
|
|
1164
|
+
if (s.joinedBefore) parts.push(`entrou antes de ${new Date(s.joinedBefore).toLocaleDateString("pt-BR")}`);
|
|
1165
|
+
return parts.length ? parts.join(" \xB7 ") : "Todos os clientes";
|
|
1166
|
+
}
|
|
1167
|
+
function buildShopCustomerSegmentEntity(currencyCode = "BRL") {
|
|
1168
|
+
const money3 = (v) => new Intl.NumberFormat("pt-BR", { style: "currency", currency: currencyCode }).format(v);
|
|
1169
|
+
const fields = [
|
|
1170
|
+
{
|
|
1171
|
+
key: "name",
|
|
1172
|
+
label: "Nome",
|
|
1173
|
+
type: "text",
|
|
1174
|
+
required: true,
|
|
1175
|
+
group: "basic",
|
|
1176
|
+
span: 2,
|
|
1177
|
+
placeholder: "Clientes fi\xE9is",
|
|
1178
|
+
searchable: true,
|
|
1179
|
+
showInTable: true,
|
|
1180
|
+
sortable: true
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
key: "description",
|
|
1184
|
+
label: "Descri\xE7\xE3o",
|
|
1185
|
+
type: "textarea",
|
|
1186
|
+
group: "basic",
|
|
1187
|
+
span: 2,
|
|
1188
|
+
placeholder: "Para que serve este segmento",
|
|
1189
|
+
searchable: true,
|
|
1190
|
+
showInTable: false
|
|
1191
|
+
},
|
|
1192
|
+
// — Regra —
|
|
1193
|
+
{
|
|
1194
|
+
key: "minOrders",
|
|
1195
|
+
label: "M\xEDnimo de pedidos",
|
|
1196
|
+
type: "number",
|
|
1197
|
+
group: "rule",
|
|
1198
|
+
min: 0,
|
|
1199
|
+
placeholder: "sem m\xEDnimo",
|
|
1200
|
+
showInTable: false
|
|
1201
|
+
},
|
|
1202
|
+
{
|
|
1203
|
+
key: "maxOrders",
|
|
1204
|
+
label: "M\xE1ximo de pedidos",
|
|
1205
|
+
type: "number",
|
|
1206
|
+
group: "rule",
|
|
1207
|
+
min: 0,
|
|
1208
|
+
placeholder: "sem m\xE1ximo",
|
|
1209
|
+
showInTable: false
|
|
1210
|
+
},
|
|
1211
|
+
{
|
|
1212
|
+
key: "minSpent",
|
|
1213
|
+
label: "Gasto m\xEDnimo",
|
|
1214
|
+
type: "currency",
|
|
1215
|
+
group: "rule",
|
|
1216
|
+
min: 0,
|
|
1217
|
+
currency: currencyCode,
|
|
1218
|
+
currencySymbol: "R$",
|
|
1219
|
+
currencyLocale: "pt-BR",
|
|
1220
|
+
placeholder: "sem m\xEDnimo",
|
|
1221
|
+
showInTable: false
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
key: "maxSpent",
|
|
1225
|
+
label: "Gasto m\xE1ximo",
|
|
1226
|
+
type: "currency",
|
|
1227
|
+
group: "rule",
|
|
1228
|
+
min: 0,
|
|
1229
|
+
currency: currencyCode,
|
|
1230
|
+
currencySymbol: "R$",
|
|
1231
|
+
currencyLocale: "pt-BR",
|
|
1232
|
+
placeholder: "sem m\xE1ximo",
|
|
1233
|
+
showInTable: false
|
|
1234
|
+
},
|
|
1235
|
+
{ key: "joinedAfter", label: "Entrou depois de", type: "date", group: "rule", showInTable: false },
|
|
1236
|
+
{ key: "joinedBefore", label: "Entrou antes de", type: "date", group: "rule", showInTable: false },
|
|
1237
|
+
// Coluna derivada: a regra escrita por extenso.
|
|
1238
|
+
{
|
|
1239
|
+
key: "minSpent_rule",
|
|
1240
|
+
label: "Regra",
|
|
1241
|
+
type: "text",
|
|
1242
|
+
showInForm: false,
|
|
1243
|
+
showInTable: true,
|
|
1244
|
+
sortable: false,
|
|
1245
|
+
renderCell: (_v, row) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: describeSegment(row, money3) })
|
|
1246
|
+
},
|
|
1247
|
+
{
|
|
1248
|
+
key: "matchCount",
|
|
1249
|
+
label: "Clientes",
|
|
1250
|
+
type: "number",
|
|
1251
|
+
showInForm: false,
|
|
1252
|
+
showInTable: true,
|
|
1253
|
+
sortable: true,
|
|
1254
|
+
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children: v == null ? "\u2014" : Number(v) })
|
|
1255
|
+
}
|
|
1256
|
+
];
|
|
1257
|
+
return {
|
|
1258
|
+
name: "Segmento",
|
|
1259
|
+
namePlural: "Segmentos",
|
|
1260
|
+
icon: "Filter",
|
|
1261
|
+
displayField: "name",
|
|
1262
|
+
defaultSort: "name",
|
|
1263
|
+
fields,
|
|
1264
|
+
fieldGroups: [
|
|
1265
|
+
{ id: "basic", label: "Segmento", columns: 2 },
|
|
1266
|
+
{
|
|
1267
|
+
id: "rule",
|
|
1268
|
+
label: "Regra",
|
|
1269
|
+
columns: 2,
|
|
1270
|
+
description: "Todos os crit\xE9rios preenchidos valem juntos. Deixe vazio o que n\xE3o quiser exigir."
|
|
1271
|
+
}
|
|
1272
|
+
]
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
789
1275
|
|
|
790
1276
|
// src/data/shared.ts
|
|
791
1277
|
var resolve = (p) => typeof p === "function" ? p() : p;
|
|
@@ -923,6 +1409,211 @@ function createShopShippingZoneDataProvider(provider) {
|
|
|
923
1409
|
}
|
|
924
1410
|
};
|
|
925
1411
|
}
|
|
1412
|
+
|
|
1413
|
+
// src/data/categoryDataProvider.ts
|
|
1414
|
+
function createShopCategoryDataProvider(provider) {
|
|
1415
|
+
return {
|
|
1416
|
+
async list(query) {
|
|
1417
|
+
const rows = await resolve(provider).listCategories();
|
|
1418
|
+
const term = query.search?.trim().toLowerCase();
|
|
1419
|
+
const filtered = term ? rows.filter((c) => c.name.toLowerCase().includes(term) || (c.slug ?? "").toLowerCase().includes(term)) : rows;
|
|
1420
|
+
return applyQuery(filtered, query);
|
|
1421
|
+
},
|
|
1422
|
+
async create(data) {
|
|
1423
|
+
const input = data;
|
|
1424
|
+
return resolve(provider).createCategory({
|
|
1425
|
+
name: String(input.name ?? ""),
|
|
1426
|
+
slug: input.slug?.trim() || void 0,
|
|
1427
|
+
description: input.description ?? void 0,
|
|
1428
|
+
// The form writes '' for "no parent"; the column is a nullable FK, and
|
|
1429
|
+
// '' is not a uuid — it must go as absent, not as empty text.
|
|
1430
|
+
parentId: input.parentId?.trim() || void 0,
|
|
1431
|
+
sortOrder: input.sortOrder ?? void 0
|
|
1432
|
+
});
|
|
1433
|
+
},
|
|
1434
|
+
async update(id, data) {
|
|
1435
|
+
const patch = {};
|
|
1436
|
+
if (data.name !== void 0) patch.name = data.name;
|
|
1437
|
+
if (data.slug !== void 0) patch.slug = data.slug?.trim() || void 0;
|
|
1438
|
+
if (data.description !== void 0) patch.description = data.description;
|
|
1439
|
+
if (data.parentId !== void 0) patch.parentId = data.parentId?.trim() || null;
|
|
1440
|
+
if (data.sortOrder !== void 0) patch.sortOrder = data.sortOrder;
|
|
1441
|
+
return resolve(provider).updateCategory(id, patch);
|
|
1442
|
+
},
|
|
1443
|
+
async remove(id) {
|
|
1444
|
+
await resolve(provider).deleteCategory(id);
|
|
1445
|
+
}
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// src/data/collectionDataProvider.ts
|
|
1450
|
+
function requireCollections(p) {
|
|
1451
|
+
if (!p.listCollections) throw new Error("This store provider does not support collections");
|
|
1452
|
+
return p;
|
|
1453
|
+
}
|
|
1454
|
+
function createShopCollectionDataProvider(provider) {
|
|
1455
|
+
return {
|
|
1456
|
+
async list(query) {
|
|
1457
|
+
const p = resolve(provider);
|
|
1458
|
+
const rows = await p.listCollections?.() ?? [];
|
|
1459
|
+
const term = query.search?.trim().toLowerCase();
|
|
1460
|
+
const filtered = term ? rows.filter((c) => c.title.toLowerCase().includes(term) || (c.handle ?? "").toLowerCase().includes(term)) : rows;
|
|
1461
|
+
return applyQuery(filtered, query);
|
|
1462
|
+
},
|
|
1463
|
+
async create(data) {
|
|
1464
|
+
const p = requireCollections(resolve(provider));
|
|
1465
|
+
const input = data;
|
|
1466
|
+
return p.createCollection({
|
|
1467
|
+
title: String(input.title ?? ""),
|
|
1468
|
+
handle: input.handle?.trim() || void 0,
|
|
1469
|
+
description: input.description ?? null,
|
|
1470
|
+
seoTitle: input.seoTitle ?? null,
|
|
1471
|
+
seoDescription: input.seoDescription ?? null,
|
|
1472
|
+
published: input.published ?? true,
|
|
1473
|
+
sortOrder: input.sortOrder ?? void 0
|
|
1474
|
+
});
|
|
1475
|
+
},
|
|
1476
|
+
async update(id, data) {
|
|
1477
|
+
const p = requireCollections(resolve(provider));
|
|
1478
|
+
const { productIds: _members, ...patch } = data;
|
|
1479
|
+
return p.updateCollection(id, patch);
|
|
1480
|
+
},
|
|
1481
|
+
async remove(id) {
|
|
1482
|
+
const p = requireCollections(resolve(provider));
|
|
1483
|
+
await p.deleteCollection(id);
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// src/data/giftCardDataProvider.ts
|
|
1489
|
+
function requireGiftCards(p) {
|
|
1490
|
+
if (!p.listGiftCards) throw new Error("This store provider does not support gift cards");
|
|
1491
|
+
return p;
|
|
1492
|
+
}
|
|
1493
|
+
function createShopGiftCardDataProvider(provider) {
|
|
1494
|
+
return {
|
|
1495
|
+
async list(query) {
|
|
1496
|
+
const rows = await resolve(provider).listGiftCards?.() ?? [];
|
|
1497
|
+
const term = query.search?.trim().toLowerCase();
|
|
1498
|
+
const filtered = term ? rows.filter((g) => g.code.toLowerCase().includes(term) || (g.customerEmail ?? "").toLowerCase().includes(term)) : rows;
|
|
1499
|
+
return applyQuery(filtered, query, "status");
|
|
1500
|
+
},
|
|
1501
|
+
async create(data) {
|
|
1502
|
+
const p = requireGiftCards(resolve(provider));
|
|
1503
|
+
const input = data;
|
|
1504
|
+
return p.createGiftCard({
|
|
1505
|
+
code: input.code?.trim() || void 0,
|
|
1506
|
+
initialBalance: Number(input.initialBalance) || 0,
|
|
1507
|
+
status: input.status ?? "active",
|
|
1508
|
+
expiresAt: input.expiresAt || null,
|
|
1509
|
+
customerEmail: input.customerEmail || null,
|
|
1510
|
+
note: input.note || null
|
|
1511
|
+
});
|
|
1512
|
+
},
|
|
1513
|
+
async update(id, data) {
|
|
1514
|
+
const p = requireGiftCards(resolve(provider));
|
|
1515
|
+
const { initialBalance: _face, ...patch } = data;
|
|
1516
|
+
return p.updateGiftCard(id, patch);
|
|
1517
|
+
},
|
|
1518
|
+
async remove(id) {
|
|
1519
|
+
const p = requireGiftCards(resolve(provider));
|
|
1520
|
+
await p.deleteGiftCard(id);
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// src/data/customerGroupDataProvider.ts
|
|
1526
|
+
function requireGroups(p) {
|
|
1527
|
+
if (!p.listCustomerGroups) throw new Error("This store provider does not support customer groups");
|
|
1528
|
+
return p;
|
|
1529
|
+
}
|
|
1530
|
+
function createShopCustomerGroupDataProvider(provider) {
|
|
1531
|
+
return {
|
|
1532
|
+
async list(query) {
|
|
1533
|
+
const rows = await resolve(provider).listCustomerGroups?.() ?? [];
|
|
1534
|
+
const term = query.search?.trim().toLowerCase();
|
|
1535
|
+
const filtered = term ? rows.filter((g) => g.name.toLowerCase().includes(term) || (g.description ?? "").toLowerCase().includes(term)) : rows;
|
|
1536
|
+
return applyQuery(filtered, query);
|
|
1537
|
+
},
|
|
1538
|
+
async create(data) {
|
|
1539
|
+
const p = requireGroups(resolve(provider));
|
|
1540
|
+
const input = data;
|
|
1541
|
+
return p.createCustomerGroup({
|
|
1542
|
+
name: String(input.name ?? ""),
|
|
1543
|
+
description: input.description ?? null,
|
|
1544
|
+
color: input.color ?? null
|
|
1545
|
+
});
|
|
1546
|
+
},
|
|
1547
|
+
async update(id, data) {
|
|
1548
|
+
const p = requireGroups(resolve(provider));
|
|
1549
|
+
const { customerIds: _members, ...patch } = data;
|
|
1550
|
+
return p.updateCustomerGroup(id, patch);
|
|
1551
|
+
},
|
|
1552
|
+
async remove(id) {
|
|
1553
|
+
const p = requireGroups(resolve(provider));
|
|
1554
|
+
await p.deleteCustomerGroup(id);
|
|
1555
|
+
}
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// src/data/customerSegmentDataProvider.ts
|
|
1560
|
+
function requireSegments(p) {
|
|
1561
|
+
if (!p.listCustomerSegments) throw new Error("This store provider does not support customer segments");
|
|
1562
|
+
return p;
|
|
1563
|
+
}
|
|
1564
|
+
function customerMatchesSegment(c, s) {
|
|
1565
|
+
const orders = c.ordersCount ?? 0;
|
|
1566
|
+
const spent = c.totalSpent ?? 0;
|
|
1567
|
+
if (s.minOrders != null && orders < s.minOrders) return false;
|
|
1568
|
+
if (s.maxOrders != null && orders > s.maxOrders) return false;
|
|
1569
|
+
if (s.minSpent != null && spent < s.minSpent) return false;
|
|
1570
|
+
if (s.maxSpent != null && spent > s.maxSpent) return false;
|
|
1571
|
+
const joined = c.createdAt?.slice(0, 10);
|
|
1572
|
+
if (s.joinedAfter && (!joined || joined < s.joinedAfter)) return false;
|
|
1573
|
+
if (s.joinedBefore && (!joined || joined > s.joinedBefore)) return false;
|
|
1574
|
+
return true;
|
|
1575
|
+
}
|
|
1576
|
+
function createShopCustomerSegmentDataProvider(provider) {
|
|
1577
|
+
return {
|
|
1578
|
+
async list(query) {
|
|
1579
|
+
const p = resolve(provider);
|
|
1580
|
+
const rows = await p.listCustomerSegments?.() ?? [];
|
|
1581
|
+
const customers = rows.length ? await p.listCustomers().catch(() => []) : [];
|
|
1582
|
+
const withCounts = rows.map((s) => ({
|
|
1583
|
+
...s,
|
|
1584
|
+
matchCount: customers.filter((c) => customerMatchesSegment(c, s)).length
|
|
1585
|
+
}));
|
|
1586
|
+
const term = query.search?.trim().toLowerCase();
|
|
1587
|
+
const filtered = term ? withCounts.filter((s) => s.name.toLowerCase().includes(term) || (s.description ?? "").toLowerCase().includes(term)) : withCounts;
|
|
1588
|
+
return applyQuery(filtered, query);
|
|
1589
|
+
},
|
|
1590
|
+
async create(data) {
|
|
1591
|
+
const p = requireSegments(resolve(provider));
|
|
1592
|
+
const input = data;
|
|
1593
|
+
const created = await p.createCustomerSegment({
|
|
1594
|
+
name: String(input.name ?? ""),
|
|
1595
|
+
description: input.description ?? null,
|
|
1596
|
+
minOrders: input.minOrders ?? null,
|
|
1597
|
+
maxOrders: input.maxOrders ?? null,
|
|
1598
|
+
minSpent: input.minSpent ?? null,
|
|
1599
|
+
maxSpent: input.maxSpent ?? null,
|
|
1600
|
+
joinedAfter: input.joinedAfter || null,
|
|
1601
|
+
joinedBefore: input.joinedBefore || null
|
|
1602
|
+
});
|
|
1603
|
+
return { ...created, matchCount: 0 };
|
|
1604
|
+
},
|
|
1605
|
+
async update(id, data) {
|
|
1606
|
+
const p = requireSegments(resolve(provider));
|
|
1607
|
+
const { matchCount: _count, ...patch } = data;
|
|
1608
|
+
const updated = await p.updateCustomerSegment(id, patch);
|
|
1609
|
+
return { ...updated, matchCount: 0 };
|
|
1610
|
+
},
|
|
1611
|
+
async remove(id) {
|
|
1612
|
+
const p = requireSegments(resolve(provider));
|
|
1613
|
+
await p.deleteCustomerSegment(id);
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
926
1617
|
var CURRENCIES = [
|
|
927
1618
|
{ value: "BRL", label: "Real (R$)" },
|
|
928
1619
|
{ value: "USD", label: "US Dollar ($)" },
|
|
@@ -1217,13 +1908,133 @@ function AddChannelForm({ kind, onKind, onCancel, onAdd }) {
|
|
|
1217
1908
|
] })
|
|
1218
1909
|
] });
|
|
1219
1910
|
}
|
|
1911
|
+
var TABS = [
|
|
1912
|
+
{ value: "all", label: "Todos" },
|
|
1913
|
+
{ value: "image", label: "Imagens" },
|
|
1914
|
+
{ value: "video", label: "V\xEDdeos" }
|
|
1915
|
+
];
|
|
1916
|
+
function resolve3(resolver) {
|
|
1917
|
+
return typeof resolver === "function" ? resolver() : resolver ?? getShopProvider();
|
|
1918
|
+
}
|
|
1919
|
+
function formatSize(bytes) {
|
|
1920
|
+
if (!bytes) return "";
|
|
1921
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
1922
|
+
const i = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
|
1923
|
+
return `${(bytes / 1024 ** i).toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
|
|
1924
|
+
}
|
|
1925
|
+
function ContentPage({ resolveProvider }) {
|
|
1926
|
+
const provider = React3.useMemo(() => resolve3(resolveProvider), [resolveProvider]);
|
|
1927
|
+
const supported = typeof provider.listMediaFiles === "function";
|
|
1928
|
+
const [files, setFiles] = React3.useState(null);
|
|
1929
|
+
const [tab, setTab] = React3.useState("all");
|
|
1930
|
+
const [uploading, setUploading] = React3.useState(false);
|
|
1931
|
+
const inputRef = React3.useRef(null);
|
|
1932
|
+
const reload = React3.useCallback(async () => {
|
|
1933
|
+
if (!provider.listMediaFiles) {
|
|
1934
|
+
setFiles([]);
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
try {
|
|
1938
|
+
setFiles(await provider.listMediaFiles());
|
|
1939
|
+
} catch (err) {
|
|
1940
|
+
console.warn("[content] failed to list media", err);
|
|
1941
|
+
setFiles([]);
|
|
1942
|
+
}
|
|
1943
|
+
}, [provider]);
|
|
1944
|
+
React3.useEffect(() => {
|
|
1945
|
+
void reload();
|
|
1946
|
+
}, [reload]);
|
|
1947
|
+
const handleUpload = async (picked) => {
|
|
1948
|
+
if (!picked?.length || !provider.uploadMediaFile) return;
|
|
1949
|
+
setUploading(true);
|
|
1950
|
+
try {
|
|
1951
|
+
for (const file of Array.from(picked)) {
|
|
1952
|
+
await provider.uploadMediaFile(file);
|
|
1953
|
+
}
|
|
1954
|
+
toast.success(picked.length === 1 ? "Arquivo enviado" : `${picked.length} arquivos enviados`);
|
|
1955
|
+
await reload();
|
|
1956
|
+
} catch (err) {
|
|
1957
|
+
console.warn("[content] upload failed", err);
|
|
1958
|
+
toast.error("N\xE3o foi poss\xEDvel enviar o arquivo");
|
|
1959
|
+
} finally {
|
|
1960
|
+
setUploading(false);
|
|
1961
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
1962
|
+
}
|
|
1963
|
+
};
|
|
1964
|
+
const handleDelete = async (file) => {
|
|
1965
|
+
if (!provider.deleteMediaFile) return;
|
|
1966
|
+
try {
|
|
1967
|
+
await provider.deleteMediaFile(file.id);
|
|
1968
|
+
setFiles((prev) => prev?.filter((f) => f.id !== file.id) ?? null);
|
|
1969
|
+
} catch (err) {
|
|
1970
|
+
console.warn("[content] delete failed", err);
|
|
1971
|
+
toast.error("N\xE3o foi poss\xEDvel excluir o arquivo");
|
|
1972
|
+
}
|
|
1973
|
+
};
|
|
1974
|
+
const visible = (files ?? []).filter((f) => tab === "all" || f.kind === tab);
|
|
1975
|
+
const imageCount = (files ?? []).filter((f) => f.kind === "image").length;
|
|
1976
|
+
const caption = files === null ? "" : `${files.length} ${files.length === 1 ? "arquivo" : "arquivos"} \xB7 ${imageCount} ${imageCount === 1 ? "imagem" : "imagens"}`;
|
|
1977
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
1978
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [
|
|
1979
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1980
|
+
/* @__PURE__ */ jsx(HeaderTabs, { tabs: TABS, value: tab, onChange: setTab, "aria-label": "Arquivos" }),
|
|
1981
|
+
caption && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: caption })
|
|
1982
|
+
] }),
|
|
1983
|
+
supported && provider.uploadMediaFile && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1984
|
+
/* @__PURE__ */ jsx(
|
|
1985
|
+
"input",
|
|
1986
|
+
{
|
|
1987
|
+
ref: inputRef,
|
|
1988
|
+
type: "file",
|
|
1989
|
+
multiple: true,
|
|
1990
|
+
className: "hidden",
|
|
1991
|
+
onChange: (e) => void handleUpload(e.target.files)
|
|
1992
|
+
}
|
|
1993
|
+
),
|
|
1994
|
+
/* @__PURE__ */ jsxs(Button, { onClick: () => inputRef.current?.click(), disabled: uploading, children: [
|
|
1995
|
+
/* @__PURE__ */ jsx(Upload, { className: "mr-2 h-4 w-4" }),
|
|
1996
|
+
uploading ? "Enviando\u2026" : "Enviar arquivos"
|
|
1997
|
+
] })
|
|
1998
|
+
] })
|
|
1999
|
+
] }),
|
|
2000
|
+
!supported ? /* @__PURE__ */ jsxs(Card, { className: "p-12 text-center", children: [
|
|
2001
|
+
/* @__PURE__ */ jsx(ImageOff, { className: "mx-auto h-8 w-8 text-muted-foreground" }),
|
|
2002
|
+
/* @__PURE__ */ jsx("p", { className: "mt-3 font-medium", children: "Armazenamento n\xE3o configurado" }),
|
|
2003
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "A biblioteca de arquivos precisa do storage da loja, que n\xE3o est\xE1 dispon\xEDvel neste modo." })
|
|
2004
|
+
] }) : files === null ? /* @__PURE__ */ jsx("div", { className: "grid gap-4 sm:grid-cols-3 lg:grid-cols-5", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsxs(Card, { className: "p-3", children: [
|
|
2005
|
+
/* @__PURE__ */ jsx("div", { className: "aspect-square animate-pulse rounded-md bg-muted" }),
|
|
2006
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2 h-3.5 w-2/3 animate-pulse rounded bg-muted" })
|
|
2007
|
+
] }, i)) }) : visible.length === 0 ? /* @__PURE__ */ jsxs(Card, { className: "p-12 text-center", children: [
|
|
2008
|
+
/* @__PURE__ */ jsx(ImageOff, { className: "mx-auto h-8 w-8 text-muted-foreground" }),
|
|
2009
|
+
/* @__PURE__ */ jsx("p", { className: "mt-3 font-medium", children: "Nenhum arquivo ainda" }),
|
|
2010
|
+
/* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "As fotos dos seus produtos e o que voc\xEA enviar aqui aparecem nesta biblioteca." })
|
|
2011
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "grid gap-4 sm:grid-cols-3 lg:grid-cols-5", children: visible.map((file) => /* @__PURE__ */ jsxs(Card, { className: "group overflow-hidden p-3", children: [
|
|
2012
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex aspect-square items-center justify-center overflow-hidden rounded-md bg-muted", children: [
|
|
2013
|
+
file.kind === "image" ? /* @__PURE__ */ jsx("img", { src: file.url, alt: file.name, loading: "lazy", className: "h-full w-full object-contain" }) : file.kind === "video" ? /* @__PURE__ */ jsx(Film, { className: "h-8 w-8 text-muted-foreground" }) : /* @__PURE__ */ jsx(FileText, { className: "h-8 w-8 text-muted-foreground" }),
|
|
2014
|
+
provider.deleteMediaFile && /* @__PURE__ */ jsx(Hint, { label: "Excluir", children: /* @__PURE__ */ jsx(
|
|
2015
|
+
"button",
|
|
2016
|
+
{
|
|
2017
|
+
type: "button",
|
|
2018
|
+
"aria-label": "Excluir",
|
|
2019
|
+
onClick: () => void handleDelete(file),
|
|
2020
|
+
className: "absolute right-1.5 top-1.5 rounded-md bg-card/90 p-1.5 text-muted-foreground opacity-0 shadow-button transition-opacity hover:text-destructive group-hover:opacity-100",
|
|
2021
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
2022
|
+
}
|
|
2023
|
+
) })
|
|
2024
|
+
] }),
|
|
2025
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 truncate text-[13px] font-medium", title: file.name, children: file.name }),
|
|
2026
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: [file.extension, formatSize(file.size)].filter(Boolean).join(" \xB7 ") })
|
|
2027
|
+
] }, file.id)) })
|
|
2028
|
+
] });
|
|
2029
|
+
}
|
|
1220
2030
|
|
|
1221
2031
|
// src/index.ts
|
|
1222
|
-
var ShopPage = React3.lazy(() => import('./ShopPage-
|
|
2032
|
+
var ShopPage = React3.lazy(() => import('./ShopPage-PVHFKDF3.js').then((m) => ({ default: m.ShopPage })));
|
|
1223
2033
|
function createShopPlugin(options) {
|
|
1224
2034
|
const curr = { code: "BRL", locale: "pt-BR", symbol: "R$", ...options?.currency };
|
|
1225
2035
|
setShopTenantResolver(() => useOrganizationStore.getState().currentOrg?.id);
|
|
1226
|
-
|
|
2036
|
+
registerMessageDefaults(buildShopMessageDefaults());
|
|
2037
|
+
registerEntityPath("shop_customer", "/orders/customers");
|
|
1227
2038
|
const pageFor = (section) => {
|
|
1228
2039
|
const Wrapped = () => React3.createElement(
|
|
1229
2040
|
React3.Suspense,
|
|
@@ -1233,43 +2044,279 @@ function createShopPlugin(options) {
|
|
|
1233
2044
|
{ provider: options?.provider, section }
|
|
1234
2045
|
)
|
|
1235
2046
|
);
|
|
1236
|
-
Wrapped.displayName =
|
|
2047
|
+
Wrapped.displayName = `ShopPage:${section}` ;
|
|
1237
2048
|
return Wrapped;
|
|
1238
2049
|
};
|
|
1239
2050
|
const resolveProvider = () => typeof options?.provider === "function" ? options.provider() : options?.provider ?? getShopProvider();
|
|
2051
|
+
const label = (key, fallback) => options?.sectionLabels?.[key] ?? fallback;
|
|
1240
2052
|
const ProductsCrudPage = createCrudPage(buildShopProductEntity(curr.code, resolveProvider), {
|
|
1241
2053
|
dataProvider: createShopProductDataProvider(resolveProvider),
|
|
1242
|
-
feature: "shop"
|
|
2054
|
+
feature: "shop",
|
|
2055
|
+
stats: (items, total) => [
|
|
2056
|
+
{ id: "total", label: "Produtos", value: total, icon: Package },
|
|
2057
|
+
{ id: "active", label: "Ativos", value: items?.filter((p) => p.status === "active").length ?? 0, icon: Eye },
|
|
2058
|
+
{ id: "draft", label: "Rascunhos", value: items?.filter((p) => p.status === "draft").length ?? 0, icon: FileEdit },
|
|
2059
|
+
{ id: "out-of-stock", label: "Sem estoque", value: items?.filter((p) => (p.inventoryCount ?? 0) <= 0).length ?? 0, icon: PackageX }
|
|
2060
|
+
]
|
|
2061
|
+
});
|
|
2062
|
+
const customerTabs = () => [
|
|
2063
|
+
{
|
|
2064
|
+
id: "customers",
|
|
2065
|
+
label: label("customers", "Clientes"),
|
|
2066
|
+
icon: Users,
|
|
2067
|
+
onSelect: () => adminNavigateTo("/orders/customers")
|
|
2068
|
+
},
|
|
2069
|
+
{
|
|
2070
|
+
id: "groups",
|
|
2071
|
+
label: label("customerGroups", "Grupos"),
|
|
2072
|
+
icon: UsersRound,
|
|
2073
|
+
onSelect: () => adminNavigateTo("/orders/customers/groups")
|
|
2074
|
+
},
|
|
2075
|
+
{
|
|
2076
|
+
id: "segments",
|
|
2077
|
+
label: label("customerSegments", "Segmentos"),
|
|
2078
|
+
icon: Filter,
|
|
2079
|
+
onSelect: () => adminNavigateTo("/orders/customers/segments")
|
|
2080
|
+
}
|
|
2081
|
+
];
|
|
2082
|
+
const CustomerGroupsCrudPage = createCrudPage(buildShopCustomerGroupEntity(), {
|
|
2083
|
+
dataProvider: createShopCustomerGroupDataProvider(resolveProvider),
|
|
2084
|
+
feature: "shop",
|
|
2085
|
+
navTabs: customerTabs,
|
|
2086
|
+
activeNavTab: "groups",
|
|
2087
|
+
stats: (items, total) => [
|
|
2088
|
+
{ id: "total", label: "Grupos", value: total, icon: UsersRound },
|
|
2089
|
+
{
|
|
2090
|
+
id: "members",
|
|
2091
|
+
label: "Clientes agrupados",
|
|
2092
|
+
icon: Users,
|
|
2093
|
+
// Distinct people, not the sum: someone in two groups is still one
|
|
2094
|
+
// customer, and a sum would report more members than the store has.
|
|
2095
|
+
value: new Set((items ?? []).flatMap((g) => g.customerIds ?? [])).size
|
|
2096
|
+
},
|
|
2097
|
+
{
|
|
2098
|
+
id: "empty",
|
|
2099
|
+
label: "Grupos vazios",
|
|
2100
|
+
icon: UserX,
|
|
2101
|
+
value: items?.filter((g) => (g.customerIds?.length ?? 0) === 0).length ?? 0
|
|
2102
|
+
},
|
|
2103
|
+
{ id: "segments", label: "Segmentos", pending: "Ver aba", icon: Filter }
|
|
2104
|
+
]
|
|
2105
|
+
});
|
|
2106
|
+
const CustomerSegmentsCrudPage = createCrudPage(buildShopCustomerSegmentEntity(curr.code), {
|
|
2107
|
+
dataProvider: createShopCustomerSegmentDataProvider(resolveProvider),
|
|
2108
|
+
feature: "shop",
|
|
2109
|
+
navTabs: customerTabs,
|
|
2110
|
+
activeNavTab: "segments",
|
|
2111
|
+
stats: (items, total) => [
|
|
2112
|
+
{ id: "total", label: "Segmentos", value: total, icon: Filter },
|
|
2113
|
+
{
|
|
2114
|
+
id: "matched",
|
|
2115
|
+
label: "Maior segmento",
|
|
2116
|
+
icon: Users,
|
|
2117
|
+
value: items?.length ? Math.max(...items.map((s) => s.matchCount ?? 0)) : 0
|
|
2118
|
+
},
|
|
2119
|
+
{
|
|
2120
|
+
id: "empty",
|
|
2121
|
+
label: "Sem ningu\xE9m",
|
|
2122
|
+
icon: UserX,
|
|
2123
|
+
value: items?.filter((s) => (s.matchCount ?? 0) === 0).length ?? 0
|
|
2124
|
+
},
|
|
2125
|
+
{ id: "groups", label: "Grupos", pending: "Ver aba", icon: UsersRound }
|
|
2126
|
+
]
|
|
1243
2127
|
});
|
|
1244
2128
|
const CustomersCrudPage = createCrudPage(buildShopCustomerEntity(), {
|
|
1245
2129
|
dataProvider: createShopCustomerDataProvider(resolveProvider),
|
|
1246
|
-
feature: "shop"
|
|
2130
|
+
feature: "shop",
|
|
2131
|
+
navTabs: customerTabs,
|
|
2132
|
+
activeNavTab: "customers",
|
|
2133
|
+
stats: (items, total) => [
|
|
2134
|
+
{ id: "total", label: "Total de clientes", value: total, icon: Users },
|
|
2135
|
+
{ id: "with-orders", label: "Com pedidos", value: items?.filter((c) => (c.ordersCount ?? 0) > 0).length ?? 0, icon: ShoppingBag },
|
|
2136
|
+
// Não computados ainda: um zero confiante aqui seria lido como medição.
|
|
2137
|
+
{ id: "repeat", label: "Taxa de recompra", pending: "Em breve", icon: Repeat },
|
|
2138
|
+
{ id: "aov", label: "Ticket m\xE9dio", pending: "Em breve", icon: CircleDollarSign }
|
|
2139
|
+
]
|
|
1247
2140
|
});
|
|
1248
2141
|
const DiscountsCrudPage = createCrudPage(buildShopDiscountEntity(), {
|
|
1249
2142
|
dataProvider: createShopDiscountDataProvider(resolveProvider),
|
|
1250
|
-
feature: "shop"
|
|
2143
|
+
feature: "shop",
|
|
2144
|
+
stats: (items, total) => [
|
|
2145
|
+
{ id: "total", label: "Total de cupons", value: total, icon: Tag },
|
|
2146
|
+
{ id: "active", label: "Ativos agora", value: items?.filter((d) => d.status === "active").length ?? 0, icon: Eye },
|
|
2147
|
+
{ id: "uses", label: "Usos", value: items?.reduce((sum, d) => sum + (d.timesUsed ?? 0), 0) ?? 0, icon: TicketCheck },
|
|
2148
|
+
{ id: "draft", label: "Rascunhos", value: items?.filter((d) => d.status === "draft").length ?? 0, icon: FileEdit }
|
|
2149
|
+
]
|
|
1251
2150
|
});
|
|
1252
2151
|
const ShippingZonesCrudPage = createCrudPage(buildShopShippingZoneEntity(curr.code), {
|
|
1253
2152
|
dataProvider: createShopShippingZoneDataProvider(resolveProvider),
|
|
1254
2153
|
feature: "shop"
|
|
1255
2154
|
});
|
|
1256
|
-
const
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
2155
|
+
const CategoriesCrudPage = createCrudPage(buildShopCategoryEntity(), {
|
|
2156
|
+
dataProvider: createShopCategoryDataProvider(resolveProvider),
|
|
2157
|
+
feature: "shop"
|
|
2158
|
+
});
|
|
2159
|
+
const CollectionsCrudPage = createCrudPage(buildShopCollectionEntity(), {
|
|
2160
|
+
dataProvider: createShopCollectionDataProvider(resolveProvider),
|
|
2161
|
+
feature: "shop",
|
|
2162
|
+
stats: (items, total) => [
|
|
2163
|
+
{ id: "total", label: "Cole\xE7\xF5es", value: total, icon: Layers },
|
|
2164
|
+
{ id: "published", label: "Publicadas", value: items?.filter((c) => c.published).length ?? 0, icon: Eye },
|
|
2165
|
+
{ id: "draft", label: "Rascunhos", value: items?.filter((c) => !c.published).length ?? 0, icon: FileEdit },
|
|
2166
|
+
{
|
|
2167
|
+
id: "members",
|
|
2168
|
+
label: "Produtos agrupados",
|
|
2169
|
+
icon: Package,
|
|
2170
|
+
value: items?.reduce((sum, c) => sum + (c.productIds?.length ?? 0), 0) ?? 0
|
|
2171
|
+
}
|
|
2172
|
+
]
|
|
2173
|
+
});
|
|
2174
|
+
const GiftCardsCrudPage = createCrudPage(buildShopGiftCardEntity(curr.code), {
|
|
2175
|
+
dataProvider: createShopGiftCardDataProvider(resolveProvider),
|
|
2176
|
+
feature: "shop",
|
|
2177
|
+
stats: (items, total) => {
|
|
2178
|
+
const money3 = (v) => new Intl.NumberFormat(curr.locale, { style: "currency", currency: curr.code }).format(v);
|
|
2179
|
+
const active = items?.filter((g) => g.status === "active") ?? [];
|
|
2180
|
+
return [
|
|
2181
|
+
{ id: "total", label: "Cart\xF5es emitidos", value: total, icon: CreditCard },
|
|
2182
|
+
{ id: "active", label: "Ativos", value: active.length, icon: Eye },
|
|
2183
|
+
// Saldo em aberto é passivo: dinheiro já recebido que a loja ainda deve
|
|
2184
|
+
// em produto. É o número que a conciliação procura aqui.
|
|
2185
|
+
{
|
|
2186
|
+
id: "outstanding",
|
|
2187
|
+
label: "Saldo em aberto",
|
|
2188
|
+
icon: CircleDollarSign,
|
|
2189
|
+
value: money3(active.reduce((sum, g) => sum + (g.balance ?? 0), 0))
|
|
2190
|
+
},
|
|
2191
|
+
{
|
|
2192
|
+
id: "issued",
|
|
2193
|
+
label: "Valor emitido",
|
|
2194
|
+
icon: TicketCheck,
|
|
2195
|
+
value: money3(items?.reduce((sum, g) => sum + (g.initialBalance ?? 0), 0) ?? 0)
|
|
2196
|
+
}
|
|
2197
|
+
];
|
|
2198
|
+
}
|
|
2199
|
+
});
|
|
2200
|
+
const ContentPageWrapped = () => React3.createElement(ContentPage, { resolveProvider: options?.provider });
|
|
2201
|
+
ContentPageWrapped.displayName = "ContentPage";
|
|
1269
2202
|
const ChannelsPageWrapped = () => React3.createElement(ChannelsPage, { resolveProvider: options?.provider });
|
|
1270
2203
|
ChannelsPageWrapped.displayName = "ChannelsPage";
|
|
1271
2204
|
const navSection = options?.navSection ?? "main";
|
|
1272
2205
|
const navPosition = options?.navPosition ?? 6;
|
|
2206
|
+
const NAV = [
|
|
2207
|
+
// Pedidos ▸ Pedidos · Clientes
|
|
2208
|
+
{
|
|
2209
|
+
route: "/orders",
|
|
2210
|
+
label: label("orders", "Pedidos"),
|
|
2211
|
+
icon: "ShoppingBag",
|
|
2212
|
+
position: navPosition,
|
|
2213
|
+
component: pageFor("orders")
|
|
2214
|
+
},
|
|
2215
|
+
{
|
|
2216
|
+
route: "/orders/customers",
|
|
2217
|
+
label: label("customers", "Clientes"),
|
|
2218
|
+
icon: "Users",
|
|
2219
|
+
position: navPosition + 0.1,
|
|
2220
|
+
component: CustomersCrudPage
|
|
2221
|
+
},
|
|
2222
|
+
// Produtos ▸ Produtos · Coleções · Categorias
|
|
2223
|
+
{
|
|
2224
|
+
route: "/products",
|
|
2225
|
+
label: label("products", "Produtos"),
|
|
2226
|
+
icon: "Package",
|
|
2227
|
+
position: navPosition + 1,
|
|
2228
|
+
component: ProductsCrudPage
|
|
2229
|
+
},
|
|
2230
|
+
{
|
|
2231
|
+
route: "/products/collections",
|
|
2232
|
+
label: label("collections", "Cole\xE7\xF5es"),
|
|
2233
|
+
icon: "Layers",
|
|
2234
|
+
position: navPosition + 1.1,
|
|
2235
|
+
component: CollectionsCrudPage
|
|
2236
|
+
},
|
|
2237
|
+
{
|
|
2238
|
+
route: "/products/categories",
|
|
2239
|
+
label: label("categories", "Categorias"),
|
|
2240
|
+
icon: "FolderTree",
|
|
2241
|
+
position: navPosition + 1.2,
|
|
2242
|
+
component: CategoriesCrudPage
|
|
2243
|
+
},
|
|
2244
|
+
// Conteúdo — a biblioteca de arquivos (sem filhos).
|
|
2245
|
+
{
|
|
2246
|
+
route: "/content",
|
|
2247
|
+
label: label("content", "Conte\xFAdo"),
|
|
2248
|
+
icon: "Image",
|
|
2249
|
+
position: navPosition + 2,
|
|
2250
|
+
indexChild: false,
|
|
2251
|
+
component: ContentPageWrapped
|
|
2252
|
+
},
|
|
2253
|
+
// Descontos ▸ Descontos · Gift Cards
|
|
2254
|
+
{
|
|
2255
|
+
route: "/discounts",
|
|
2256
|
+
label: label("discounts", "Descontos"),
|
|
2257
|
+
icon: "Tag",
|
|
2258
|
+
position: navPosition + 3,
|
|
2259
|
+
component: DiscountsCrudPage
|
|
2260
|
+
},
|
|
2261
|
+
{
|
|
2262
|
+
route: "/discounts/gift-cards",
|
|
2263
|
+
label: label("giftCards", "Gift Cards"),
|
|
2264
|
+
icon: "CreditCard",
|
|
2265
|
+
position: navPosition + 3.1,
|
|
2266
|
+
component: GiftCardsCrudPage
|
|
2267
|
+
}
|
|
2268
|
+
];
|
|
2269
|
+
const HIDDEN_ROUTES = [
|
|
2270
|
+
{ path: "/store", component: ChannelsPageWrapped, title: label("store", "Canais de venda") },
|
|
2271
|
+
{
|
|
2272
|
+
path: "/store/shipping",
|
|
2273
|
+
component: ShippingZonesCrudPage,
|
|
2274
|
+
title: label("shipping", "Entrega"),
|
|
2275
|
+
parentRoute: "/store"
|
|
2276
|
+
},
|
|
2277
|
+
// Reached from the Clientes tab strip, deliberately not from the menu:
|
|
2278
|
+
// three sidebar entries for one destination is the duplication the tab
|
|
2279
|
+
// strip exists to avoid. Nesting under /orders/customers also keeps
|
|
2280
|
+
// "Clientes" highlighted in the rail while you are on them.
|
|
2281
|
+
// Detalhe do modelo de e-mail. Sem entrada no menu: é uma linha da lista,
|
|
2282
|
+
// não um destino. Ganha do curinga /emails/* por especificidade de rota.
|
|
2283
|
+
{
|
|
2284
|
+
path: "/orders/customers/groups",
|
|
2285
|
+
component: CustomerGroupsCrudPage,
|
|
2286
|
+
title: label("customerGroups", "Grupos"),
|
|
2287
|
+
parentRoute: "/orders/customers"
|
|
2288
|
+
},
|
|
2289
|
+
{
|
|
2290
|
+
path: "/orders/customers/segments",
|
|
2291
|
+
component: CustomerSegmentsCrudPage,
|
|
2292
|
+
title: label("customerSegments", "Segmentos"),
|
|
2293
|
+
parentRoute: "/orders/customers"
|
|
2294
|
+
}
|
|
2295
|
+
];
|
|
2296
|
+
const redirectTo = (target) => {
|
|
2297
|
+
const Redirect = () => {
|
|
2298
|
+
React3.useEffect(() => {
|
|
2299
|
+
adminNavigateTo(target);
|
|
2300
|
+
}, []);
|
|
2301
|
+
return null;
|
|
2302
|
+
};
|
|
2303
|
+
Redirect.displayName = `Redirect(${target})`;
|
|
2304
|
+
return Redirect;
|
|
2305
|
+
};
|
|
2306
|
+
const LEGACY_ROUTES = [
|
|
2307
|
+
{ path: "/shop", component: redirectTo("/products") },
|
|
2308
|
+
{ path: "/shop/products", component: redirectTo("/products") },
|
|
2309
|
+
{ path: "/shop/orders", component: redirectTo("/orders") },
|
|
2310
|
+
{ path: "/shop/discounts", component: redirectTo("/discounts") },
|
|
2311
|
+
{ path: "/shop/shipping", component: redirectTo("/store/shipping") },
|
|
2312
|
+
{ path: "/shop/channels", component: redirectTo("/store") },
|
|
2313
|
+
{ path: "/customers", component: redirectTo("/orders/customers") },
|
|
2314
|
+
// A mensageria da loja virou parte de Automações — mesmos eventos, agora ao
|
|
2315
|
+
// lado dos do CRM e da agenda, e com WhatsApp além do e-mail.
|
|
2316
|
+
{ path: "/emails", component: redirectTo("/automations") },
|
|
2317
|
+
{ path: "/emails/:event", component: redirectTo("/automations") }
|
|
2318
|
+
];
|
|
2319
|
+
const READ = { feature: "shop", action: "read" };
|
|
1273
2320
|
return {
|
|
1274
2321
|
id: "shop",
|
|
1275
2322
|
name: options?.navLabel ?? "Shop",
|
|
@@ -1282,72 +2329,27 @@ function createShopPlugin(options) {
|
|
|
1282
2329
|
scaffolds: ["ecommerce"],
|
|
1283
2330
|
defaultEnabled: true,
|
|
1284
2331
|
dependencies: [],
|
|
1285
|
-
navigation:
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
indexChild: false
|
|
1295
|
-
},
|
|
1296
|
-
...SECTIONS.map((s, i) => ({
|
|
1297
|
-
section: navSection,
|
|
1298
|
-
position: navPosition + i + 1,
|
|
1299
|
-
label: s.label,
|
|
1300
|
-
route: `/shop/${s.id}`,
|
|
1301
|
-
icon: s.icon,
|
|
1302
|
-
permission: { feature: "shop", action: "read" }
|
|
1303
|
-
})),
|
|
1304
|
-
// Clientes — top-level (route not under /shop, so it leaves the accordion).
|
|
1305
|
-
// Fractional position keeps it right below "Loja", above other plugins.
|
|
1306
|
-
{
|
|
1307
|
-
section: navSection,
|
|
1308
|
-
position: navPosition + 0.5,
|
|
1309
|
-
label: CustomersNavLabel,
|
|
1310
|
-
route: "/customers",
|
|
1311
|
-
icon: "Users",
|
|
1312
|
-
permission: { feature: "shop", action: "read" }
|
|
1313
|
-
},
|
|
1314
|
-
// Canais de venda — inside the "Loja" group (route under /shop nests it in
|
|
1315
|
-
// the accordion). The store's points of sale (Loja Online, WhatsApp, …).
|
|
1316
|
-
{
|
|
1317
|
-
section: navSection,
|
|
1318
|
-
position: navPosition + SECTIONS.length + 1,
|
|
1319
|
-
label: "Canais de venda",
|
|
1320
|
-
route: "/shop/channels",
|
|
1321
|
-
icon: "Radio",
|
|
1322
|
-
permission: { feature: "shop", action: "read" }
|
|
1323
|
-
}
|
|
1324
|
-
],
|
|
2332
|
+
navigation: NAV.map((entry) => ({
|
|
2333
|
+
section: navSection,
|
|
2334
|
+
position: entry.position,
|
|
2335
|
+
label: entry.label,
|
|
2336
|
+
route: entry.route,
|
|
2337
|
+
icon: entry.icon,
|
|
2338
|
+
permission: READ,
|
|
2339
|
+
...entry.indexChild === false ? { indexChild: false } : {}
|
|
2340
|
+
})),
|
|
1325
2341
|
routes: [
|
|
1326
|
-
{
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
component: pageFor(),
|
|
1330
|
-
permission: { feature: "shop", action: "read" }
|
|
1331
|
-
},
|
|
1332
|
-
...SECTIONS.map((s) => ({
|
|
1333
|
-
path: `/shop/${s.id}`,
|
|
1334
|
-
component: CRUD_PAGES[s.id] ?? pageFor(s.id),
|
|
1335
|
-
permission: { feature: "shop", action: "read" }
|
|
1336
|
-
})),
|
|
1337
|
-
// Clientes at the top level, same CRUD page as before.
|
|
1338
|
-
{
|
|
1339
|
-
path: "/customers",
|
|
1340
|
-
component: CustomersCrudPage,
|
|
1341
|
-
permission: { feature: "shop", action: "read" }
|
|
1342
|
-
},
|
|
1343
|
-
// Canais de venda management page (under the Loja group).
|
|
1344
|
-
{
|
|
1345
|
-
path: "/shop/channels",
|
|
1346
|
-
component: ChannelsPageWrapped,
|
|
1347
|
-
permission: { feature: "shop", action: "read" }
|
|
1348
|
-
}
|
|
2342
|
+
...NAV.map((entry) => ({ path: entry.route, component: entry.component, permission: READ })),
|
|
2343
|
+
...HIDDEN_ROUTES.map((r) => ({ ...r, permission: READ })),
|
|
2344
|
+
...LEGACY_ROUTES.map((r) => ({ ...r, permission: READ }))
|
|
1349
2345
|
],
|
|
1350
2346
|
widgets: [],
|
|
2347
|
+
// Os cards de varejo ficaram na onda parqueada: os read models deles leem
|
|
2348
|
+
// public.fulfillments, que faz parte do modelo de estoque não portado.
|
|
2349
|
+
// Os fatos que a loja emite. Lidos por três consumidores — automações,
|
|
2350
|
+
// integrações e auditoria — e agregados pelo runtime só quando o plugin
|
|
2351
|
+
// está ATIVO, para um módulo desligado não anunciar eventos que não vêm.
|
|
2352
|
+
events: buildShopDomainEvents(),
|
|
1351
2353
|
aiTools: [
|
|
1352
2354
|
{
|
|
1353
2355
|
id: "shop.list-products",
|