forlogic-core 1.6.3 → 1.6.5

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 CHANGED
@@ -906,6 +906,265 @@ fetch(url, { headers: { 'un-alias': 'true' } }); // ✅
906
906
 
907
907
  ---
908
908
 
909
+ ## 🔍 FILTROS NO BACKEND (Backend Filtering)
910
+
911
+ ### **Por que Filtros no Backend?**
912
+
913
+ Filtrar dados no **backend** é a abordagem recomendada porque:
914
+ - ✅ **Paginação correta**: Total de itens reflete os dados filtrados
915
+ - ✅ **Performance**: Menos dados trafegados pela rede
916
+ - ✅ **Escalabilidade**: Funciona com milhares de registros
917
+ - ✅ **Ordenação**: Ordena apenas os dados filtrados
918
+
919
+ ### **Como Implementar Filtros no Backend**
920
+
921
+ #### **1️⃣ Filtros Estáticos (via additionalFilters)**
922
+
923
+ Para filtros simples e estáticos, use `additionalFilters` no service:
924
+
925
+ ```typescript
926
+ // src/subprocesses/SubprocessService.ts
927
+ import { createSimpleService } from 'forlogic-core';
928
+ import { Subprocess, SubprocessInsert, SubprocessUpdate } from './types';
929
+
930
+ export const {
931
+ service: subprocessService,
932
+ useCrudHook: baseUseCrudHook
933
+ } = createSimpleService<Subprocess, SubprocessInsert, SubprocessUpdate>({
934
+ tableName: 'subprocesses',
935
+ entityName: 'subprocesso',
936
+ schemaName: 'processes',
937
+ searchFields: ['title', 'description'],
938
+ enableQualiexEnrichment: true,
939
+ // Filtros estáticos aplicados sempre
940
+ additionalFilters: [
941
+ { field: 'is_removed', operator: 'eq', value: false }
942
+ ]
943
+ });
944
+ ```
945
+
946
+ #### **2️⃣ Filtros Dinâmicos (via Hook Customizado)**
947
+
948
+ Para filtros dinâmicos controlados por URL ou estado, crie um hook wrapper:
949
+
950
+ ```typescript
951
+ // src/subprocesses/SubprocessService.ts
952
+ import { useMemo } from 'react';
953
+ import { createSimpleService } from 'forlogic-core';
954
+
955
+ // Service base
956
+ export const {
957
+ service: subprocessService,
958
+ useCrudHook: baseUseCrudHook
959
+ } = createSimpleService<Subprocess, SubprocessInsert, SubprocessUpdate>({
960
+ tableName: 'subprocesses',
961
+ entityName: 'subprocesso',
962
+ schemaName: 'processes',
963
+ searchFields: ['title', 'description']
964
+ });
965
+
966
+ // Hook customizado que aceita filtros dinâmicos
967
+ export function useSubprocessesCrud(filters?: {
968
+ is_actived?: boolean;
969
+ id_process?: string | 'none';
970
+ }) {
971
+ // Transforma filtros em additionalFilters
972
+ const additionalFilters = useMemo(() => {
973
+ const filterList: any[] = [];
974
+
975
+ // Filtro de status (ativo/inativo)
976
+ if (filters?.is_actived !== undefined) {
977
+ filterList.push({
978
+ field: 'is_actived',
979
+ operator: 'eq',
980
+ value: filters.is_actived
981
+ });
982
+ }
983
+
984
+ // Filtro de processo (incluindo "sem processo")
985
+ if (filters?.id_process) {
986
+ if (filters.id_process === 'none') {
987
+ // Operador especial para NULL
988
+ filterList.push({
989
+ field: 'id_process',
990
+ operator: 'is_null'
991
+ });
992
+ } else {
993
+ filterList.push({
994
+ field: 'id_process',
995
+ operator: 'eq',
996
+ value: filters.id_process
997
+ });
998
+ }
999
+ }
1000
+
1001
+ return filterList;
1002
+ }, [filters?.is_actived, filters?.id_process]);
1003
+
1004
+ // Chama hook base com filtros dinâmicos
1005
+ return baseUseCrudHook({ additionalFilters });
1006
+ }
1007
+ ```
1008
+
1009
+ #### **3️⃣ Usar na Página com URL Search Params**
1010
+
1011
+ ```typescript
1012
+ // src/subprocesses/SubprocessesPage.tsx
1013
+ import { useSearchParams } from 'react-router-dom';
1014
+ import { useSubprocessesCrud } from './SubprocessService';
1015
+
1016
+ export default function SubprocessesPage() {
1017
+ const [searchParams, setSearchParams] = useSearchParams();
1018
+
1019
+ // Ler filtros da URL
1020
+ const filters = useMemo(() => ({
1021
+ is_actived: searchParams.get('is_actived') === 'true',
1022
+ id_process: searchParams.get('id_process') || undefined
1023
+ }), [searchParams]);
1024
+
1025
+ // Manager com filtros aplicados no backend
1026
+ const manager = useSubprocessesCrud(filters);
1027
+
1028
+ // Config com filtros de UI
1029
+ const config = useMemo(() => generateCrudConfig<Subprocess>({
1030
+ entityName: 'Subprocesso',
1031
+ entityNamePlural: 'Subprocessos',
1032
+ filters: [
1033
+ {
1034
+ type: 'custom',
1035
+ component: StatusSelect,
1036
+ props: {
1037
+ value: searchParams.get('is_actived') || 'all',
1038
+ onChange: (value: string) => {
1039
+ if (value === 'all') {
1040
+ searchParams.delete('is_actived');
1041
+ } else {
1042
+ searchParams.set('is_actived', value === 'active' ? 'true' : 'false');
1043
+ }
1044
+ setSearchParams(searchParams);
1045
+ }
1046
+ }
1047
+ },
1048
+ {
1049
+ type: 'custom',
1050
+ component: ProcessSelect,
1051
+ props: {
1052
+ value: searchParams.get('id_process') || 'all',
1053
+ onChange: (value: string) => {
1054
+ if (value === 'all') {
1055
+ searchParams.delete('id_process');
1056
+ } else {
1057
+ searchParams.set('id_process', value);
1058
+ }
1059
+ setSearchParams(searchParams);
1060
+ }
1061
+ }
1062
+ }
1063
+ ],
1064
+ columns: [...]
1065
+ }), [searchParams]);
1066
+
1067
+ return <CrudPage />;
1068
+ }
1069
+ ```
1070
+
1071
+ ### **Operadores de Filtro Disponíveis**
1072
+
1073
+ O `BaseService` suporta os seguintes operadores em `additionalFilters`:
1074
+
1075
+ | Operador | Descrição | Exemplo |
1076
+ |----------|-----------|---------|
1077
+ | `eq` | Igual a | `{ field: 'status', operator: 'eq', value: 'active' }` |
1078
+ | `neq` | Diferente de | `{ field: 'status', operator: 'neq', value: 'archived' }` |
1079
+ | `gt` | Maior que | `{ field: 'price', operator: 'gt', value: 100 }` |
1080
+ | `gte` | Maior ou igual | `{ field: 'price', operator: 'gte', value: 100 }` |
1081
+ | `lt` | Menor que | `{ field: 'stock', operator: 'lt', value: 10 }` |
1082
+ | `lte` | Menor ou igual | `{ field: 'stock', operator: 'lte', value: 10 }` |
1083
+ | `in` | Em lista | `{ field: 'category', operator: 'in', value: ['A', 'B'] }` |
1084
+ | `contains` | Contém texto | `{ field: 'name', operator: 'contains', value: 'test' }` |
1085
+ | `is_null` | É nulo | `{ field: 'deleted_at', operator: 'is_null' }` |
1086
+
1087
+ ### **Exemplo Completo: Filtro de Status e Processo**
1088
+
1089
+ ```typescript
1090
+ // Hook com filtros dinâmicos
1091
+ export function useSubprocessesCrud(filters?: {
1092
+ is_actived?: boolean;
1093
+ id_process?: string | 'none';
1094
+ }) {
1095
+ const additionalFilters = useMemo(() => {
1096
+ const filterList: any[] = [];
1097
+
1098
+ if (filters?.is_actived !== undefined) {
1099
+ filterList.push({
1100
+ field: 'is_actived',
1101
+ operator: 'eq',
1102
+ value: filters.is_actived
1103
+ });
1104
+ }
1105
+
1106
+ if (filters?.id_process) {
1107
+ if (filters.id_process === 'none') {
1108
+ filterList.push({
1109
+ field: 'id_process',
1110
+ operator: 'is_null'
1111
+ });
1112
+ } else {
1113
+ filterList.push({
1114
+ field: 'id_process',
1115
+ operator: 'eq',
1116
+ value: filters.id_process
1117
+ });
1118
+ }
1119
+ }
1120
+
1121
+ return filterList;
1122
+ }, [filters?.is_actived, filters?.id_process]);
1123
+
1124
+ return baseUseCrudHook({ additionalFilters });
1125
+ }
1126
+
1127
+ // Na página
1128
+ const filters = useMemo(() => ({
1129
+ is_actived: searchParams.get('is_actived') === 'true',
1130
+ id_process: searchParams.get('id_process') || undefined
1131
+ }), [searchParams]);
1132
+
1133
+ const manager = useSubprocessesCrud(filters);
1134
+
1135
+ // Resultado: Paginação mostra "1-10 de 43 itens" (correto!)
1136
+ // Backend retorna apenas os 43 subprocessos ativos
1137
+ ```
1138
+
1139
+ ### **⚠️ Filtros Frontend vs Backend**
1140
+
1141
+ **❌ Filtros no Frontend (EVITAR):**
1142
+ ```typescript
1143
+ // Problema: Paginação incorreta
1144
+ const filteredEntities = useMemo(() =>
1145
+ manager.entities.filter(e => e.is_actived),
1146
+ [manager.entities]
1147
+ );
1148
+
1149
+ // manager.totalCount = 100 (total do backend)
1150
+ // filteredEntities.length = 43 (após filtro)
1151
+ // Paginação mostra "1-10 de 100 itens" ❌ ERRADO
1152
+ ```
1153
+
1154
+ **✅ Filtros no Backend (CORRETO):**
1155
+ ```typescript
1156
+ // Backend retorna apenas dados filtrados
1157
+ const manager = useSubprocessesCrud({
1158
+ is_actived: true
1159
+ });
1160
+
1161
+ // manager.totalCount = 43 (correto!)
1162
+ // manager.entities.length = 10 (página 1)
1163
+ // Paginação mostra "1-10 de 43 itens" ✅ CORRETO
1164
+ ```
1165
+
1166
+ ---
1167
+
909
1168
  ## 📐 CONTROLE DE LARGURA DAS COLUNAS
