@jean.anjos/coresuit-gen-crud 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -0
- package/bin/cli.js +377 -0
- package/package.json +17 -0
- package/templates/page__components__filters.tsx.tpl +47 -0
- package/templates/page__consts__index.ts.tpl +12 -0
- package/templates/page__create__index.tsx.tpl +39 -0
- package/templates/page__delete__index.tsx.tpl +31 -0
- package/templates/page__hooks__use-create.ts.tpl +56 -0
- package/templates/page__hooks__use-delete.ts.tpl +46 -0
- package/templates/page__hooks__use-index.ts.tpl +58 -0
- package/templates/page__hooks__use-restore.ts.tpl +46 -0
- package/templates/page__hooks__use-update.ts.tpl +68 -0
- package/templates/page__index.tsx.tpl +117 -0
- package/templates/page__restore__index.tsx.tpl +31 -0
- package/templates/page__update__index.tsx.tpl +39 -0
- package/templates/service_index.ts.tpl +33 -0
- package/templates/service_types.ts.tpl +35 -0
- package/templates/types_interface.ts.tpl +7 -0
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
# como instalar no projeto
|
|
3
|
+
|
|
4
|
+
### Baixa o projeto git clone
|
|
5
|
+
|
|
6
|
+
## no seu package.json colocar
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
"scripts": {
|
|
10
|
+
"generate": "coresuit-react-gen"
|
|
11
|
+
},
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## instala com o seguinte comando
|
|
15
|
+
npm i -D C:\dev\core-suit\generate-crud-react-js
|
|
16
|
+
|
|
17
|
+
# (opcional) filtrar por tags:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
npm run generate -- "--link=http://127.0.0.1:8000/docs?api-docs.json" --only=Categoria,Cliente
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
npm run generate -- --name=categoria --fields="id:int:true,user_id:int:true,nome:string:true,deleted_at:string:false,created_at:string:false,updated_at:string:false"
|
|
26
|
+
```
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable no-console */
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
// ---------- utils de nome ----------
|
|
7
|
+
function toPascalCase(s) {
|
|
8
|
+
return String(s)
|
|
9
|
+
.replace(/[_\-\s]+/g, " ")
|
|
10
|
+
.replace(/(?:^|\s)(\w)/g, (_, c) => c.toUpperCase())
|
|
11
|
+
.replace(/\s+/g, "");
|
|
12
|
+
}
|
|
13
|
+
function toCamelCase(s) {
|
|
14
|
+
const p = toPascalCase(s);
|
|
15
|
+
return p.charAt(0).toLowerCase() + p.slice(1);
|
|
16
|
+
}
|
|
17
|
+
function toKebab(s) {
|
|
18
|
+
return String(s)
|
|
19
|
+
.trim()
|
|
20
|
+
.replace(/[_\s]+/g, "-")
|
|
21
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
22
|
+
.toLowerCase();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// nome de campo no MODEL (response) => camelCase simples
|
|
26
|
+
function toModelFieldName(dtoFieldName) {
|
|
27
|
+
const n = String(dtoFieldName || "").trim();
|
|
28
|
+
if (!n) return n;
|
|
29
|
+
return n.charAt(0).toLowerCase() + n.slice(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------- tipos ----------
|
|
33
|
+
function tsTypeFor(t) {
|
|
34
|
+
t = String(t || "").toLowerCase();
|
|
35
|
+
if (["int", "bigint", "integer", "smallint", "tinyint", "float", "double", "decimal", "number"].includes(t))
|
|
36
|
+
return "number";
|
|
37
|
+
if (["bool", "boolean"].includes(t)) return "boolean";
|
|
38
|
+
if (["json", "array", "object", "any"].includes(t)) return "any";
|
|
39
|
+
if (["date", "datetime", "date-time"].includes(t)) return "string";
|
|
40
|
+
return "string";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isNumberType(t) {
|
|
44
|
+
return tsTypeFor(t) === "number";
|
|
45
|
+
}
|
|
46
|
+
function isBooleanType(t) {
|
|
47
|
+
return tsTypeFor(t) === "boolean";
|
|
48
|
+
}
|
|
49
|
+
function isDateType(t) {
|
|
50
|
+
return ["date", "datetime", "date-time"].includes(String(t).toLowerCase()) || /date/i.test(String(t));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ---------- args ----------
|
|
54
|
+
function getArgMap(argv) {
|
|
55
|
+
const map = new Map();
|
|
56
|
+
const args = argv.slice(2);
|
|
57
|
+
|
|
58
|
+
for (let i = 0; i < args.length; i++) {
|
|
59
|
+
const a = args[i];
|
|
60
|
+
if (!a.startsWith("--")) continue;
|
|
61
|
+
|
|
62
|
+
const eq = a.indexOf("=");
|
|
63
|
+
if (eq > 2) {
|
|
64
|
+
map.set(a.slice(2, eq), a.slice(eq + 1));
|
|
65
|
+
} else {
|
|
66
|
+
const key = a.slice(2);
|
|
67
|
+
const nx = args[i + 1];
|
|
68
|
+
if (nx && !nx.startsWith("--")) {
|
|
69
|
+
map.set(key, nx);
|
|
70
|
+
i++;
|
|
71
|
+
} else map.set(key, "true");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// fallback npm_config_*
|
|
76
|
+
for (const k of ["name", "fields", "force", "baseDir", "servicesDir", "typesDir"]) {
|
|
77
|
+
const ev = process.env[`npm_config_${k.replace(/-/g, "_")}`];
|
|
78
|
+
if (ev !== undefined && !map.has(k)) map.set(k, ev);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Object.fromEntries(map);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------- fs helpers ----------
|
|
85
|
+
function ensureDir(dir) {
|
|
86
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
87
|
+
}
|
|
88
|
+
function writeFile(dest, contents, force) {
|
|
89
|
+
ensureDir(path.dirname(dest));
|
|
90
|
+
if (fs.existsSync(dest) && !force) {
|
|
91
|
+
console.log(`• Já existe: ${dest} (use --force=true para sobrescrever)`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
fs.writeFileSync(dest, contents);
|
|
95
|
+
console.log(`• Criado: ${dest}`);
|
|
96
|
+
}
|
|
97
|
+
function readTpl(name) {
|
|
98
|
+
const root = path.dirname(path.dirname(__filename));
|
|
99
|
+
return fs.readFileSync(path.join(root, "templates", name), "utf8");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---------- templates ----------
|
|
103
|
+
const TPL_SERVICE_INDEX = readTpl("service_index.ts.tpl");
|
|
104
|
+
const TPL_SERVICE_TYPES = readTpl("service_types.ts.tpl");
|
|
105
|
+
const TPL_ENTITY_IFACE = readTpl("types_interface.ts.tpl");
|
|
106
|
+
|
|
107
|
+
const TPL_PAGE_INDEX = readTpl("page__index.tsx.tpl");
|
|
108
|
+
const TPL_CONSTS = readTpl("page__consts__index.ts.tpl");
|
|
109
|
+
const TPL_FILTERS_COMPONENT = readTpl("page__components__filters.tsx.tpl");
|
|
110
|
+
|
|
111
|
+
const TPL_CREATE = readTpl("page__create__index.tsx.tpl");
|
|
112
|
+
const TPL_UPDATE = readTpl("page__update__index.tsx.tpl");
|
|
113
|
+
const TPL_DELETE = readTpl("page__delete__index.tsx.tpl");
|
|
114
|
+
const TPL_RESTORE = readTpl("page__restore__index.tsx.tpl");
|
|
115
|
+
|
|
116
|
+
const TPL_HOOK_INDEX = readTpl("page__hooks__use-index.ts.tpl");
|
|
117
|
+
const TPL_HOOK_CREATE = readTpl("page__hooks__use-create.ts.tpl");
|
|
118
|
+
const TPL_HOOK_UPDATE = readTpl("page__hooks__use-update.ts.tpl");
|
|
119
|
+
const TPL_HOOK_DELETE = readTpl("page__hooks__use-delete.ts.tpl");
|
|
120
|
+
const TPL_HOOK_RESTORE = readTpl("page__hooks__use-restore.ts.tpl");
|
|
121
|
+
|
|
122
|
+
function renderTemplate(tpl, ctx) {
|
|
123
|
+
return tpl
|
|
124
|
+
.replaceAll("{{PascalName}}", ctx.PascalName)
|
|
125
|
+
.replaceAll("{{camelName}}", ctx.camelName)
|
|
126
|
+
.replaceAll("{{kebabName}}", ctx.kebabName)
|
|
127
|
+
.replaceAll("{{route}}", ctx.route)
|
|
128
|
+
.replaceAll("{{FIELDS_INTERFACE}}", ctx.FIELDS_INTERFACE || "")
|
|
129
|
+
.replaceAll("{{FILTERS_INTERFACE}}", ctx.FILTERS_INTERFACE || "")
|
|
130
|
+
.replaceAll("{{CREATE_FIELDS}}", ctx.CREATE_FIELDS || "")
|
|
131
|
+
.replaceAll("{{UPDATE_FIELDS}}", ctx.UPDATE_FIELDS || "")
|
|
132
|
+
.replaceAll("{{TABLE_HEAD}}", ctx.TABLE_HEAD || "")
|
|
133
|
+
.replaceAll("{{TABLE_ROW_CELLS}}", ctx.TABLE_ROW_CELLS || "")
|
|
134
|
+
.replaceAll("{{FILTERS_INITIAL_OBJECT}}", ctx.FILTERS_INITIAL_OBJECT || "")
|
|
135
|
+
.replaceAll("{{FILTERS_FIELDS_JSX}}", ctx.FILTERS_FIELDS_JSX || "")
|
|
136
|
+
.replaceAll("{{FILTER_SPREAD_IN_QUERY}}", ctx.FILTER_SPREAD_IN_QUERY || "")
|
|
137
|
+
.replaceAll("{{FORM_FIELDS_CREATE_TSX}}", ctx.FORM_FIELDS_CREATE_TSX || "")
|
|
138
|
+
.replaceAll("{{FORM_FIELDS_UPDATE_TSX}}", ctx.FORM_FIELDS_UPDATE_TSX || "")
|
|
139
|
+
.replaceAll("{{YUP_CREATE_RULES}}", ctx.YUP_CREATE_RULES || "")
|
|
140
|
+
.replaceAll("{{YUP_UPDATE_RULES}}", ctx.YUP_UPDATE_RULES || "")
|
|
141
|
+
.replaceAll("{{FORM_DEFAULTS_CREATE}}", ctx.FORM_DEFAULTS_CREATE || "")
|
|
142
|
+
.replaceAll("{{FORM_DEFAULTS_UPDATE}}", ctx.FORM_DEFAULTS_UPDATE || "")
|
|
143
|
+
.replaceAll("{{FORM_VALUES_UPDATE}}", ctx.FORM_VALUES_UPDATE || "");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ---------- parsing manual ----------
|
|
147
|
+
function parseFields(raw) {
|
|
148
|
+
if (!raw) return [];
|
|
149
|
+
return raw
|
|
150
|
+
.split(",")
|
|
151
|
+
.map((x) => x.trim())
|
|
152
|
+
.filter(Boolean)
|
|
153
|
+
.map((chunk) => {
|
|
154
|
+
const [name, type, required] = chunk.split(":");
|
|
155
|
+
return {
|
|
156
|
+
name: String(name).trim(), // DTO/Request => PascalCase
|
|
157
|
+
type: String(type || "string").trim(),
|
|
158
|
+
required: String(required || "false").toLowerCase() === "true",
|
|
159
|
+
modelName: toModelFieldName(String(name).trim()), // Model/Response => camelCase
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ---------- geração (novo padrão) ----------
|
|
165
|
+
function buildCtxFromFields(resourceName, fields) {
|
|
166
|
+
const PascalName = toPascalCase(resourceName); // Bank
|
|
167
|
+
const camelName = toCamelCase(resourceName); // bank
|
|
168
|
+
const kebabName = toKebab(resourceName); // bank
|
|
169
|
+
|
|
170
|
+
// endpoint => PascalName literal
|
|
171
|
+
const route = PascalName;
|
|
172
|
+
|
|
173
|
+
// 1) Interface de entidade (MODEL/Response) camelCase + campos padrão
|
|
174
|
+
const entityFields = [
|
|
175
|
+
{ name: "id", ts: "number" },
|
|
176
|
+
...fields.map((f) => ({ name: f.modelName, ts: tsTypeFor(f.type) })),
|
|
177
|
+
{ name: "deletedAt", ts: "string" },
|
|
178
|
+
{ name: "createdAt", ts: "string" },
|
|
179
|
+
{ name: "updatedAt", ts: "string" },
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
const FIELDS_INTERFACE = entityFields
|
|
183
|
+
.map((f, i, arr) => ` ${f.name}: ${f.ts}${i < arr.length - 1 ? "," : ""}`)
|
|
184
|
+
.join("\n");
|
|
185
|
+
|
|
186
|
+
// 2) Table head (Padrão fixo)
|
|
187
|
+
const TABLE_HEAD = [
|
|
188
|
+
` { id: 'Id', label: 'Id' },`,
|
|
189
|
+
...fields.map((f) => ` { id: '${f.name}', label: '${f.name}' },`),
|
|
190
|
+
` { id: 'DeletedAt', label: 'Ativo?' },`,
|
|
191
|
+
` { id: 'CreatedAt', label: 'Cadastro' },`,
|
|
192
|
+
` { id: 'UpdatedAt', label: 'Atualizado' },`,
|
|
193
|
+
].join("\n");
|
|
194
|
+
|
|
195
|
+
// 3) Table row cells (MODEL)
|
|
196
|
+
const TABLE_ROW_CELLS = [
|
|
197
|
+
` <TableCell>{d.id}</TableCell>`,
|
|
198
|
+
...fields.map((f) => ` <TableCell>{d.${f.modelName}}</TableCell>`),
|
|
199
|
+
` <TableCell>
|
|
200
|
+
<Label color={d.deletedAt ? 'error' : 'success'}>
|
|
201
|
+
{d.deletedAt ? 'Não' : 'Sim'}
|
|
202
|
+
</Label>
|
|
203
|
+
</TableCell>`,
|
|
204
|
+
` <TableCell>{d.createdAt ? dayjs(d.createdAt).format('lll') : ''}</TableCell>`,
|
|
205
|
+
` <TableCell>{d.updatedAt ? dayjs(d.updatedAt).format('lll') : ''}</TableCell>`,
|
|
206
|
+
].join("\n");
|
|
207
|
+
|
|
208
|
+
// 4) Filters (DTO)
|
|
209
|
+
const FILTERS_INITIAL_OBJECT = fields.map((f) => ` ${f.name}: ""`).join(",\n");
|
|
210
|
+
|
|
211
|
+
const FILTERS_FIELDS_JSX = fields
|
|
212
|
+
.map(
|
|
213
|
+
(f) => `
|
|
214
|
+
<TextField
|
|
215
|
+
value={filter.${f.name}}
|
|
216
|
+
onChange={(e) => setFilter(prev => ({ ...prev, ${f.name}: e.target.value }))}
|
|
217
|
+
label="${f.name}"
|
|
218
|
+
autoComplete="off"
|
|
219
|
+
sx={{ mb: 2 }}
|
|
220
|
+
/>`.trim()
|
|
221
|
+
)
|
|
222
|
+
.join("\n\n ");
|
|
223
|
+
|
|
224
|
+
const FILTER_SPREAD_IN_QUERY = fields.map((f) => ` ${f.name}: filter.${f.name},`).join("\n");
|
|
225
|
+
|
|
226
|
+
// 5) Services types (DTO)
|
|
227
|
+
const FILTERS_INTERFACE = fields.map((f) => ` ${f.name}: string;`).join("\n");
|
|
228
|
+
|
|
229
|
+
const CREATE_FIELDS = fields
|
|
230
|
+
.map((f) => ` ${f.name}${f.required ? "" : "?"}: ${tsTypeFor(f.type)};`)
|
|
231
|
+
.join("\n");
|
|
232
|
+
|
|
233
|
+
const UPDATE_FIELDS = fields
|
|
234
|
+
.map((f) => ` ${f.name}${f.required ? "" : "?"}: ${tsTypeFor(f.type)};`)
|
|
235
|
+
.join("\n");
|
|
236
|
+
|
|
237
|
+
// 6) Form fields (DTO) - Field.Text
|
|
238
|
+
function fieldToTsx(f) {
|
|
239
|
+
const isNum = isNumberType(f.type);
|
|
240
|
+
return ` <Grid item md={12} xs={12}>
|
|
241
|
+
<Field.Text fullWidth name="${f.name}" label="${f.name}"${isNum ? ' type="number"' : ""} />
|
|
242
|
+
</Grid>`;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const FORM_FIELDS_CREATE_TSX = fields.map(fieldToTsx).join("\n");
|
|
246
|
+
const FORM_FIELDS_UPDATE_TSX = fields.map(fieldToTsx).join("\n");
|
|
247
|
+
|
|
248
|
+
// 7) Yup + defaults
|
|
249
|
+
function yupBaseByType(f) {
|
|
250
|
+
if (isNumberType(f.type)) return `yup.number().typeError("Número inválido")`;
|
|
251
|
+
if (isBooleanType(f.type)) return `yup.boolean()`;
|
|
252
|
+
if (isDateType(f.type)) return `yup.string()`;
|
|
253
|
+
return `yup.string()`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function yupRule(f, isReq) {
|
|
257
|
+
const base = yupBaseByType(f);
|
|
258
|
+
return ` ${f.name}: ${base}${isReq ? '.required("Campo obrigatório")' : ".optional()"},`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const YUP_CREATE_RULES = fields.map((f) => yupRule(f, !!f.required)).join("\n");
|
|
262
|
+
const YUP_UPDATE_RULES = fields.map((f) => yupRule(f, !!f.required)).join("\n");
|
|
263
|
+
|
|
264
|
+
const FORM_DEFAULTS_CREATE = fields
|
|
265
|
+
.map((f) => ` ${f.name}: ${isNumberType(f.type) ? "undefined" : '""'},`)
|
|
266
|
+
.join("\n");
|
|
267
|
+
|
|
268
|
+
const FORM_DEFAULTS_UPDATE = fields
|
|
269
|
+
.map((f) => ` ${f.name}: undefined,`)
|
|
270
|
+
.join("\n");
|
|
271
|
+
|
|
272
|
+
const FORM_VALUES_UPDATE = fields
|
|
273
|
+
.map((f) => {
|
|
274
|
+
if (isNumberType(f.type)) return ` ${f.name}: selectClickItem?.${f.modelName} ?? undefined,`;
|
|
275
|
+
return ` ${f.name}: selectClickItem?.${f.modelName} ?? "",`;
|
|
276
|
+
})
|
|
277
|
+
.join("\n");
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
PascalName,
|
|
281
|
+
camelName,
|
|
282
|
+
kebabName,
|
|
283
|
+
route,
|
|
284
|
+
FIELDS_INTERFACE,
|
|
285
|
+
FILTERS_INTERFACE,
|
|
286
|
+
CREATE_FIELDS,
|
|
287
|
+
UPDATE_FIELDS,
|
|
288
|
+
TABLE_HEAD,
|
|
289
|
+
TABLE_ROW_CELLS,
|
|
290
|
+
FILTERS_INITIAL_OBJECT,
|
|
291
|
+
FILTERS_FIELDS_JSX,
|
|
292
|
+
FILTER_SPREAD_IN_QUERY,
|
|
293
|
+
FORM_FIELDS_CREATE_TSX,
|
|
294
|
+
FORM_FIELDS_UPDATE_TSX,
|
|
295
|
+
YUP_CREATE_RULES,
|
|
296
|
+
YUP_UPDATE_RULES,
|
|
297
|
+
FORM_DEFAULTS_CREATE,
|
|
298
|
+
FORM_DEFAULTS_UPDATE,
|
|
299
|
+
FORM_VALUES_UPDATE,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function writeOutputs(cwd, baseDir, servicesDir, typesDir, ctx) {
|
|
304
|
+
const force = writeOutputs._force;
|
|
305
|
+
|
|
306
|
+
// services + types
|
|
307
|
+
const outServiceIndex = path.join(cwd, baseDir, servicesDir, ctx.kebabName, "index.ts");
|
|
308
|
+
const outServiceTypes = path.join(cwd, baseDir, servicesDir, ctx.kebabName, "types.ts");
|
|
309
|
+
const outEntityType = path.join(cwd, baseDir, typesDir, `i-${ctx.kebabName}.ts`);
|
|
310
|
+
|
|
311
|
+
writeFile(outServiceIndex, renderTemplate(TPL_SERVICE_INDEX, ctx), force);
|
|
312
|
+
writeFile(outServiceTypes, renderTemplate(TPL_SERVICE_TYPES, ctx), force);
|
|
313
|
+
writeFile(outEntityType, renderTemplate(TPL_ENTITY_IFACE, ctx), force);
|
|
314
|
+
|
|
315
|
+
// pages
|
|
316
|
+
const pageBase = path.join(cwd, baseDir, "pages", ctx.kebabName);
|
|
317
|
+
|
|
318
|
+
writeFile(path.join(pageBase, "index.tsx"), renderTemplate(TPL_PAGE_INDEX, ctx), force);
|
|
319
|
+
writeFile(path.join(pageBase, "consts", "index.ts"), renderTemplate(TPL_CONSTS, ctx), force);
|
|
320
|
+
writeFile(path.join(pageBase, "components", `${ctx.kebabName}-filters-component.tsx`), renderTemplate(TPL_FILTERS_COMPONENT, ctx), force);
|
|
321
|
+
|
|
322
|
+
writeFile(path.join(pageBase, "create", "index.tsx"), renderTemplate(TPL_CREATE, ctx), force);
|
|
323
|
+
writeFile(path.join(pageBase, "update", "index.tsx"), renderTemplate(TPL_UPDATE, ctx), force);
|
|
324
|
+
writeFile(path.join(pageBase, "delete", "index.tsx"), renderTemplate(TPL_DELETE, ctx), force);
|
|
325
|
+
writeFile(path.join(pageBase, "restore", "index.tsx"), renderTemplate(TPL_RESTORE, ctx), force);
|
|
326
|
+
|
|
327
|
+
writeFile(path.join(pageBase, "hooks", `use-${ctx.kebabName}-index.ts`), renderTemplate(TPL_HOOK_INDEX, ctx), force);
|
|
328
|
+
writeFile(path.join(pageBase, "hooks", `use-${ctx.kebabName}-create.ts`), renderTemplate(TPL_HOOK_CREATE, ctx), force);
|
|
329
|
+
writeFile(path.join(pageBase, "hooks", `use-${ctx.kebabName}-update.ts`), renderTemplate(TPL_HOOK_UPDATE, ctx), force);
|
|
330
|
+
writeFile(path.join(pageBase, "hooks", `use-${ctx.kebabName}-delete.ts`), renderTemplate(TPL_HOOK_DELETE, ctx), force);
|
|
331
|
+
writeFile(path.join(pageBase, "hooks", `use-${ctx.kebabName}-restore.ts`), renderTemplate(TPL_HOOK_RESTORE, ctx), force);
|
|
332
|
+
|
|
333
|
+
console.log(` → ${path.relative(cwd, outServiceIndex)}`);
|
|
334
|
+
console.log(` → ${path.relative(cwd, outServiceTypes)}`);
|
|
335
|
+
console.log(` → ${path.relative(cwd, outEntityType)}`);
|
|
336
|
+
console.log(` → ${path.relative(cwd, path.join(pageBase, "index.tsx"))}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// ---------- main ----------
|
|
340
|
+
async function main() {
|
|
341
|
+
const argMap = getArgMap(process.argv);
|
|
342
|
+
|
|
343
|
+
const defaults = {
|
|
344
|
+
baseDir: "src",
|
|
345
|
+
servicesDir: "services/system",
|
|
346
|
+
typesDir: "types/system",
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const force = String(argMap.force || "false") === "true";
|
|
350
|
+
writeOutputs._force = force;
|
|
351
|
+
|
|
352
|
+
const baseDir = argMap.baseDir || defaults.baseDir;
|
|
353
|
+
const servicesDir = argMap.servicesDir || defaults.servicesDir;
|
|
354
|
+
const typesDir = argMap.typesDir || defaults.typesDir;
|
|
355
|
+
|
|
356
|
+
const cwd = process.cwd();
|
|
357
|
+
|
|
358
|
+
const name = argMap.name || "";
|
|
359
|
+
const fieldsRaw = argMap.fields || "";
|
|
360
|
+
|
|
361
|
+
if (!name || !fieldsRaw) {
|
|
362
|
+
console.error(
|
|
363
|
+
"Uso:\n" +
|
|
364
|
+
' npm run generate -- --name=Bank --fields="Code:string:true,Name:string:true" [--force=true]\n'
|
|
365
|
+
);
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const fields = parseFields(fieldsRaw);
|
|
370
|
+
|
|
371
|
+
const ctx = buildCtxFromFields(name, fields);
|
|
372
|
+
writeOutputs(cwd, baseDir, servicesDir, typesDir, ctx);
|
|
373
|
+
|
|
374
|
+
console.log("\n✅ Gerado com sucesso (novo padrão).");
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jean.anjos/coresuit-gen-crud",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Gerador de services e types (React/TS) no padrão CoreSuit",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"coresuit-react-gen": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"templates",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=16"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Button, TextField, Typography } from "@mui/material";
|
|
2
|
+
|
|
3
|
+
import DrawerFiltersComponent from "src/components/drawer-filters";
|
|
4
|
+
|
|
5
|
+
import { InitialFilter{{PascalName}} } from "../consts";
|
|
6
|
+
|
|
7
|
+
import type use{{PascalName}}Index from "../hooks/use-{{kebabName}}-index";
|
|
8
|
+
|
|
9
|
+
interface IProps {
|
|
10
|
+
hook{{PascalName}}Index: ReturnType<typeof use{{PascalName}}Index>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function {{PascalName}}FiltersComponent({ hook{{PascalName}}Index }: IProps) {
|
|
14
|
+
const { isFilterApplied, openDrawerFilters, queryClient, setFilter, filter } = hook{{PascalName}}Index;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<DrawerFiltersComponent
|
|
18
|
+
canReset={!isFilterApplied}
|
|
19
|
+
open={openDrawerFilters.value}
|
|
20
|
+
onClose={openDrawerFilters.onFalse}
|
|
21
|
+
onOpen={openDrawerFilters.onTrue}
|
|
22
|
+
resetFilters={() => {
|
|
23
|
+
setFilter(InitialFilter{{PascalName}});
|
|
24
|
+
setTimeout(() => {
|
|
25
|
+
queryClient.invalidateQueries({ queryKey: ["data{{PascalName}}"] });
|
|
26
|
+
}, 0);
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
<>
|
|
30
|
+
<Typography variant="subtitle2" sx={{ mb: 1 }}>
|
|
31
|
+
Filtros de Pesquisas
|
|
32
|
+
</Typography>
|
|
33
|
+
|
|
34
|
+
{{FILTERS_FIELDS_JSX}}
|
|
35
|
+
|
|
36
|
+
<Button
|
|
37
|
+
variant="outlined"
|
|
38
|
+
onClick={() => {
|
|
39
|
+
queryClient.invalidateQueries({ queryKey: ["data{{PascalName}}"] });
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
Aplicar Filtros
|
|
43
|
+
</Button>
|
|
44
|
+
</>
|
|
45
|
+
</DrawerFiltersComponent>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { I{{PascalName}}Get } from "src/services/system/{{kebabName}}/types";
|
|
2
|
+
|
|
3
|
+
const tableHead{{PascalName}} = [
|
|
4
|
+
{{TABLE_HEAD}}
|
|
5
|
+
{ id: "", label: "Ações" },
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
const InitialFilter{{PascalName}}: I{{PascalName}}Get = {
|
|
9
|
+
{{FILTERS_INITIAL_OBJECT}}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { tableHead{{PascalName}}, InitialFilter{{PascalName}} };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { LoadingButton } from "@mui/lab";
|
|
2
|
+
import { Grid, Stack, Button, Typography } from "@mui/material";
|
|
3
|
+
|
|
4
|
+
import { Form, Field } from "src/components/hook-form";
|
|
5
|
+
import DrawerFiltersComponent from "src/components/drawer-filters";
|
|
6
|
+
|
|
7
|
+
import type use{{PascalName}}Create from "../hooks/use-{{kebabName}}-create";
|
|
8
|
+
|
|
9
|
+
interface IProps {
|
|
10
|
+
hook{{PascalName}}Create: ReturnType<typeof use{{PascalName}}Create>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function {{PascalName}}Create({ hook{{PascalName}}Create }: IProps) {
|
|
14
|
+
const { handleCreate, methods, openCreate, status } = hook{{PascalName}}Create;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<DrawerFiltersComponent title="Cadastrar {{PascalName}}" open={openCreate.value} onClose={openCreate.onFalse} width={500}>
|
|
18
|
+
<>
|
|
19
|
+
<Typography variant="subtitle2">Preencha as informações</Typography>
|
|
20
|
+
|
|
21
|
+
<Form methods={methods} onSubmit={handleCreate}>
|
|
22
|
+
<Grid spacing={2} sx={{ mt: 2 }} container>
|
|
23
|
+
{{FORM_FIELDS_CREATE_TSX}}
|
|
24
|
+
</Grid>
|
|
25
|
+
<Stack spacing={2} alignItems="end" sx={{ mt: 3 }}>
|
|
26
|
+
<Stack spacing={2} flexDirection="row">
|
|
27
|
+
<Button variant="outlined" onClick={openCreate.onFalse}>
|
|
28
|
+
Cancelar
|
|
29
|
+
</Button>
|
|
30
|
+
<LoadingButton variant="outlined" type="submit" loading={status === "pending"} color="success">
|
|
31
|
+
Salvar
|
|
32
|
+
</LoadingButton>
|
|
33
|
+
</Stack>
|
|
34
|
+
</Stack>
|
|
35
|
+
</Form>
|
|
36
|
+
</>
|
|
37
|
+
</DrawerFiltersComponent>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { LoadingButton } from "@mui/lab";
|
|
2
|
+
import { Button, Dialog, Typography, DialogTitle, DialogActions, DialogContent } from "@mui/material";
|
|
3
|
+
|
|
4
|
+
import type use{{PascalName}}Delete from "../hooks/use-{{kebabName}}-delete";
|
|
5
|
+
|
|
6
|
+
interface IProps {
|
|
7
|
+
hook{{PascalName}}Delete: ReturnType<typeof use{{PascalName}}Delete>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function {{PascalName}}Delete({ hook{{PascalName}}Delete }: IProps) {
|
|
11
|
+
const { openDelete, selectClickItem, handleDelete, status } = hook{{PascalName}}Delete;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<Dialog open={openDelete.value} onClose={openDelete.onFalse} maxWidth="xs" fullWidth>
|
|
15
|
+
<DialogTitle>Excluir {{PascalName}}</DialogTitle>
|
|
16
|
+
<DialogContent dividers>
|
|
17
|
+
<Typography>
|
|
18
|
+
Você realmente deseja <strong>desativar</strong> <strong>#{selectClickItem?.id}</strong>?
|
|
19
|
+
</Typography>
|
|
20
|
+
</DialogContent>
|
|
21
|
+
<DialogActions>
|
|
22
|
+
<Button onClick={openDelete.onFalse} variant="outlined">
|
|
23
|
+
Cancelar
|
|
24
|
+
</Button>
|
|
25
|
+
<LoadingButton variant="contained" color="error" loading={status === "pending"} onClick={handleDelete}>
|
|
26
|
+
Confirmar
|
|
27
|
+
</LoadingButton>
|
|
28
|
+
</DialogActions>
|
|
29
|
+
</Dialog>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { I{{PascalName}}Create } from "src/services/system/{{kebabName}}/types";
|
|
2
|
+
|
|
3
|
+
import * as yup from "yup";
|
|
4
|
+
import { useForm } from "react-hook-form";
|
|
5
|
+
import { enqueueSnackbar } from "notistack";
|
|
6
|
+
import { yupResolver } from "@hookform/resolvers/yup";
|
|
7
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
8
|
+
|
|
9
|
+
import { useBoolean } from "src/hooks/use-boolean";
|
|
10
|
+
|
|
11
|
+
import handleApiDataError from "src/utils/handle-api-data-error";
|
|
12
|
+
|
|
13
|
+
import { {{camelName}}Service } from "src/services/system/{{kebabName}}";
|
|
14
|
+
|
|
15
|
+
export default function use{{PascalName}}Create() {
|
|
16
|
+
const api = {{camelName}}Service();
|
|
17
|
+
|
|
18
|
+
const validateForm = yup.object({
|
|
19
|
+
{{YUP_CREATE_RULES}}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const openCreate = useBoolean();
|
|
23
|
+
const queryClient = useQueryClient();
|
|
24
|
+
|
|
25
|
+
const methods = useForm<I{{PascalName}}Create>({
|
|
26
|
+
resolver: yupResolver(validateForm),
|
|
27
|
+
defaultValues: {
|
|
28
|
+
{{FORM_DEFAULTS_CREATE}}
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const { mutate, status } = useMutation({
|
|
33
|
+
mutationFn: api.{{camelName}}Create,
|
|
34
|
+
onSuccess: () => {
|
|
35
|
+
enqueueSnackbar("Cadastrado com sucesso");
|
|
36
|
+
queryClient.invalidateQueries({ queryKey: ["data{{PascalName}}"] });
|
|
37
|
+
openCreate.onFalse();
|
|
38
|
+
methods.reset();
|
|
39
|
+
},
|
|
40
|
+
onError: (err) => {
|
|
41
|
+
handleApiDataError(err, enqueueSnackbar);
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const handleCreate = methods.handleSubmit((data) => {
|
|
46
|
+
const params: I{{PascalName}}Create = { ...data };
|
|
47
|
+
mutate(params);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
status,
|
|
52
|
+
methods,
|
|
53
|
+
handleCreate,
|
|
54
|
+
openCreate,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { I{{PascalName}} } from "src/types/system/i-{{kebabName}}";
|
|
2
|
+
import type { I{{PascalName}}Delete } from "src/services/system/{{kebabName}}/types";
|
|
3
|
+
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { enqueueSnackbar } from "notistack";
|
|
6
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
7
|
+
|
|
8
|
+
import { useBoolean } from "src/hooks/use-boolean";
|
|
9
|
+
|
|
10
|
+
import handleApiDataError from "src/utils/handle-api-data-error";
|
|
11
|
+
|
|
12
|
+
import { {{camelName}}Service } from "src/services/system/{{kebabName}}";
|
|
13
|
+
|
|
14
|
+
export default function use{{PascalName}}Delete() {
|
|
15
|
+
const api = {{camelName}}Service();
|
|
16
|
+
|
|
17
|
+
const [selectClickItem, setSelectClickItem] = useState<I{{PascalName}}>();
|
|
18
|
+
|
|
19
|
+
const openDelete = useBoolean();
|
|
20
|
+
const queryClient = useQueryClient();
|
|
21
|
+
|
|
22
|
+
const { mutate, status } = useMutation({
|
|
23
|
+
mutationFn: api.{{camelName}}Delete,
|
|
24
|
+
onSuccess: () => {
|
|
25
|
+
enqueueSnackbar("Deletado com sucesso");
|
|
26
|
+
queryClient.invalidateQueries({ queryKey: ["data{{PascalName}}"] });
|
|
27
|
+
openDelete.onFalse();
|
|
28
|
+
},
|
|
29
|
+
onError: (err) => {
|
|
30
|
+
handleApiDataError(err, enqueueSnackbar);
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const handleDelete = () => {
|
|
35
|
+
const params: I{{PascalName}}Delete = { id: selectClickItem?.id.toString()! };
|
|
36
|
+
mutate(params);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
status,
|
|
41
|
+
handleDelete,
|
|
42
|
+
openDelete,
|
|
43
|
+
selectClickItem,
|
|
44
|
+
setSelectClickItem,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { I{{PascalName}}Get } from "src/services/system/{{kebabName}}/types";
|
|
2
|
+
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { isEqual } from "lodash";
|
|
5
|
+
import { enqueueSnackbar } from "notistack";
|
|
6
|
+
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
7
|
+
|
|
8
|
+
import useTable from "src/hooks/use-table";
|
|
9
|
+
import { useBoolean } from "src/hooks/use-boolean";
|
|
10
|
+
|
|
11
|
+
import handleApiDataError from "src/utils/handle-api-data-error";
|
|
12
|
+
|
|
13
|
+
import { {{camelName}}Service } from "src/services/system/{{kebabName}}";
|
|
14
|
+
|
|
15
|
+
import { InitialFilter{{PascalName}} } from "../consts";
|
|
16
|
+
|
|
17
|
+
export default function use{{PascalName}}Index() {
|
|
18
|
+
const api = {{camelName}}Service();
|
|
19
|
+
|
|
20
|
+
const [filter, setFilter] = useState<I{{PascalName}}Get>(InitialFilter{{PascalName}});
|
|
21
|
+
|
|
22
|
+
const table = useTable();
|
|
23
|
+
const openDrawerFilters = useBoolean();
|
|
24
|
+
const queryClient = useQueryClient();
|
|
25
|
+
|
|
26
|
+
const { data: data{{PascalName}}, status: status{{PascalName}} } = useQuery({
|
|
27
|
+
queryKey: ["data{{PascalName}}", table, filter],
|
|
28
|
+
queryFn: async () => {
|
|
29
|
+
try {
|
|
30
|
+
const params: I{{PascalName}}Get = {
|
|
31
|
+
Direction: table.order,
|
|
32
|
+
PageNumber: table.page,
|
|
33
|
+
PageSize: table.pageSize,
|
|
34
|
+
SortBy: table.orderBy,
|
|
35
|
+
{{FILTER_SPREAD_IN_QUERY}}
|
|
36
|
+
};
|
|
37
|
+
const response = await api.{{camelName}}Get(params);
|
|
38
|
+
return response;
|
|
39
|
+
} catch (error) {
|
|
40
|
+
handleApiDataError(error, enqueueSnackbar);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const isFilterApplied = !!isEqual(filter, InitialFilter{{PascalName}});
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
table,
|
|
50
|
+
queryClient,
|
|
51
|
+
data{{PascalName}},
|
|
52
|
+
status{{PascalName}},
|
|
53
|
+
filter,
|
|
54
|
+
openDrawerFilters,
|
|
55
|
+
setFilter,
|
|
56
|
+
isFilterApplied,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { I{{PascalName}} } from "src/types/system/i-{{kebabName}}";
|
|
2
|
+
import type { I{{PascalName}}Restore } from "src/services/system/{{kebabName}}/types";
|
|
3
|
+
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { enqueueSnackbar } from "notistack";
|
|
6
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
7
|
+
|
|
8
|
+
import { useBoolean } from "src/hooks/use-boolean";
|
|
9
|
+
|
|
10
|
+
import handleApiDataError from "src/utils/handle-api-data-error";
|
|
11
|
+
|
|
12
|
+
import { {{camelName}}Service } from "src/services/system/{{kebabName}}";
|
|
13
|
+
|
|
14
|
+
export default function use{{PascalName}}Restore() {
|
|
15
|
+
const api = {{camelName}}Service();
|
|
16
|
+
|
|
17
|
+
const [selectClickItem, setSelectClickItem] = useState<I{{PascalName}}>();
|
|
18
|
+
|
|
19
|
+
const openRestore = useBoolean();
|
|
20
|
+
const queryClient = useQueryClient();
|
|
21
|
+
|
|
22
|
+
const { mutate, status } = useMutation({
|
|
23
|
+
mutationFn: api.{{camelName}}Restore,
|
|
24
|
+
onSuccess: () => {
|
|
25
|
+
enqueueSnackbar("Restaurado com sucesso");
|
|
26
|
+
queryClient.invalidateQueries({ queryKey: ["data{{PascalName}}"] });
|
|
27
|
+
openRestore.onFalse();
|
|
28
|
+
},
|
|
29
|
+
onError: (err) => {
|
|
30
|
+
handleApiDataError(err, enqueueSnackbar);
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const handleRestore = () => {
|
|
35
|
+
const params: I{{PascalName}}Restore = { id: selectClickItem?.id.toString()! };
|
|
36
|
+
mutate(params);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
status,
|
|
41
|
+
handleRestore,
|
|
42
|
+
openRestore,
|
|
43
|
+
selectClickItem,
|
|
44
|
+
setSelectClickItem,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { I{{PascalName}} } from "src/types/system/i-{{kebabName}}";
|
|
2
|
+
import type { I{{PascalName}}Update } from "src/services/system/{{kebabName}}/types";
|
|
3
|
+
|
|
4
|
+
import * as yup from "yup";
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { useForm } from "react-hook-form";
|
|
7
|
+
import { enqueueSnackbar } from "notistack";
|
|
8
|
+
import { yupResolver } from "@hookform/resolvers/yup";
|
|
9
|
+
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
10
|
+
|
|
11
|
+
import { useBoolean } from "src/hooks/use-boolean";
|
|
12
|
+
|
|
13
|
+
import handleApiDataError from "src/utils/handle-api-data-error";
|
|
14
|
+
|
|
15
|
+
import { {{camelName}}Service } from "src/services/system/{{kebabName}}";
|
|
16
|
+
|
|
17
|
+
export default function use{{PascalName}}Update() {
|
|
18
|
+
const api = {{camelName}}Service();
|
|
19
|
+
|
|
20
|
+
const [selectClickItem, setSelectClickItem] = useState<I{{PascalName}}>();
|
|
21
|
+
|
|
22
|
+
const validateForm = yup.object({
|
|
23
|
+
{{YUP_UPDATE_RULES}}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const openUpdate = useBoolean();
|
|
27
|
+
const queryClient = useQueryClient();
|
|
28
|
+
|
|
29
|
+
const methods = useForm<Omit<I{{PascalName}}Update, "id">>({
|
|
30
|
+
resolver: yupResolver(validateForm),
|
|
31
|
+
defaultValues: {
|
|
32
|
+
{{FORM_DEFAULTS_UPDATE}}
|
|
33
|
+
},
|
|
34
|
+
values: {
|
|
35
|
+
{{FORM_VALUES_UPDATE}}
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const { mutate, status } = useMutation({
|
|
40
|
+
mutationFn: api.{{camelName}}Update,
|
|
41
|
+
onSuccess: () => {
|
|
42
|
+
enqueueSnackbar("Editado com sucesso");
|
|
43
|
+
queryClient.invalidateQueries({ queryKey: ["data{{PascalName}}"] });
|
|
44
|
+
openUpdate.onFalse();
|
|
45
|
+
methods.reset();
|
|
46
|
+
},
|
|
47
|
+
onError: (err) => {
|
|
48
|
+
handleApiDataError(err, enqueueSnackbar);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const handleUpdate = methods.handleSubmit((data) => {
|
|
53
|
+
const params: I{{PascalName}}Update = {
|
|
54
|
+
id: selectClickItem?.id.toString()!,
|
|
55
|
+
...data,
|
|
56
|
+
};
|
|
57
|
+
mutate(params);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
status,
|
|
62
|
+
methods,
|
|
63
|
+
handleUpdate,
|
|
64
|
+
openUpdate,
|
|
65
|
+
selectClickItem,
|
|
66
|
+
setSelectClickItem,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import dayjs from "dayjs";
|
|
2
|
+
|
|
3
|
+
import { Box, Card, Stack, Button, Tooltip, TableRow, TableCell, ButtonGroup } from "@mui/material";
|
|
4
|
+
|
|
5
|
+
import { Label } from "src/components/label";
|
|
6
|
+
import PageComponent from "src/components/page";
|
|
7
|
+
import { Iconify } from "src/components/iconify";
|
|
8
|
+
import TableComponent from "src/components/table-basic";
|
|
9
|
+
import { CustomBreadcrumbs } from "src/components/custom-breadcrumbs";
|
|
10
|
+
|
|
11
|
+
import {{PascalName}}Create from "./create";
|
|
12
|
+
import {{PascalName}}Delete from "./delete";
|
|
13
|
+
import {{PascalName}}Update from "./update";
|
|
14
|
+
import {{PascalName}}Restore from "./restore";
|
|
15
|
+
import { tableHead{{PascalName}} } from "./consts";
|
|
16
|
+
import use{{PascalName}}Index from "./hooks/use-{{kebabName}}-index";
|
|
17
|
+
import use{{PascalName}}Create from "./hooks/use-{{kebabName}}-create";
|
|
18
|
+
import use{{PascalName}}Delete from "./hooks/use-{{kebabName}}-delete";
|
|
19
|
+
import use{{PascalName}}Update from "./hooks/use-{{kebabName}}-update";
|
|
20
|
+
import use{{PascalName}}Restore from "./hooks/use-{{kebabName}}-restore";
|
|
21
|
+
import {{PascalName}}FiltersComponent from "./components/{{kebabName}}-filters-component";
|
|
22
|
+
|
|
23
|
+
export default function {{PascalName}}IndexPage() {
|
|
24
|
+
const hook{{PascalName}}Index = use{{PascalName}}Index();
|
|
25
|
+
const hook{{PascalName}}Create = use{{PascalName}}Create();
|
|
26
|
+
const hook{{PascalName}}Delete = use{{PascalName}}Delete();
|
|
27
|
+
const hook{{PascalName}}Restore = use{{PascalName}}Restore();
|
|
28
|
+
const hook{{PascalName}}Update = use{{PascalName}}Update();
|
|
29
|
+
|
|
30
|
+
const { table, status{{PascalName}}, data{{PascalName}} } = hook{{PascalName}}Index;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<PageComponent>
|
|
34
|
+
<>
|
|
35
|
+
<CustomBreadcrumbs
|
|
36
|
+
heading="{{PascalName}}"
|
|
37
|
+
links={[{ name: "{{PascalName}}" }, { name: "lista" }]}
|
|
38
|
+
action={
|
|
39
|
+
<Button
|
|
40
|
+
onClick={hook{{PascalName}}Create.openCreate.onTrue}
|
|
41
|
+
startIcon={<Iconify icon="material-symbols:add" />}
|
|
42
|
+
variant="outlined"
|
|
43
|
+
>
|
|
44
|
+
Novo (a)
|
|
45
|
+
</Button>
|
|
46
|
+
}
|
|
47
|
+
sx={{ mb: { xs: 3, md: 5 } }}
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
<Card>
|
|
51
|
+
<TableComponent
|
|
52
|
+
headLabel={tableHead{{PascalName}}}
|
|
53
|
+
table={table}
|
|
54
|
+
loading={status{{PascalName}} === "pending"}
|
|
55
|
+
totalCount={data{{PascalName}}?.totalCount ?? 0}
|
|
56
|
+
order={table.order}
|
|
57
|
+
orderBy={table.orderBy}
|
|
58
|
+
onSort={table.onSort}
|
|
59
|
+
action={
|
|
60
|
+
<{{PascalName}}FiltersComponent hook{{PascalName}}Index={hook{{PascalName}}Index} />
|
|
61
|
+
}
|
|
62
|
+
>
|
|
63
|
+
<>
|
|
64
|
+
{data{{PascalName}}?.data.map((d, k) => (
|
|
65
|
+
<TableRow hover key={k}>
|
|
66
|
+
{{TABLE_ROW_CELLS}}
|
|
67
|
+
<TableCell>
|
|
68
|
+
<ButtonGroup color="inherit" size="small">
|
|
69
|
+
<Tooltip title="Editar dados">
|
|
70
|
+
<Button
|
|
71
|
+
onClick={() => {
|
|
72
|
+
hook{{PascalName}}Update.openUpdate.onTrue();
|
|
73
|
+
hook{{PascalName}}Update.setSelectClickItem(d);
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
<Iconify icon="material-symbols:edit-document-outline" />
|
|
77
|
+
</Button>
|
|
78
|
+
</Tooltip>
|
|
79
|
+
<Tooltip title={d.deletedAt ? "Ativar" : "Desativar"}>
|
|
80
|
+
<Button
|
|
81
|
+
onClick={() => {
|
|
82
|
+
if (d.deletedAt) {
|
|
83
|
+
hook{{PascalName}}Restore.setSelectClickItem(d);
|
|
84
|
+
hook{{PascalName}}Restore.openRestore.onTrue();
|
|
85
|
+
} else {
|
|
86
|
+
hook{{PascalName}}Delete.setSelectClickItem(d);
|
|
87
|
+
hook{{PascalName}}Delete.openDelete.onTrue();
|
|
88
|
+
}
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
<Iconify
|
|
92
|
+
icon={
|
|
93
|
+
d.deletedAt
|
|
94
|
+
? "material-symbols:upload"
|
|
95
|
+
: "material-symbols:delete-outline-sharp"
|
|
96
|
+
}
|
|
97
|
+
/>
|
|
98
|
+
</Button>
|
|
99
|
+
</Tooltip>
|
|
100
|
+
</ButtonGroup>
|
|
101
|
+
</TableCell>
|
|
102
|
+
</TableRow>
|
|
103
|
+
))}
|
|
104
|
+
</>
|
|
105
|
+
</TableComponent>
|
|
106
|
+
</Card>
|
|
107
|
+
|
|
108
|
+
<Box sx={{ mb: 10 }} />
|
|
109
|
+
|
|
110
|
+
<{{PascalName}}Create hook{{PascalName}}Create={hook{{PascalName}}Create} />
|
|
111
|
+
<{{PascalName}}Delete hook{{PascalName}}Delete={hook{{PascalName}}Delete} />
|
|
112
|
+
<{{PascalName}}Restore hook{{PascalName}}Restore={hook{{PascalName}}Restore} />
|
|
113
|
+
<{{PascalName}}Update hook{{PascalName}}Update={hook{{PascalName}}Update} />
|
|
114
|
+
</>
|
|
115
|
+
</PageComponent>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { LoadingButton } from "@mui/lab";
|
|
2
|
+
import { Dialog, Button, Typography, DialogTitle, DialogContent, DialogActions } from "@mui/material";
|
|
3
|
+
|
|
4
|
+
import type use{{PascalName}}Restore from "../hooks/use-{{kebabName}}-restore";
|
|
5
|
+
|
|
6
|
+
interface IProps {
|
|
7
|
+
hook{{PascalName}}Restore: ReturnType<typeof use{{PascalName}}Restore>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default function {{PascalName}}Restore({ hook{{PascalName}}Restore }: IProps) {
|
|
11
|
+
const { openRestore, selectClickItem, status, handleRestore } = hook{{PascalName}}Restore;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<Dialog open={openRestore.value} onClose={openRestore.onFalse} maxWidth="xs" fullWidth>
|
|
15
|
+
<DialogTitle>Restaurar {{PascalName}}</DialogTitle>
|
|
16
|
+
<DialogContent dividers>
|
|
17
|
+
<Typography>
|
|
18
|
+
Você realmente deseja <strong>restaurar</strong> <strong>#{selectClickItem?.id}</strong>?
|
|
19
|
+
</Typography>
|
|
20
|
+
</DialogContent>
|
|
21
|
+
<DialogActions>
|
|
22
|
+
<Button onClick={openRestore.onFalse} variant="outlined">
|
|
23
|
+
Cancelar
|
|
24
|
+
</Button>
|
|
25
|
+
<LoadingButton variant="contained" color="success" loading={status === "pending"} onClick={handleRestore}>
|
|
26
|
+
Confirmar
|
|
27
|
+
</LoadingButton>
|
|
28
|
+
</DialogActions>
|
|
29
|
+
</Dialog>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { LoadingButton } from "@mui/lab";
|
|
2
|
+
import { Grid, Stack, Button, Typography } from "@mui/material";
|
|
3
|
+
|
|
4
|
+
import { Form, Field } from "src/components/hook-form";
|
|
5
|
+
import DrawerFiltersComponent from "src/components/drawer-filters";
|
|
6
|
+
|
|
7
|
+
import type use{{PascalName}}Update from "../hooks/use-{{kebabName}}-update";
|
|
8
|
+
|
|
9
|
+
interface IProps {
|
|
10
|
+
hook{{PascalName}}Update: ReturnType<typeof use{{PascalName}}Update>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export default function {{PascalName}}Update({ hook{{PascalName}}Update }: IProps) {
|
|
14
|
+
const { handleUpdate, methods, openUpdate, status } = hook{{PascalName}}Update;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<DrawerFiltersComponent title="Editar {{PascalName}}" open={openUpdate.value} onClose={openUpdate.onFalse} width={500}>
|
|
18
|
+
<>
|
|
19
|
+
<Typography variant="subtitle2">Atualize as informações</Typography>
|
|
20
|
+
|
|
21
|
+
<Form methods={methods} onSubmit={handleUpdate}>
|
|
22
|
+
<Grid spacing={2} sx={{ mt: 2 }} container>
|
|
23
|
+
{{FORM_FIELDS_UPDATE_TSX}}
|
|
24
|
+
</Grid>
|
|
25
|
+
<Stack spacing={2} alignItems="end" sx={{ mt: 3 }}>
|
|
26
|
+
<Stack spacing={2} flexDirection="row">
|
|
27
|
+
<Button variant="outlined" onClick={openUpdate.onFalse}>
|
|
28
|
+
Cancelar
|
|
29
|
+
</Button>
|
|
30
|
+
<LoadingButton variant="outlined" type="submit" loading={status === "pending"} color="success">
|
|
31
|
+
Salvar
|
|
32
|
+
</LoadingButton>
|
|
33
|
+
</Stack>
|
|
34
|
+
</Stack>
|
|
35
|
+
</Form>
|
|
36
|
+
</>
|
|
37
|
+
</DrawerFiltersComponent>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { I{{PascalName}} } from "src/types/system/i-{{kebabName}}";
|
|
2
|
+
import type { IPagination } from "src/types/default/i-pagination";
|
|
3
|
+
|
|
4
|
+
import { API_BASE } from "src/services/api";
|
|
5
|
+
|
|
6
|
+
import type { I{{PascalName}}Get, I{{PascalName}}Create, I{{PascalName}}Update, I{{PascalName}}Delete, I{{PascalName}}GetById, I{{PascalName}}Restore } from "./types";
|
|
7
|
+
|
|
8
|
+
export const {{camelName}}Service = () => ({
|
|
9
|
+
{{camelName}}Get: async (params: I{{PascalName}}Get): Promise<IPagination<I{{PascalName}}>> => {
|
|
10
|
+
const response = await API_BASE.get(`{{route}}`, { params });
|
|
11
|
+
return response.data;
|
|
12
|
+
},
|
|
13
|
+
{{camelName}}Create: async (params: I{{PascalName}}Create): Promise<void> => {
|
|
14
|
+
const response = await API_BASE.post(`{{route}}`, params);
|
|
15
|
+
return response.data;
|
|
16
|
+
},
|
|
17
|
+
{{camelName}}GetById: async (params: I{{PascalName}}GetById): Promise<I{{PascalName}}> => {
|
|
18
|
+
const response = await API_BASE.get(`{{route}}/${params.id}`);
|
|
19
|
+
return response.data;
|
|
20
|
+
},
|
|
21
|
+
{{camelName}}Update: async (params: I{{PascalName}}Update): Promise<void> => {
|
|
22
|
+
const response = await API_BASE.put(`{{route}}/${params.id}`);
|
|
23
|
+
return response.data;
|
|
24
|
+
},
|
|
25
|
+
{{camelName}}Delete: async (params: I{{PascalName}}Delete): Promise<void> => {
|
|
26
|
+
const response = await API_BASE.delete(`{{route}}/${params.id}`);
|
|
27
|
+
return response.data;
|
|
28
|
+
},
|
|
29
|
+
{{camelName}}Restore: async (params: I{{PascalName}}Restore): Promise<void> => {
|
|
30
|
+
const response = await API_BASE.put(`{{route}}/${params.id}`);
|
|
31
|
+
return response.data;
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { IPaginationRequest } from "src/types/default/i-pagination";
|
|
2
|
+
|
|
3
|
+
interface I{{PascalName}}Get extends IPaginationRequest {
|
|
4
|
+
{{FILTERS_INTERFACE}}
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface I{{PascalName}}Create {
|
|
8
|
+
{{CREATE_FIELDS}}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface I{{PascalName}}GetById {
|
|
12
|
+
id: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface I{{PascalName}}Update {
|
|
16
|
+
id: string;
|
|
17
|
+
{{UPDATE_FIELDS}}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface I{{PascalName}}Delete {
|
|
21
|
+
id: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface I{{PascalName}}Restore {
|
|
25
|
+
id: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type {
|
|
29
|
+
I{{PascalName}}Get,
|
|
30
|
+
I{{PascalName}}Create,
|
|
31
|
+
I{{PascalName}}Update,
|
|
32
|
+
I{{PascalName}}Delete,
|
|
33
|
+
I{{PascalName}}GetById,
|
|
34
|
+
I{{PascalName}}Restore,
|
|
35
|
+
}
|