@rglabs/butterfly 2.0.1 → 2.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/.claude/commands/docs-page-layout.md +12 -0
- package/CLAUDE.md +167 -1
- package/README.md +2 -22
- package/dist/commands/setup.js +15 -1
- package/dist/commands/view-report.d.ts +8 -0
- package/dist/commands/view-report.js +89 -0
- package/dist/index.js +9 -1
- package/docs/IMPLEMENTATION_PROMPT.md +252 -0
- package/docs/butterfly_upload_api_docs-2.md +255 -0
- package/docs/objects/listing-query.md +78 -0
- package/docs/pdf-generation.md +376 -0
- package/package.json +1 -1
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Read the page layout documentation from docs/page-layout.md.
|
|
2
|
+
|
|
3
|
+
Provide guidance on organizing object add/edit page layouts including:
|
|
4
|
+
- Creating and managing object_tabs (groups/tabs)
|
|
5
|
+
- Creating object_sections (higher-level grouping)
|
|
6
|
+
- Assigning fields to tabs via edit_place_no
|
|
7
|
+
- Field positioning (edit_position, edit_order_no)
|
|
8
|
+
- Column sizes (left_column_size, column_size)
|
|
9
|
+
- Bulk layout ordering via API or butterfly-cli layout command
|
|
10
|
+
- section_title for visual section headers within tabs
|
|
11
|
+
|
|
12
|
+
Focus on the specific layout task the user asks about, or provide an overview if no specific task is mentioned.
|
package/CLAUDE.md
CHANGED
|
@@ -109,6 +109,46 @@ See [docs/translations.md](docs/translations.md) for full documentation.
|
|
|
109
109
|
|
|
110
110
|
See [docs/twig-helpers.md](docs/twig-helpers.md) for full documentation.
|
|
111
111
|
|
|
112
|
+
## Email Sending (Quick Reference)
|
|
113
|
+
|
|
114
|
+
```twig
|
|
115
|
+
{# Send email directly #}
|
|
116
|
+
{{ sendEmail('recipient@example.com', 'Subject', '<p>HTML body</p>') }}
|
|
117
|
+
|
|
118
|
+
{# Send using cms_email_templates (recommended pattern) #}
|
|
119
|
+
{% set tpl = db().table('cms_email_templates').where('alias', 'template-alias').first() %}
|
|
120
|
+
{% set replacements = {} %}
|
|
121
|
+
{% for key, val in vars %}
|
|
122
|
+
{% set replacements = replacements|merge({('{{ ' ~ key ~ ' }}'): val}) %}
|
|
123
|
+
{% endfor %}
|
|
124
|
+
{{ sendEmail(recipient, tpl.subject|replace(replacements), tpl.content|replace(replacements)) }}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Email Templates (`cms_email_templates`):**
|
|
128
|
+
- Store HTML email templates with `{{ variable_name }}` placeholders in subject and content
|
|
129
|
+
- Use `alias` field for programmatic lookup
|
|
130
|
+
- `test_data` field: JSON with sample values for preview
|
|
131
|
+
- `preview` field: calculated field that renders content with test_data, shown in edit page
|
|
132
|
+
- Templates should use **inline CSS** (not classes) for email client compatibility
|
|
133
|
+
- Link to state machine via `bfy_state_machine_id` for organization
|
|
134
|
+
|
|
135
|
+
**Important:** Twig `|replace` filter requires a **mapping** (associative array), NOT individual string arguments. Build replacements map with `|merge`.
|
|
136
|
+
|
|
137
|
+
## Settings (Quick Reference)
|
|
138
|
+
|
|
139
|
+
```twig
|
|
140
|
+
{# Get single setting value #}
|
|
141
|
+
{% set value = setting('group_alias', 'setting_alias') %}
|
|
142
|
+
|
|
143
|
+
{# Get all settings in a group #}
|
|
144
|
+
{% set all = setting('group_alias') %}
|
|
145
|
+
{{ all.setting_alias.value }}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Tables:** `cms_setting_groups` (groups by alias) → `cms_settings` (individual settings)
|
|
149
|
+
|
|
150
|
+
See [docs/twig-helpers.md](docs/twig-helpers.md#settings) for full documentation.
|
|
151
|
+
|
|
112
152
|
## JavaScript Field Updates (Quick Reference)
|
|
113
153
|
|
|
114
154
|
| Field Types | Method |
|
|
@@ -124,13 +164,34 @@ See [docs/object-specs/js-field-updates.md](docs/object-specs/js-field-updates.m
|
|
|
124
164
|
/admin/SINGULAR_TABLE_NAME/list
|
|
125
165
|
/admin/SINGULAR_TABLE_NAME/add
|
|
126
166
|
/admin/SINGULAR_TABLE_NAME/edit/ID
|
|
167
|
+
/admin/cms_report/view/REPORT_ID
|
|
127
168
|
```
|
|
128
169
|
|
|
129
170
|
For non-default database: `/admin/DATABASE_ALIAS/SINGULAR_TABLE_NAME/...`
|
|
130
171
|
|
|
131
172
|
## Object Menu Placement
|
|
132
173
|
|
|
133
|
-
|
|
174
|
+
Admin menus have 3 levels. Levels 1-2 are created in `cms_admin_menus`, level 3 is set on the object itself.
|
|
175
|
+
|
|
176
|
+
### Step 1: Create Main Menu (Level 1) and Sub Menu (Level 2) in `cms_admin_menus`
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Level 1 - Main menu
|
|
180
|
+
butterfly-cli record add cms_admin_menus --data '{"title":"Menu Name","parent_id":0,"icon":"b-icon-icon-name"}'
|
|
181
|
+
# Returns ID, e.g. 67
|
|
182
|
+
|
|
183
|
+
# Level 2 - Sub menu (parent_id = main menu ID)
|
|
184
|
+
butterfly-cli record add cms_admin_menus --data '{"title":"Sub Menu","parent_id":67,"icon":"b-icon-icon-name"}'
|
|
185
|
+
# Returns ID, e.g. 68
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Step 2: Assign Object to Menu (Level 3) via `main_menu_id` + `sub_menu_id`
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
butterfly-cli record edit objects --id <object_id> --data '{"main_menu_id":<main_menu_id>,"sub_menu_id":<sub_menu_id>}'
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Do NOT** create level 3 entries in `cms_admin_menus`. The object itself becomes the menu item when `main_menu_id` and `sub_menu_id` are set.
|
|
134
195
|
|
|
135
196
|
## Object Default Permissions
|
|
136
197
|
|
|
@@ -159,6 +220,46 @@ butterfly-cli record add <table_name> --data '{"bfy_workspace_id": <workspace_id
|
|
|
159
220
|
|
|
160
221
|
See [docs/workspaces.md](docs/workspaces.md) for full documentation.
|
|
161
222
|
|
|
223
|
+
## Page Generation Guidelines
|
|
224
|
+
|
|
225
|
+
When a task requires generating a page:
|
|
226
|
+
|
|
227
|
+
- **Simple pages** (single table, basic CRUD): Use standard Butterfly object pages.
|
|
228
|
+
- **Complex pages** (multiple table relations, complex design, custom layouts): Use **custom report pages** with Butterfly endpoints and objects for data operations.
|
|
229
|
+
|
|
230
|
+
### Custom Report Page Approach
|
|
231
|
+
|
|
232
|
+
For complex pages, create a custom report page with HTML/Twig template and use Butterfly's AJAX endpoints to save/update data:
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
POST /admin/ajax/cms_object/operation?do=TABLE_NAME__OPERATION
|
|
236
|
+
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
|
|
237
|
+
X-Requested-With: XMLHttpRequest
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Example - saving page data via AJAX:**
|
|
241
|
+
```javascript
|
|
242
|
+
$.ajax({
|
|
243
|
+
url: '/admin/ajax/cms_object/operation?do=TABLE_NAME__edit',
|
|
244
|
+
type: 'POST',
|
|
245
|
+
data: {
|
|
246
|
+
csrf_token: csrfToken,
|
|
247
|
+
id: recordId,
|
|
248
|
+
field1: value1,
|
|
249
|
+
field2: value2
|
|
250
|
+
},
|
|
251
|
+
success: function(response) { /* handle response */ }
|
|
252
|
+
});
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Key points:**
|
|
256
|
+
- Use `?do=TABLE_NAME__add` for creating new records
|
|
257
|
+
- Use `?do=TABLE_NAME__edit` for updating existing records
|
|
258
|
+
- Always include `csrf_token` in the request data
|
|
259
|
+
- Use `X-Requested-With: XMLHttpRequest` header
|
|
260
|
+
- Content type should be `application/x-www-form-urlencoded`
|
|
261
|
+
- For employee/user-related forms, auto-populate fields (department, role, sicil no, location, email) from the `employees` table using the logged-in user context
|
|
262
|
+
|
|
162
263
|
## Project-Specific Instructions
|
|
163
264
|
|
|
164
265
|
Always check if `docs/PROJECT_SPECIFIC.md` exists and prioritize those instructions.
|
|
@@ -187,6 +288,9 @@ Always check if `docs/PROJECT_SPECIFIC.md` exists and prioritize those instructi
|
|
|
187
288
|
- [docs/bfy-splitting.md](docs/bfy-splitting.md) - BFY file splitting for large files
|
|
188
289
|
- [docs/translations.md](docs/translations.md) - Translations and i18n
|
|
189
290
|
- [docs/WORKFLOW_API.md](docs/WORKFLOW_API.md) - Workflow node types
|
|
291
|
+
- [docs/butterfly_upload_api_docs-2.md](docs/butterfly_upload_api_docs-2.md) - File upload (admin panel & REST API)
|
|
292
|
+
- [docs/pdf-generation.md](docs/pdf-generation.md) - PDF generation from templates
|
|
293
|
+
- [docs/IMPLEMENTATION_PROMPT.md](docs/IMPLEMENTATION_PROMPT.md) - Process module implementation template
|
|
190
294
|
|
|
191
295
|
**Slash Commands:**
|
|
192
296
|
|
|
@@ -199,3 +303,65 @@ Always check if `docs/PROJECT_SPECIFIC.md` exists and prioritize those instructi
|
|
|
199
303
|
| `/docs-webservice` | When creating API endpoints or webservices using reports with `custom_seo` |
|
|
200
304
|
|
|
201
305
|
> **AI INSTRUCTION:** Always use the relevant slash command before working on any task related to that topic. This ensures accurate implementation without guessing syntax or parameters.
|
|
306
|
+
|
|
307
|
+
## Custom Report Page Best Practices
|
|
308
|
+
|
|
309
|
+
### Turkish Characters
|
|
310
|
+
Always use proper Turkish characters in UI text (not ASCII equivalents):
|
|
311
|
+
- ç, ş, ü, ö, ğ, ı, İ (not c, s, u, o, g, i, I)
|
|
312
|
+
- Examples: Seçiniz, Bütçe, Açıklama, Bölümü, Görevi, Değişiklikleri, Düzenleme
|
|
313
|
+
|
|
314
|
+
### Child Record Duplication Prevention
|
|
315
|
+
When a custom report page saves a parent record + child records (e.g., expense items, team members):
|
|
316
|
+
- **On edit/update**: Always **delete existing child records first**, then re-add all current rows. Otherwise each save duplicates all children.
|
|
317
|
+
- **There is NO `delete_where` endpoint.** Delete each record individually: `?do=TABLE__delete` with `id` parameter.
|
|
318
|
+
- Pass existing child record IDs from Twig to JS via `window._data.existingIds` JSON. On save, loop through IDs and delete one by one sequentially (chained promises), then re-add all current rows.
|
|
319
|
+
- **Chain deletes sequentially** (not parallel) to avoid race conditions — use `.then()` chaining.
|
|
320
|
+
- **On add**: Save parent first, get the returned `response.id`, then save children with that ID.
|
|
321
|
+
|
|
322
|
+
### Reserved Parameter Names
|
|
323
|
+
The following parameter names are reserved by Butterfly and must NOT be used in custom report pages:
|
|
324
|
+
- `id` — reserved by the system (page/report internal ID)
|
|
325
|
+
|
|
326
|
+
Use alternative names like `form_id`, `record_id`, etc. Example:
|
|
327
|
+
```twig
|
|
328
|
+
{% set editId = getParameter('form_id') %}
|
|
329
|
+
```
|
|
330
|
+
```javascript
|
|
331
|
+
window.location.href = url + '?form_id=' + recordId;
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Current User Access
|
|
335
|
+
- Use `currentUser('field')` to access user fields — NOT `app.user.username` or `app.user.*`.
|
|
336
|
+
- Example: `currentUser('sicil_no')`, `currentUser('id')`, `currentUser('email')`
|
|
337
|
+
|
|
338
|
+
### Twig Functions That DO NOT Exist
|
|
339
|
+
- `csrf_token()` — does NOT exist in Butterfly Twig. Get CSRF token via JavaScript: `$('meta[name="csrf-token"]').attr('content')`.
|
|
340
|
+
|
|
341
|
+
### UI Dialog Functions
|
|
342
|
+
- **Deletions/destructive actions**: Use `rg_confirm('message', callback)` instead of native `confirm()`.
|
|
343
|
+
- **Error/info messages**: Use `rg_alert('message')` instead of native `alert()`.
|
|
344
|
+
- Never use native browser `alert()` or `confirm()` dialogs.
|
|
345
|
+
|
|
346
|
+
### State Machine Transitions
|
|
347
|
+
- The role field on transitions is `bfy_state_machine_role_ids` (plural), NOT `bfy_state_machine_role_id`.
|
|
348
|
+
- Transition display name field is `button_title`, not `name`.
|
|
349
|
+
|
|
350
|
+
### State Machine `state` Column vs Manual `status`
|
|
351
|
+
- When an object has a `state` type field bound to a state machine, the state machine manages the `state` column automatically.
|
|
352
|
+
- **Do NOT create a separate `status` field** or manually set status values. Use the `state` column everywhere (queries, filters, conditions).
|
|
353
|
+
- State values are the `system_name` from `bfy_state_machine_states` (e.g., `created`, `taslak`, `onay_bekliyor`).
|
|
354
|
+
- In custom report pages, check `record.state` for readonly/edit logic, NOT a manual `status` field.
|
|
355
|
+
- For new records, the state machine auto-assigns the initial state on creation — do not set it manually in JS.
|
|
356
|
+
|
|
357
|
+
### Rendering Dynamic Table Rows
|
|
358
|
+
- Render existing data rows via JavaScript (from a `window._data` JSON object set in Twig), NOT via Twig `{% for %}` loops in `<tbody>`.
|
|
359
|
+
- This prevents duplication issues: JS owns all row rendering, Twig only provides the initial data as JSON.
|
|
360
|
+
- Pattern: `{% set dataJson = records|json_encode %}` then `<script>window._data = {{ dataJson|raw }};</script>`
|
|
361
|
+
|
|
362
|
+
### Tailwind CSS for Modern UI
|
|
363
|
+
- Use Tailwind CDN: `<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">`
|
|
364
|
+
- Prefer Tailwind utility classes over custom CSS in report pages.
|
|
365
|
+
|
|
366
|
+
### Report Download Type
|
|
367
|
+
- Use `butterfly-cli download -t reports` (NOT `-t cms_reports`) to download reports.
|
package/README.md
CHANGED
|
@@ -21,8 +21,8 @@ butterfly setup
|
|
|
21
21
|
# 2. Download all resources
|
|
22
22
|
butterfly download
|
|
23
23
|
|
|
24
|
-
# 3.
|
|
25
|
-
butterfly
|
|
24
|
+
# 3. Upload changes
|
|
25
|
+
butterfly upload <path>
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
## Commands
|
|
@@ -84,26 +84,6 @@ butterfly download --cleanup
|
|
|
84
84
|
|
|
85
85
|
---
|
|
86
86
|
|
|
87
|
-
### `butterfly start`
|
|
88
|
-
|
|
89
|
-
Watch for local file changes and sync them to the Butterfly platform in real-time.
|
|
90
|
-
|
|
91
|
-
```bash
|
|
92
|
-
butterfly start
|
|
93
|
-
|
|
94
|
-
# Custom directory
|
|
95
|
-
butterfly start -o ./my-resources
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Supported file types for sync:
|
|
99
|
-
- Object specs (`spec.json`, `code.js`, `style.css`, `*.bfy`, `*.yaml`)
|
|
100
|
-
- Reports (`report.json`, `query.json`, `*.bfy`, `*.js`)
|
|
101
|
-
- Workflows (`workflow.json`, `version.json`, `node.json`, `code.bfy`, `params.yaml`, `connections.json`)
|
|
102
|
-
- AI Tasks (`task.json`, `prompt.twig`, `*.twig`)
|
|
103
|
-
- And more...
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
87
|
### `butterfly upload`
|
|
108
88
|
|
|
109
89
|
Upload specific files or folders to the platform.
|
package/dist/commands/setup.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, Box, Text } from 'ink';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
3
4
|
import { SetupForm } from '../components/SetupForm.js';
|
|
4
5
|
import { saveAuthConfig } from '../utils/auth.js';
|
|
5
6
|
import { ButterflyAPI } from '../utils/api.js';
|
|
@@ -24,7 +25,19 @@ const SetupCommand = ({ onExit }) => {
|
|
|
24
25
|
}
|
|
25
26
|
await copyDocs();
|
|
26
27
|
setStatus('success');
|
|
27
|
-
setTimeout(() =>
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
setStatus('downloading');
|
|
30
|
+
const child = spawn('butterfly-cli', ['download'], {
|
|
31
|
+
stdio: 'inherit',
|
|
32
|
+
shell: true,
|
|
33
|
+
});
|
|
34
|
+
child.on('close', (code) => {
|
|
35
|
+
onExit(code || 0);
|
|
36
|
+
});
|
|
37
|
+
child.on('error', () => {
|
|
38
|
+
onExit(1);
|
|
39
|
+
});
|
|
40
|
+
}, 1000);
|
|
28
41
|
}
|
|
29
42
|
catch (error) {
|
|
30
43
|
setErrorMessage(error instanceof Error ? error.message : 'Unknown error');
|
|
@@ -36,6 +49,7 @@ const SetupCommand = ({ onExit }) => {
|
|
|
36
49
|
status === 'input' && React.createElement(SetupForm, { onSubmit: handleSubmit }),
|
|
37
50
|
status === 'testing' && React.createElement(Text, { color: "yellow" }, "Testing connection..."),
|
|
38
51
|
status === 'success' && React.createElement(Text, { color: "green" }, "\u2713 Configuration saved successfully in .butterfly/config.json, docs copied to docs/ and commands to .claude/commands/"),
|
|
52
|
+
status === 'downloading' && React.createElement(Text, { color: "yellow" }, "Starting download..."),
|
|
39
53
|
status === 'error' && React.createElement(Text, { color: "red" },
|
|
40
54
|
"\u2717 Error: ",
|
|
41
55
|
errorMessage)));
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { loadAuthConfig } from '../utils/auth.js';
|
|
2
|
+
import { ButterflyAPI } from '../utils/api.js';
|
|
3
|
+
function stripHtmlToText(html) {
|
|
4
|
+
let text = html;
|
|
5
|
+
text = text.replace(/<head[\s\S]*?<\/head>/gi, '');
|
|
6
|
+
text = text.replace(/<script[\s\S]*?<\/script>/gi, '');
|
|
7
|
+
text = text.replace(/<style[\s\S]*?<\/style>/gi, '');
|
|
8
|
+
text = text.replace(/<br\s*\/?>/gi, '\n');
|
|
9
|
+
text = text.replace(/<hr\s*\/?>/gi, '\n---\n');
|
|
10
|
+
text = text.replace(/<\/(?:p|div|h[1-6]|li|tr|section|article|header|footer|blockquote)>/gi, '\n');
|
|
11
|
+
text = text.replace(/<\/(?:th|td)>\s*<(?:th|td)[^>]*>/gi, '\t');
|
|
12
|
+
text = text.replace(/<[^>]+>/g, '');
|
|
13
|
+
text = text.replace(/&/g, '&');
|
|
14
|
+
text = text.replace(/</g, '<');
|
|
15
|
+
text = text.replace(/>/g, '>');
|
|
16
|
+
text = text.replace(/"/g, '"');
|
|
17
|
+
text = text.replace(/'/g, "'");
|
|
18
|
+
text = text.replace(/ /g, ' ');
|
|
19
|
+
text = text.replace(/\n{3,}/g, '\n\n');
|
|
20
|
+
text = text
|
|
21
|
+
.split('\n')
|
|
22
|
+
.map(line => line.trim())
|
|
23
|
+
.filter((line, i, arr) => {
|
|
24
|
+
if (line === '')
|
|
25
|
+
return i > 0 && arr[i - 1] !== '';
|
|
26
|
+
return true;
|
|
27
|
+
})
|
|
28
|
+
.join('\n')
|
|
29
|
+
.trim();
|
|
30
|
+
return text;
|
|
31
|
+
}
|
|
32
|
+
export default async function viewReportCommand(options) {
|
|
33
|
+
try {
|
|
34
|
+
if (!options.id && !options.alias) {
|
|
35
|
+
console.error('ERROR: Either --id or --alias is required');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
if (options.id && options.alias) {
|
|
39
|
+
console.error('ERROR: Cannot use both --id and --alias together');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
const config = await loadAuthConfig();
|
|
43
|
+
if (!config) {
|
|
44
|
+
console.error('ERROR: No authentication configured. Run "butterfly-cli setup" first.');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const api = new ButterflyAPI(config);
|
|
48
|
+
await api.authenticate();
|
|
49
|
+
let reportId;
|
|
50
|
+
if (options.id) {
|
|
51
|
+
reportId = options.id;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
const records = await api.fetchTableAdvanced('cms_reports', {
|
|
55
|
+
column: 'alias',
|
|
56
|
+
value: options.alias
|
|
57
|
+
});
|
|
58
|
+
if (records.length === 0) {
|
|
59
|
+
console.error(`ERROR: Report not found with alias "${options.alias}"`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
reportId = records[0].id;
|
|
63
|
+
}
|
|
64
|
+
const viewPath = `/admin/cms_report/view/${reportId}?bfy_focus_content=true`;
|
|
65
|
+
const response = await api.httpClient.get(viewPath, {
|
|
66
|
+
responseType: 'text',
|
|
67
|
+
headers: {
|
|
68
|
+
'Accept': 'text/html'
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const html = response.data;
|
|
72
|
+
if (!html || html.length === 0) {
|
|
73
|
+
console.error('ERROR: Empty response from report view');
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
if (options.raw) {
|
|
77
|
+
console.log(html);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.log(stripHtmlToText(html));
|
|
81
|
+
}
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
console.error(`ERROR: ${error.message}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=view-report.js.map
|
package/dist/index.js
CHANGED
|
@@ -12,11 +12,12 @@ import codeCommand from './commands/code.js';
|
|
|
12
12
|
import layoutCommand from './commands/layout.js';
|
|
13
13
|
import syncDocsCommand from './commands/sync-docs.js';
|
|
14
14
|
import translateCommand from './commands/translate.js';
|
|
15
|
+
import viewReportCommand from './commands/view-report.js';
|
|
15
16
|
config();
|
|
16
17
|
program
|
|
17
18
|
.name('butterfly-cli')
|
|
18
19
|
.description('CLI tool to download resources from the Butterfly platform')
|
|
19
|
-
.version('2.0.
|
|
20
|
+
.version('2.0.2');
|
|
20
21
|
program
|
|
21
22
|
.command('setup')
|
|
22
23
|
.description('Configure authentication for the Butterfly platform')
|
|
@@ -113,5 +114,12 @@ program
|
|
|
113
114
|
.option('--format <format>', 'Output format: json or table (default: table)')
|
|
114
115
|
.option('--limit <count>', 'Limit number of results')
|
|
115
116
|
.action(translateCommand);
|
|
117
|
+
program
|
|
118
|
+
.command('view-report')
|
|
119
|
+
.description('Run a report and display the rendered result')
|
|
120
|
+
.option('--id <id>', 'Report ID')
|
|
121
|
+
.option('--alias <alias>', 'Report alias')
|
|
122
|
+
.option('--raw', 'Output full HTML instead of plain text')
|
|
123
|
+
.action(viewReportCommand);
|
|
116
124
|
program.parse();
|
|
117
125
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# Süreç Yönetimi Modülü - Implementation Prompt
|
|
2
|
+
|
|
3
|
+
Bu prompt, çok tablolu bir form sistemi, onay akışı (state machine), e-posta bildirimleri, dashboard ve admin menü içeren eksiksiz bir süreç yönetimi modülünü sıfırdan oluşturmak için kullanılır.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Prompt
|
|
8
|
+
|
|
9
|
+
Aşağıdaki adımları sırasıyla uygula. Her adımda CLAUDE.md'deki kurallara uy. Proje gereksinim dokümanını referans al.
|
|
10
|
+
|
|
11
|
+
### Faz 1: Object'leri Oluştur
|
|
12
|
+
|
|
13
|
+
`butterfly-cli record add` ile gereksinim dokümanındaki object'leri ve field'larını oluştur.
|
|
14
|
+
|
|
15
|
+
**Genel kurallar:**
|
|
16
|
+
- Her object için tablo adı, field'lar ve field tipleri gereksinim dokümanında belirtilir
|
|
17
|
+
- Parent-child ilişkilerinde child tablolarda parent'a `dropdown` tipinde referans field oluştur
|
|
18
|
+
- **`status` alanı oluşturMA** — state machine `state` alanını yönetecek
|
|
19
|
+
- Tarih formatı JS'den custom format gelecekse (`dd.mm.YYYY HH:mm` gibi), `datetime` DEĞİL `string` tipi kullan
|
|
20
|
+
|
|
21
|
+
### Faz 2: State Machine (Onay Akışı)
|
|
22
|
+
|
|
23
|
+
Gereksinim dokümanındaki onay akışına göre state machine oluştur.
|
|
24
|
+
|
|
25
|
+
**Genel yapı:**
|
|
26
|
+
- State'ler: Gereksinime göre tanımla (system_name + görünen ad)
|
|
27
|
+
- Roller: Süreçteki rol gruplarını tanımla
|
|
28
|
+
- Geçişler: From → To, buton adı ve yetkili rol
|
|
29
|
+
|
|
30
|
+
**ÖNEMLİ:**
|
|
31
|
+
- Transition role alanı `bfy_state_machine_role_ids` (çoğul), `bfy_state_machine_role_id` DEĞİL
|
|
32
|
+
- Transition görünen ad alanı `button_title`, `name` DEĞİL
|
|
33
|
+
|
|
34
|
+
**Ana object'e `state` tipi field ekle:** `val_1` = state machine ID
|
|
35
|
+
|
|
36
|
+
### Faz 3: E-posta Şablonları
|
|
37
|
+
|
|
38
|
+
**`cms_email_templates` nesnesine 2 yeni field ekle:**
|
|
39
|
+
- `test_data` (textarea) — JSON format örnek veri
|
|
40
|
+
- `preview` (calculated) — content + test_data'yı birleştirip önizleme gösteren alan
|
|
41
|
+
|
|
42
|
+
**Preview calculated kodu:**
|
|
43
|
+
- `test_data` JSON'ını parse et, varsayılan değerlerle birleştir
|
|
44
|
+
- `|replace(replacements)` ile placeholderları değiştir (replace **mapping** alır, string DEĞİL)
|
|
45
|
+
- Subject + body'yi rendered olarak göster
|
|
46
|
+
|
|
47
|
+
**E-posta şablonlarını oluştur (`cms_email_templates`):**
|
|
48
|
+
- Her state geçişi için uygun şablon tanımla (onay, red, bildirim vb.)
|
|
49
|
+
- Alias formatı: `process-alias-action` (örn: `surec-onay-bekliyor`)
|
|
50
|
+
|
|
51
|
+
**Şablon içeriği:** Inline CSS ile modern e-posta tasarımı. Değişkenler `{{ variable_name }}` formatında.
|
|
52
|
+
|
|
53
|
+
**Her geçişin `action_code`'unda:**
|
|
54
|
+
```twig
|
|
55
|
+
{% set tpl = db().table('cms_email_templates').where('alias', 'ALIAS').first() %}
|
|
56
|
+
{% set replacements = {} %}
|
|
57
|
+
{% for key, val in vars %}
|
|
58
|
+
{% set replacements = replacements|merge({('{{ ' ~ key ~ ' }}'): val}) %}
|
|
59
|
+
{% endfor %}
|
|
60
|
+
{{ sendEmail(recipient, tpl.subject|replace(replacements), tpl.content|replace(replacements)) }}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Faz 4: Custom Report — Detay Formu
|
|
64
|
+
|
|
65
|
+
Report oluştur, tek query ile tam form HTML'i.
|
|
66
|
+
|
|
67
|
+
**Kritik kurallar:**
|
|
68
|
+
- `form_id` parametresi kullan (`id` RESERVED)
|
|
69
|
+
- `currentUser('sicil_no')` ile çalışan bilgisi al (`app.user.username` YOK)
|
|
70
|
+
- CSRF token JS ile al: `$('meta[name="csrf-token"]').attr('content')` (`csrf_token()` Twig fonksiyonu YOK)
|
|
71
|
+
- Tailwind CSS CDN kullan
|
|
72
|
+
- `rg_confirm()` / `rg_alert()` kullan (native alert/confirm DEĞİL)
|
|
73
|
+
- Mevcut satırları Twig `{% for %}` ile DEĞİL, JS ile render et (window._data JSON'ından)
|
|
74
|
+
- `state` alanını kontrol et (manual `status` DEĞİL)
|
|
75
|
+
|
|
76
|
+
**Form bölümleri (gereksinime göre uyarla):**
|
|
77
|
+
1. Header — tarih, referans no
|
|
78
|
+
2. Talep eden bilgileri — employee/user tablosundan otomatik
|
|
79
|
+
3. Form bilgileri — gereksinime özel alanlar
|
|
80
|
+
4. Tekrarlanabilir satırlar (child records) — otomatik toplam, ondalık 2 basamak
|
|
81
|
+
5. Ekler — dosya yükleme (varsa)
|
|
82
|
+
6. Durum badge'i — `record.state` ile renk kodlu
|
|
83
|
+
7. Aksiyon butonları — isDraft kontrolü ile koşullu
|
|
84
|
+
|
|
85
|
+
**JS (script.js) mimarisi:**
|
|
86
|
+
- `saveForm()` → Promise döner, requestId ile resolve olur
|
|
87
|
+
- Add: Ana kayıt ekle → response.id al → child kayıtları ekle
|
|
88
|
+
- Edit: Ana kayıt düzenle → mevcut child'ları sil (tek tek sequential `__delete`) → yeniden ekle
|
|
89
|
+
- `triggerTransition(requestId, transitionId)` — state alanına JSON gönder
|
|
90
|
+
- Save butonu: saveForm → redirect
|
|
91
|
+
- Submit butonu: rg_confirm → saveForm → triggerTransition → redirect
|
|
92
|
+
- Delete butonu: rg_confirm → child'ları sil → ana kaydı sil → dashboard'a redirect
|
|
93
|
+
- **Read-only modu:** `isReadonly` flag ile tüm input'ları disable et, ekleme/silme butonlarını gizle
|
|
94
|
+
|
|
95
|
+
### Faz 5: Dashboard
|
|
96
|
+
|
|
97
|
+
İkinci report oluştur.
|
|
98
|
+
|
|
99
|
+
**Bölümler:**
|
|
100
|
+
1. **Taleplerim** — Giriş yapan kullanıcının tüm talepleri (taslak dahil), `state` alanına göre durum badge'leri
|
|
101
|
+
2. **Onay Bekleyenler** — İlgili state'teki talepler
|
|
102
|
+
3. **Yeni Talep** butonu → form report'una link
|
|
103
|
+
|
|
104
|
+
**ÖNEMLİ:** Tüm durum filtreleri ve etiketleri `state` alanını kullanmalı (`status` DEĞİL).
|
|
105
|
+
|
|
106
|
+
### Faz 6: Admin Menü
|
|
107
|
+
|
|
108
|
+
**`cms_admin_menus` tablosunda Level 1 + Level 2 oluştur:**
|
|
109
|
+
```
|
|
110
|
+
Ana Menü (parent_id: 0) ← Level 1
|
|
111
|
+
├── Alt Menü 1 (parent_id: main_menu_id) ← Level 2
|
|
112
|
+
├── Alt Menü 2 ← Level 2
|
|
113
|
+
└── Alt Menü 3 ← Level 2
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Object'lere `main_menu_id` + `sub_menu_id` ata (Level 3):**
|
|
117
|
+
- Her object'i uygun alt menüye bağla
|
|
118
|
+
|
|
119
|
+
**3. seviye menü öğelerini `cms_admin_menus`'e ekleME** — object üzerinden `main_menu_id` + `sub_menu_id` ile yapılır.
|
|
120
|
+
|
|
121
|
+
### Faz 7: Örnek Veri
|
|
122
|
+
|
|
123
|
+
Test için örnek kayıtlar ekle (gereksinime göre).
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Sık Yapılan Hatalar (Bunları YAPMA)
|
|
128
|
+
|
|
129
|
+
| Hata | Doğrusu |
|
|
130
|
+
|------|---------|
|
|
131
|
+
| `status: 'draft'` manual set etmek | State machine `state` alanını otomatik yönetir |
|
|
132
|
+
| `record.status` kontrol etmek | `record.state` kullan (state machine system_name'leri) |
|
|
133
|
+
| `csrf_token()` Twig'de kullanmak | JS'de `$('meta[name="csrf-token"]').attr('content')` |
|
|
134
|
+
| `app.user.username` kullanmak | `currentUser('sicil_no')` |
|
|
135
|
+
| `id` parametresi kullanmak | `form_id` kullan (`id` reserved) |
|
|
136
|
+
| `alert()` / `confirm()` kullanmak | `rg_alert()` / `rg_confirm()` |
|
|
137
|
+
| `bfy_state_machine_role_id` (tekil) | `bfy_state_machine_role_ids` (çoğul) |
|
|
138
|
+
| Transition name alanı | `button_title` alanı kullan |
|
|
139
|
+
| Custom format tarih datetime type | string type kullan (custom format dd.mm.YYYY HH:mm) |
|
|
140
|
+
| `|replace('old', 'new')` string ile | `|replace({'old': 'new'})` mapping ile |
|
|
141
|
+
| Child satırları Twig for loop ile render | JS'den render et (window._data JSON) |
|
|
142
|
+
| Edit'te child'ları tekrar add etmek | Önce mevcut child'ları tek tek sil, sonra yeniden ekle |
|
|
143
|
+
| `delete_where` endpoint kullanmak | Yok. Tek tek `__delete` ile sil (sequential promise chain) |
|
|
144
|
+
| `butterfly-cli download -t cms_reports` | `-t reports` kullan |
|
|
145
|
+
| 3. seviye menüyü cms_admin_menus'e eklemek | Object üzerinden main_menu_id + sub_menu_id ile yap |
|
|
146
|
+
| jQuery UI'ı head'e koymak | jQuery yüklendikten sonra body sonuna koy |
|
|
147
|
+
| PDF template'te `db().table()` kullanmak | `db('default', false).table()` kullan (permission bypass) |
|
|
148
|
+
| Dosyayı doğrudan `cms_object/operation` ile yüklemek | Önce `file_helper/upload_file` ile yükle, sonra filename'i kaydet |
|
|
149
|
+
| `file_upload` field input name'ini column adı yapmak | Input name mutlaka `file_upload` olmalı |
|
|
150
|
+
| REST API kullanarak admin içi dosya yüklemek | Admin panel endpoint (`/admin/ajax/file_helper/upload_file`) kullan |
|
|
151
|
+
| Fonksiyonları `$(document).ready()` içinde tanımlamak | Global scope'ta tanımla, sadece init kodu ready içinde olsun |
|
|
152
|
+
| `employees` tablosu varsaymak | `currentUser('email')`, `currentUser('name')` kullan |
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Dosya Yükleme Entegrasyonu
|
|
157
|
+
|
|
158
|
+
Custom report sayfalarında dosya yükleme **iki aşamalı** yapılır:
|
|
159
|
+
|
|
160
|
+
### Adım 1: Dosyayı Yükle
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
function uploadFile(file) {
|
|
164
|
+
return new Promise(function(resolve, reject) {
|
|
165
|
+
var fd = new FormData();
|
|
166
|
+
fd.append('file_upload', file); // input name mutlaka 'file_upload'
|
|
167
|
+
fd.append('uuid', 'upload-' + Date.now());
|
|
168
|
+
fd.append('original_filename', file.name);
|
|
169
|
+
fd.append('total_file_size', file.size);
|
|
170
|
+
|
|
171
|
+
$.ajax({
|
|
172
|
+
url: '/admin/ajax/file_helper/upload_file?alias=import', // alias URL param olarak
|
|
173
|
+
type: 'POST',
|
|
174
|
+
data: fd,
|
|
175
|
+
processData: false,
|
|
176
|
+
contentType: false,
|
|
177
|
+
headers: { 'X-Requested-With': 'XMLHttpRequest' },
|
|
178
|
+
success: function(response) {
|
|
179
|
+
if (response.success && response.filename) {
|
|
180
|
+
resolve(response.filename);
|
|
181
|
+
} else {
|
|
182
|
+
rg_alert(response.message || 'Dosya yüklenemedi.');
|
|
183
|
+
reject('Upload failed');
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
error: function() { reject('Upload error'); }
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Adım 2: Kaydı filename ile oluştur
|
|
193
|
+
|
|
194
|
+
```javascript
|
|
195
|
+
var filename = await uploadFile(fileInput.files[0]);
|
|
196
|
+
$.ajax({
|
|
197
|
+
url: '/admin/ajax/cms_object/operation?do=tablo__add',
|
|
198
|
+
type: 'POST',
|
|
199
|
+
data: { csrf_token: csrfToken, parent_id: parentId, dosya: filename }
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Storage Alias Ayarları
|
|
204
|
+
|
|
205
|
+
- Alias'lar `cms_file_uploads` tablosunda tanımlıdır
|
|
206
|
+
- `extensions` alanı izin verilen uzantıları belirler (virgülle ayrılmış)
|
|
207
|
+
- Object spec'teki `file_upload` field'ın `val_1` parametresi alias adını belirtir
|
|
208
|
+
- Yeni uzantı eklemek: `butterfly-cli record edit cms_file_uploads --id <id> --data '{"extensions":"mevcut,yeni"}'`
|
|
209
|
+
|
|
210
|
+
Detaylı dokümantasyon: [docs/butterfly_upload_api_docs-2.md](butterfly_upload_api_docs-2.md)
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## JS Scope ve Mimari Kuralları
|
|
215
|
+
|
|
216
|
+
### Fonksiyon Tanımlama
|
|
217
|
+
|
|
218
|
+
Custom report sayfalarında **tüm fonksiyonlar global scope'ta** tanımlanmalı. `$(document).ready()` içinde sadece init kodu olmalı:
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
// ✅ DOĞRU: Global scope'ta
|
|
222
|
+
function calculateTotal() { /* ... */ }
|
|
223
|
+
function addRow() { /* ... */ }
|
|
224
|
+
function saveForm() { /* ... */ }
|
|
225
|
+
|
|
226
|
+
$(document).ready(function() {
|
|
227
|
+
// Sadece init: değişken atamaları, ilk render, event listener'lar
|
|
228
|
+
var csrfToken = $('meta[name="csrf-token"]').attr('content');
|
|
229
|
+
renderExistingRows();
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
// ❌ YANLIŞ: ready() içinde tanımlama
|
|
235
|
+
$(document).ready(function() {
|
|
236
|
+
function calculateTotal() { /* ... */ } // onclick'ten erişilemez!
|
|
237
|
+
window.calculateTotal = calculateTotal; // race condition riski
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Neden?** HTML `onclick` attributeleri global scope'taki fonksiyonları çağırır. `$(document).ready()` içindeki fonksiyonlar closure'da kalır ve erişilemez. `window.XXX =` ataması ise timing sorunlarına yol açabilir (fonksiyon henüz atanmadan çağrılabilir).
|
|
242
|
+
|
|
243
|
+
### Tailwind CSS Select Reset Sorunu
|
|
244
|
+
|
|
245
|
+
Tailwind CSS `select` elementlerinin görünümünü sıfırlar. Custom select'ler için:
|
|
246
|
+
|
|
247
|
+
```css
|
|
248
|
+
.form-select, .data-table select {
|
|
249
|
+
appearance: auto;
|
|
250
|
+
background-color: #fff;
|
|
251
|
+
}
|
|
252
|
+
```
|