910
1169
 
911
1170
  O `forlogic-core` oferece três formas de definir larguras de colunas nas tabelas CRUD:
@@ -1027,4 +1286,4 @@ src/
1027
1286
 
1028
1287
  MIT License - ForLogic © 2025
1029
1288
 
1030
- **Última atualização:** 2025-10-05
1289
+ **Última atualização:** 2025-10-06
package/dist/README.md CHANGED
@@ -906,6 +906,265 @@ fetch(url, { headers: { 'un-alias': 'true' } }); // ✅
906
906
 
907
907
  ---
908
908
 
909
+ ## 🔍 FILTROS NO BACKEND (Backend Filtering)
910
+
911
+ ### **Por que Filtros no Backend?**
912
+
913
+ Filtrar dados no **backend** é a abordagem recomendada porque:
914
+ - ✅ **Paginação correta**: Total de itens reflete os dados filtrados
915
+ - ✅ **Performance**: Menos dados trafegados pela rede
916
+ - ✅ **Escalabilidade**: Funciona com milhares de registros
917
+ - ✅ **Ordenação**: Ordena apenas os dados filtrados
918
+
919
+ ### **Como Implementar Filtros no Backend**
920
+
921
+ #### **1️⃣ Filtros Estáticos (via additionalFilters)**
922
+
923
+ Para filtros simples e estáticos, use `additionalFilters` no service:
924
+
925
+ ```typescript
926
+ // src/subprocesses/SubprocessService.ts
927
+ import { createSimpleService } from 'forlogic-core';
928
+ import { Subprocess, SubprocessInsert, SubprocessUpdate } from './types';
929
+
930
+ export const {
931
+ service: subprocessService,
932
+ useCrudHook: baseUseCrudHook
933
+ } = createSimpleService<Subprocess, SubprocessInsert, SubprocessUpdate>({
934
+ tableName: 'subprocesses',
935
+ entityName: 'subprocesso',
936
+ schemaName: 'processes',
937
+ searchFields: ['title', 'description'],
938
+ enableQualiexEnrichment: true,
939
+ // Filtros estáticos aplicados sempre
940
+ additionalFilters: [
941
+ { field: 'is_removed', operator: 'eq', value: false }
942
+ ]
943
+ });
944
+ ```
945
+
946
+ #### **2️⃣ Filtros Dinâmicos (via Hook Customizado)**
947
+
948
+ Para filtros dinâmicos controlados por URL ou estado, crie um hook wrapper:
949
+
950
+ ```typescript
951
+ // src/subprocesses/SubprocessService.ts
952
+ import { useMemo } from 'react';
953
+ import { createSimpleService } from 'forlogic-core';
954
+
955
+ // Service base
956
+ export const {
957
+ service: subprocessService,
958
+ useCrudHook: baseUseCrudHook
959
+ } = createSimpleService<Subprocess, SubprocessInsert, SubprocessUpdate>({
960
+ tableName: 'subprocesses',
961
+ entityName: 'subprocesso',
962
+ schemaName: 'processes',
963
+ searchFields: ['title', 'description']
964
+ });
965
+
966
+ // Hook customizado que aceita filtros dinâmicos
967
+ export function useSubprocessesCrud(filters?: {
968
+ is_actived?: boolean;
969
+ id_process?: string | 'none';
970
+ }) {
971
+ // Transforma filtros em additionalFilters
972
+ const additionalFilters = useMemo(() => {
973
+ const filterList: any[] = [];
974
+
975
+ // Filtro de status (ativo/inativo)
976
+ if (filters?.is_actived !== undefined) {
977
+ filterList.push({
978
+ field: 'is_actived',
979
+ operator: 'eq',
980
+ value: filters.is_actived
981
+ });
982
+ }
983
+
984
+ // Filtro de processo (incluindo "sem processo")
985
+ if (filters?.id_process) {
986
+ if (filters.id_process === 'none') {
987
+ // Operador especial para NULL
988
+ filterList.push({
989
+ field: 'id_process',
990
+ operator: 'is_null'
991
+ });
992
+ } else {
993
+ filterList.push({
994
+ field: 'id_process',
995
+ operator: 'eq',
996
+ value: filters.id_process
997
+ });
998
+ }
999
+ }
1000
+
1001
+ return filterList;
1002
+ }, [filters?.is_actived, filters?.id_process]);
1003
+
1004
+ // Chama hook base com filtros dinâmicos
1005
+ return baseUseCrudHook({ additionalFilters });
1006
+ }
1007
+ ```
1008
+
1009
+ #### **3️⃣ Usar na Página com URL Search Params**
1010
+
1011
+ ```typescript
1012
+ // src/subprocesses/SubprocessesPage.tsx
1013
+ import { useSearchParams } from 'react-router-dom';
1014
+ import { useSubprocessesCrud } from './SubprocessService';
1015
+
1016
+ export default function SubprocessesPage() {
1017
+ const [searchParams, setSearchParams] = useSearchParams();
1018
+
1019
+ // Ler filtros da URL
1020
+ const filters = useMemo(() => ({
1021
+ is_actived: searchParams.get('is_actived') === 'true',
1022
+ id_process: searchParams.get('id_process') || undefined
1023
+ }), [searchParams]);
1024
+
1025
+ // Manager com filtros aplicados no backend
1026
+ const manager = useSubprocessesCrud(filters);
1027
+
1028
+ // Config com filtros de UI
1029
+ const config = useMemo(() => generateCrudConfig<Subprocess>({
1030
+ entityName: 'Subprocesso',
1031
+ entityNamePlural: 'Subprocessos',
1032
+ filters: [
1033
+ {
1034
+ type: 'custom',
1035
+ component: StatusSelect,
1036
+ props: {
1037
+ value: searchParams.get('is_actived') || 'all',
1038
+ onChange: (value: string) => {
1039
+ if (value === 'all') {
1040
+ searchParams.delete('is_actived');
1041
+ } else {
1042
+ searchParams.set('is_actived', value === 'active' ? 'true' : 'false');
1043
+ }
1044
+ setSearchParams(searchParams);
1045
+ }
1046
+ }
1047
+ },
1048
+ {
1049
+ type: 'custom',
1050
+ component: ProcessSelect,
1051
+ props: {
1052
+ value: searchParams.get('id_process') || 'all',
1053
+ onChange: (value: string) => {
1054
+ if (value === 'all') {
1055
+ searchParams.delete('id_process');
1056
+ } else {
1057
+ searchParams.set('id_process', value);
1058
+ }
1059
+ setSearchParams(searchParams);
1060
+ }
1061
+ }
1062
+ }
1063
+ ],
1064
+ columns: [...]
1065
+ }), [searchParams]);
1066
+
1067
+ return <CrudPage />;
1068
+ }
1069
+ ```
1070
+
1071
+ ### **Operadores de Filtro Disponíveis**
1072
+
1073
+ O `BaseService` suporta os seguintes operadores em `additionalFilters`:
1074
+
1075
+ | Operador | Descrição | Exemplo |
1076
+ |----------|-----------|---------|
1077
+ | `eq` | Igual a | `{ field: 'status', operator: 'eq', value: 'active' }` |
1078
+ | `neq` | Diferente de | `{ field: 'status', operator: 'neq', value: 'archived' }` |
1079
+ | `gt` | Maior que | `{ field: 'price', operator: 'gt', value: 100 }` |
1080
+ | `gte` | Maior ou igual | `{ field: 'price', operator: 'gte', value: 100 }` |
1081
+ | `lt` | Menor que | `{ field: 'stock', operator: 'lt', value: 10 }` |
1082
+ | `lte` | Menor ou igual | `{ field: 'stock', operator: 'lte', value: 10 }` |
1083
+ | `in` | Em lista | `{ field: 'category', operator: 'in', value: ['A', 'B'] }` |
1084
+ | `contains` | Contém texto | `{ field: 'name', operator: 'contains', value: 'test' }` |
1085
+ | `is_null` | É nulo | `{ field: 'deleted_at', operator: 'is_null' }` |
1086
+
1087
+ ### **Exemplo Completo: Filtro de Status e Processo**
1088
+
1089
+ ```typescript
1090
+ // Hook com filtros dinâmicos
1091
+ export function useSubprocessesCrud(filters?: {
1092
+ is_actived?: boolean;
1093
+ id_process?: string | 'none';
1094
+ }) {
1095
+ const additionalFilters = useMemo(() => {
1096
+ const filterList: any[] = [];
1097
+
1098
+ if (filters?.is_actived !== undefined) {
1099
+ filterList.push({
1100
+ field: 'is_actived',
1101
+ operator: 'eq',
1102
+ value: filters.is_actived
1103
+ });
1104
+ }
1105
+
1106
+ if (filters?.id_process) {
1107
+ if (filters.id_process === 'none') {
1108
+ filterList.push({
1109
+ field: 'id_process',
1110
+ operator: 'is_null'
1111
+ });
1112
+ } else {
1113
+ filterList.push({
1114
+ field: 'id_process',
1115
+ operator: 'eq',
1116
+ value: filters.id_process
1117
+ });
1118
+ }
1119
+ }
1120
+
1121
+ return filterList;
1122
+ }, [filters?.is_actived, filters?.id_process]);
1123
+
1124
+ return baseUseCrudHook({ additionalFilters });
1125
+ }
1126
+
1127
+ // Na página
1128
+ const filters = useMemo(() => ({
1129
+ is_actived: searchParams.get('is_actived') === 'true',
1130
+ id_process: searchParams.get('id_process') || undefined
1131
+ }), [searchParams]);
1132
+
1133
+ const manager = useSubprocessesCrud(filters);
1134
+
1135
+ // Resultado: Paginação mostra "1-10 de 43 itens" (correto!)
1136
+ // Backend retorna apenas os 43 subprocessos ativos
1137
+ ```
1138
+
1139
+ ### **⚠️ Filtros Frontend vs Backend**
1140
+
1141
+ **❌ Filtros no Frontend (EVITAR):**
1142
+ ```typescript
1143
+ // Problema: Paginação incorreta
1144
+ const filteredEntities = useMemo(() =>
1145
+ manager.entities.filter(e => e.is_actived),
1146
+ [manager.entities]
1147
+ );
1148
+
1149
+ // manager.totalCount = 100 (total do backend)
1150
+ // filteredEntities.length = 43 (após filtro)
1151
+ // Paginação mostra "1-10 de 100 itens" ❌ ERRADO
1152
+ ```
1153
+
1154
+ **✅ Filtros no Backend (CORRETO):**
1155
+ ```typescript
1156
+ // Backend retorna apenas dados filtrados
1157
+ const manager = useSubprocessesCrud({
1158
+ is_actived: true
1159
+ });
1160
+
1161
+ // manager.totalCount = 43 (correto!)
1162
+ // manager.entities.length = 10 (página 1)
1163
+ // Paginação mostra "1-10 de 43 itens" ✅ CORRETO
1164
+ ```
1165
+
1166
+ ---
1167
+
909
1168
  ## 📐 CONTROLE DE LARGURA DAS COLUNAS
910
1169
 
911
1170
  O `forlogic-core` oferece três formas de definir larguras de colunas nas tabelas CRUD:
@@ -1027,4 +1286,4 @@ src/
1027
1286
 
1028
1287
  MIT License - ForLogic © 2025
1029
1288
 
1030
- **Última atualização:** 2025-10-05
1289
+ **Última atualização:** 2025-10-06