@controleonline/ui-common 1.2.87 → 1.2.88
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/.github/workflows/integration-source-gate.yml +68 -0
- package/.github/workflows/manager-worker.yml +7 -156
- package/.github/workflows/publish-npm.yml +73 -27
- package/.github/workflows/pull-request-checks.yml +7 -5
- package/docs/technical/Device-Detail-Alias-UI-Sync.md +141 -0
- package/package.json +13 -8
- package/src/react/components/BottomNavigationBar.js +178 -174
- package/src/react/components/BottomNavigationBar.styles.js +139 -118
- package/src/react/components/DefaultProvider.native.js +427 -421
- package/src/react/components/DefaultProvider.web.js +131 -125
- package/src/react/components/RemoteCheckoutService.js +3 -3
- package/src/react/components/RuntimeFooterMarqueeText.js +88 -36
- package/src/react/components/RuntimeInfoFooter.js +76 -43
- package/src/react/components/RuntimeInfoFooter.styles.js +22 -14
- package/src/react/config/deviceConfigBootstrap.js +3 -3
- package/src/react/pages/Devices/detail/DeviceDetailAlertsCommandsSection.js +4 -22
- package/src/react/pages/Devices/detail/DeviceDetailMovementSections.js +6 -2
- package/src/react/pages/Devices/detail/DeviceDetailOrdersPrintSection.js +4 -0
- package/src/react/pages/Devices/detail/DeviceDetailScreen.js +23 -338
- package/src/react/pages/Devices/detail/createDeviceDetailContext.js +20 -0
- package/src/react/pages/Devices/detail/useDeviceDetailActions.js +11 -36
- package/src/react/pages/Devices/detail/useDeviceDetailLoaders.js +129 -37
- package/src/react/pages/Devices/detail/useDeviceDetailSaves.js +11 -38
- package/src/react/pages/Devices/detail/useDeviceDetailStateA.js +9 -28
- package/src/react/pages/IntegrationConfigFields.js +1 -0
- package/src/react/pages/IntegrationConfigPage.js +101 -45
- package/src/react/pages/IntegrationConfigPage.utils.js +21 -13
- package/src/react/pages/fetchPeopleConfigs.js +21 -0
- package/src/react/pages/integrationsCatalog.js +15 -110
- package/src/react/services/Cielo/Cielo.js +9 -9
- package/src/react/services/Getnet/Getnet.web.js +25 -0
- package/src/react/utils/companyAuthority.js +36 -0
- package/src/react/utils/fileUrl.js +14 -5
- package/src/react/utils/paymentDevices.js +375 -270
- package/src/react/utils/printerDevices.js +2 -2
- package/src/react/utils/runtimeFooter.js +178 -224
- package/src/react/utils/runtimeFooterText.js +160 -0
- package/src/react/utils/runtimeLanguage.js +5 -5
- package/src/react/utils/runtimeZoom.js +29 -5
- package/src/react/utils/shopFranchises.js +167 -12
- package/src/react/utils/storeColumns.js +235 -217
- package/src/react/utils/systemErrorMessage.js +2 -1
- package/src/store/integration/index.js +43 -0
- package/src/tests/browser/manager/device-detail-apply-config-defined.spec.js +31 -0
- package/src/tests/browser/manager/device-detail-composition.spec.js +295 -0
- package/src/tests/browser/manager/device-detail-copy-config.spec.js +11 -1
- package/src/tests/browser/manager/device-detail-pdv-current-company.spec.js +273 -0
- package/src/tests/browser/manager/runtime-footer-rotation.spec.js +397 -0
- package/src/tests/react/components/DefaultProviderAuthority.test.js +28 -0
- package/src/tests/react/components/RuntimeFooterMarqueeText.build.test.js +29 -0
- package/src/tests/react/config/deviceConfigBootstrap.test.js +24 -1
- package/src/tests/react/pages/applyCurrentDeviceConfig.test.js +85 -0
- package/src/tests/react/pages/deviceDetailComposition.test.js +65 -0
- package/src/tests/react/pages/deviceDetailHookDependencies.test.mjs +31 -0
- package/src/tests/react/pages/deviceDetailLoaderDependencies.test.mjs +28 -0
- package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +27 -6
- package/src/tests/react/services/Cielo.test.js +2 -2
- package/src/tests/react/utils/companyAuthority.test.js +53 -0
- package/src/tests/react/utils/formatter.test.mjs +6 -0
- package/src/tests/react/utils/paymentDevices.test.js +72 -0
- package/src/tests/react/utils/runtimeFooter.test.js +35 -0
- package/src/tests/react/utils/runtimeFooterText.test.js +88 -0
- package/src/tests/react/utils/runtimeLanguage.test.js +4 -4
- package/src/tests/react/utils/runtimeZoom.test.js +20 -0
- package/src/tests/react/utils/shopFranchises.test.js +32 -13
- package/src/tests/react/utils/translate.test.mjs +6 -6
- package/src/utils/formatter.js +7 -3
- package/src/utils/translate.js +16 -16
- package/src/utils/translatePending.js +6 -6
- package/src/utils/translateStorage.js +2 -2
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
name: Integration source gate
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [dev, staging, master]
|
|
6
|
+
types: [opened, synchronize, reopened, edited, ready_for_review]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pull-requests: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
source-policy:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- name: Enforce task or frozen RC source
|
|
17
|
+
env:
|
|
18
|
+
HEAD_REF: ${{ github.head_ref }}
|
|
19
|
+
BASE_REF: ${{ github.base_ref }}
|
|
20
|
+
run: |
|
|
21
|
+
set -eu
|
|
22
|
+
if [ "$BASE_REF" = "dev" ]; then
|
|
23
|
+
echo "$HEAD_REF" | grep -Eq '^task-[1-9][0-9]*$' || {
|
|
24
|
+
echo "::error::dev only accepts task-<numeric issue>."
|
|
25
|
+
exit 1
|
|
26
|
+
}
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
echo "$HEAD_REF" | grep -Eq '^rc/[0-9]+\.[0-9]+\.[0-9]+-rc\.[1-9][0-9]*$' || {
|
|
30
|
+
echo "::error::$BASE_REF only accepts a frozen rc/X.Y.Z-rc.N source."
|
|
31
|
+
exit 1
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
if: github.base_ref != 'dev'
|
|
36
|
+
with:
|
|
37
|
+
ref: ${{ github.head_ref }}
|
|
38
|
+
|
|
39
|
+
- name: Validate frozen RC manifest
|
|
40
|
+
if: github.base_ref != 'dev'
|
|
41
|
+
env:
|
|
42
|
+
HEAD_REF: ${{ github.head_ref }}
|
|
43
|
+
run: |
|
|
44
|
+
set -eu
|
|
45
|
+
test -f .release/rc-manifest.json || {
|
|
46
|
+
echo "::error::Missing .release/rc-manifest.json"
|
|
47
|
+
exit 1
|
|
48
|
+
}
|
|
49
|
+
node --input-type=module <<'NODE'
|
|
50
|
+
import fs from 'node:fs';
|
|
51
|
+
const m = JSON.parse(fs.readFileSync('.release/rc-manifest.json', 'utf8'));
|
|
52
|
+
const branch = process.env.HEAD_REF;
|
|
53
|
+
const match = /^rc\/(\d+\.\d+\.\d+)-rc\.([1-9][0-9]*)$/.exec(branch);
|
|
54
|
+
if (!match) throw new Error('invalid RC branch');
|
|
55
|
+
if (m.branch !== branch || m.version !== match[1] || Number(m.rc) !== Number(match[2])) throw new Error('manifest identity mismatch');
|
|
56
|
+
if (m.frozen !== true) throw new Error('manifest is not frozen');
|
|
57
|
+
if (!Array.isArray(m.tasks) || m.tasks.length < 1 || m.tasks.length > 5) throw new Error('RC must contain 1..5 tasks');
|
|
58
|
+
const tasks = m.tasks.map(Number);
|
|
59
|
+
if (tasks.some((id) => !Number.isInteger(id) || id < 1)) throw new Error('invalid task id');
|
|
60
|
+
if (new Set(tasks).size !== tasks.length) throw new Error('duplicate RC tasks');
|
|
61
|
+
if (!/^[0-9a-f]{40}$/i.test(String(m.baseMaster || ''))) throw new Error('invalid baseMaster');
|
|
62
|
+
const repos = m.repositories || {};
|
|
63
|
+
if (!Object.keys(repos).length) throw new Error('empty repositories manifest');
|
|
64
|
+
for (const [repo, sha] of Object.entries(repos)) {
|
|
65
|
+
if (!/^ControleOnline\/[A-Za-z0-9_.-]+$/.test(repo) || !/^[0-9a-f]{40}$/i.test(String(sha))) throw new Error('invalid repository manifest entry');
|
|
66
|
+
}
|
|
67
|
+
console.log('Frozen RC manifest valid:', branch, tasks);
|
|
68
|
+
NODE
|
|
@@ -1,168 +1,19 @@
|
|
|
1
1
|
name: Manager Worker
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
# PAUSA CTO ControleOnline/agents-mcp#201 (2026-08-29)
|
|
7
|
+
# Trigger original desativado: push em master, dev, staging.
|
|
6
8
|
|
|
7
9
|
permissions:
|
|
8
|
-
contents:
|
|
10
|
+
contents: read
|
|
9
11
|
issues: write
|
|
10
12
|
pull-requests: write
|
|
11
13
|
|
|
12
|
-
# Orquestrador: Manager resolve/cria issue e decide quais workers invocar.
|
|
13
|
-
# Padrão de branch da org = master (não usar main).
|
|
14
|
-
# Em master: bump patch + tag vX.Y.Z → dispara publish-npm.yml (Trusted Publishing).
|
|
15
14
|
jobs:
|
|
16
15
|
manager:
|
|
17
|
-
name: Manager —
|
|
18
|
-
runs-on: ubuntu-latest
|
|
19
|
-
outputs:
|
|
20
|
-
issue_number: ${{ steps.mgr.outputs.issue_number }}
|
|
21
|
-
created: ${{ steps.mgr.outputs.created }}
|
|
22
|
-
run_qa: ${{ steps.mgr.outputs.run_qa }}
|
|
23
|
-
run_security: ${{ steps.mgr.outputs.run_security }}
|
|
24
|
-
run_docs: ${{ steps.mgr.outputs.run_docs }}
|
|
25
|
-
run_gates: ${{ steps.mgr.outputs.run_gates }}
|
|
26
|
-
base_branch: ${{ steps.mgr.outputs.base_branch }}
|
|
27
|
-
steps:
|
|
28
|
-
- uses: actions/checkout@v4
|
|
29
|
-
with:
|
|
30
|
-
fetch-depth: 0
|
|
31
|
-
|
|
32
|
-
- name: Manager subworker
|
|
33
|
-
id: mgr
|
|
34
|
-
uses: ./.github/actions/workers/manager
|
|
35
|
-
with:
|
|
36
|
-
gh_token: ${{ secrets.GH_TOKEN }}
|
|
37
|
-
branch: ${{ github.ref_name }}
|
|
38
|
-
|
|
39
|
-
# Workers invocados pelo plano do Manager (paralelo quando ambos true)
|
|
40
|
-
qa:
|
|
41
|
-
name: QA (invocado pelo Manager)
|
|
42
|
-
needs: manager
|
|
43
|
-
if: needs.manager.outputs.run_qa == 'true'
|
|
44
|
-
runs-on: ubuntu-latest
|
|
45
|
-
steps:
|
|
46
|
-
- uses: actions/checkout@v4
|
|
47
|
-
- uses: ./.github/actions/workers/qa
|
|
48
|
-
with:
|
|
49
|
-
gh_token: ${{ secrets.GH_TOKEN }}
|
|
50
|
-
issue_number: ${{ needs.manager.outputs.issue_number }}
|
|
51
|
-
base_branch: ${{ needs.manager.outputs.base_branch }}
|
|
52
|
-
|
|
53
|
-
security:
|
|
54
|
-
name: Security (invocado pelo Manager)
|
|
55
|
-
needs: manager
|
|
56
|
-
if: needs.manager.outputs.run_security == 'true'
|
|
57
|
-
runs-on: ubuntu-latest
|
|
58
|
-
steps:
|
|
59
|
-
- uses: actions/checkout@v4
|
|
60
|
-
- uses: ./.github/actions/workers/security
|
|
61
|
-
with:
|
|
62
|
-
gh_token: ${{ secrets.GH_TOKEN }}
|
|
63
|
-
issue_number: ${{ needs.manager.outputs.issue_number }}
|
|
64
|
-
base_branch: ${{ needs.manager.outputs.base_branch }}
|
|
65
|
-
|
|
66
|
-
technical-documenter:
|
|
67
|
-
name: Technical Documenter (invocado pelo Manager)
|
|
68
|
-
needs: manager
|
|
69
|
-
if: needs.manager.outputs.run_docs == 'true'
|
|
70
|
-
runs-on: ubuntu-latest
|
|
71
|
-
steps:
|
|
72
|
-
- uses: actions/checkout@v4
|
|
73
|
-
- uses: ./.github/actions/workers/technical-documenter
|
|
74
|
-
with:
|
|
75
|
-
gh_token: ${{ secrets.GH_TOKEN }}
|
|
76
|
-
issue_number: ${{ needs.manager.outputs.issue_number }}
|
|
77
|
-
base_branch: ${{ needs.manager.outputs.base_branch }}
|
|
78
|
-
|
|
79
|
-
# master: se faltar gate do quarteto → tags + workers faltantes
|
|
80
|
-
master-gates:
|
|
81
|
-
name: Master gates (quarteto)
|
|
82
|
-
needs: manager
|
|
83
|
-
if: needs.manager.outputs.run_gates == 'true'
|
|
84
|
-
runs-on: ubuntu-latest
|
|
85
|
-
outputs:
|
|
86
|
-
need_qa: ${{ steps.check.outputs.need_qa }}
|
|
87
|
-
need_security: ${{ steps.check.outputs.need_security }}
|
|
88
|
-
need_docs: ${{ steps.check.outputs.need_docs }}
|
|
89
|
-
steps:
|
|
90
|
-
- uses: actions/checkout@v4
|
|
91
|
-
- name: Inspect quarteto e tags
|
|
92
|
-
id: check
|
|
93
|
-
env:
|
|
94
|
-
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
95
|
-
ISSUE: ${{ needs.manager.outputs.issue_number }}
|
|
96
|
-
REPO: ${{ github.repository }}
|
|
97
|
-
run: |
|
|
98
|
-
set -euo pipefail
|
|
99
|
-
LABELS=$(gh issue view "$ISSUE" --repo "$REPO" --json labels --jq '[.labels[].name] | join(",")')
|
|
100
|
-
echo "Labels: $LABELS"
|
|
101
|
-
|
|
102
|
-
need_qa=false
|
|
103
|
-
need_security=false
|
|
104
|
-
need_docs=false
|
|
105
|
-
|
|
106
|
-
echo "$LABELS" | grep -q 'qa:accepted' || need_qa=true
|
|
107
|
-
echo "$LABELS" | grep -q 'security:accepted' || need_security=true
|
|
108
|
-
echo "$LABELS" | grep -q 'agent:technical-documenter:done' || need_docs=true
|
|
109
|
-
|
|
110
|
-
echo "need_qa=$need_qa" >> "$GITHUB_OUTPUT"
|
|
111
|
-
echo "need_security=$need_security" >> "$GITHUB_OUTPUT"
|
|
112
|
-
echo "need_docs=$need_docs" >> "$GITHUB_OUTPUT"
|
|
113
|
-
|
|
114
|
-
[ "$need_qa" = true ] && gh issue edit "$ISSUE" --repo "$REPO" --add-label "agent:qa" || true
|
|
115
|
-
[ "$need_security" = true ] && gh issue edit "$ISSUE" --repo "$REPO" --add-label "agent:security" || true
|
|
116
|
-
[ "$need_docs" = true ] && gh issue edit "$ISSUE" --repo "$REPO" --add-label "agent:technical-documenter" || true
|
|
117
|
-
|
|
118
|
-
gh issue comment "$ISSUE" --repo "$REPO" --body \
|
|
119
|
-
"Master gates (Manager): need_qa=$need_qa need_security=$need_security need_docs=$need_docs"
|
|
120
|
-
|
|
121
|
-
master-qa:
|
|
122
|
-
name: QA (master gap via Manager)
|
|
123
|
-
needs: [manager, master-gates]
|
|
124
|
-
if: needs.master-gates.outputs.need_qa == 'true'
|
|
16
|
+
name: Manager — paused
|
|
125
17
|
runs-on: ubuntu-latest
|
|
126
18
|
steps:
|
|
127
|
-
-
|
|
128
|
-
- uses: ./.github/actions/workers/qa
|
|
129
|
-
with:
|
|
130
|
-
gh_token: ${{ secrets.GH_TOKEN }}
|
|
131
|
-
issue_number: ${{ needs.manager.outputs.issue_number }}
|
|
132
|
-
base_branch: master
|
|
133
|
-
|
|
134
|
-
master-security:
|
|
135
|
-
name: Security (master gap via Manager)
|
|
136
|
-
needs: [manager, master-gates]
|
|
137
|
-
if: needs.master-gates.outputs.need_security == 'true'
|
|
138
|
-
runs-on: ubuntu-latest
|
|
139
|
-
steps:
|
|
140
|
-
- uses: actions/checkout@v4
|
|
141
|
-
- uses: ./.github/actions/workers/security
|
|
142
|
-
with:
|
|
143
|
-
gh_token: ${{ secrets.GH_TOKEN }}
|
|
144
|
-
issue_number: ${{ needs.manager.outputs.issue_number }}
|
|
145
|
-
base_branch: master
|
|
146
|
-
|
|
147
|
-
# Em push para master: bump patch + cria tag vX.Y.Z (dispara publish-npm.yml)
|
|
148
|
-
# Ignora commits de release ([skip bump]) para evitar loop.
|
|
149
|
-
version-tag:
|
|
150
|
-
name: Bump version + tag
|
|
151
|
-
if: github.ref == 'refs/heads/master' && !contains(github.event.head_commit.message, '[skip bump]')
|
|
152
|
-
runs-on: ubuntu-latest
|
|
153
|
-
steps:
|
|
154
|
-
- uses: actions/checkout@v4
|
|
155
|
-
with:
|
|
156
|
-
fetch-depth: 0
|
|
157
|
-
token: ${{ secrets.GH_TOKEN }}
|
|
158
|
-
|
|
159
|
-
- uses: actions/setup-node@v4
|
|
160
|
-
with:
|
|
161
|
-
node-version: '24'
|
|
162
|
-
|
|
163
|
-
- name: Bump patch + tag
|
|
164
|
-
run: |
|
|
165
|
-
git config user.name "github-actions[bot]"
|
|
166
|
-
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
167
|
-
npm version patch -m "chore(release): %s [skip bump]"
|
|
168
|
-
git push origin HEAD:master --follow-tags
|
|
19
|
+
- run: echo "Manager Worker paused by ControleOnline/agents-mcp#201"
|
|
@@ -2,44 +2,90 @@ name: Publish Package to npmjs
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
push:
|
|
5
|
-
tags:
|
|
6
|
-
- 'v*'
|
|
7
5
|
|
|
8
6
|
permissions:
|
|
7
|
+
contents: write
|
|
9
8
|
id-token: write
|
|
10
|
-
|
|
9
|
+
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.repository }}-npm-release
|
|
12
|
+
cancel-in-progress: false
|
|
11
13
|
|
|
12
14
|
jobs:
|
|
13
15
|
publish:
|
|
16
|
+
if: ${{ github.repository_owner == 'ControleOnline' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) }}
|
|
14
17
|
runs-on: ubuntu-latest
|
|
15
|
-
permissions:
|
|
16
|
-
id-token: write
|
|
17
|
-
contents: read
|
|
18
18
|
steps:
|
|
19
19
|
- uses: actions/checkout@v4
|
|
20
|
-
|
|
20
|
+
with:
|
|
21
|
+
ref: ${{ github.ref }}
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
- name: Validate package metadata and create version tag
|
|
24
|
+
id: release
|
|
25
|
+
env:
|
|
26
|
+
REF_TYPE: ${{ github.ref_type }}
|
|
27
|
+
REF_NAME: ${{ github.ref_name }}
|
|
28
|
+
EXPECTED_REPOSITORY: ${{ github.repository }}
|
|
29
|
+
run: |
|
|
30
|
+
node <<'NODE'
|
|
31
|
+
const fs = require('node:fs');
|
|
32
|
+
const cp = require('node:child_process');
|
|
33
|
+
const pkg = require('./package.json');
|
|
34
|
+
const expectedUrl = `https://github.com/${process.env.EXPECTED_REPOSITORY}`;
|
|
35
|
+
if (!/^\d+\.\d+\.\d+$/.test(String(pkg.version))) throw new Error(`Expected stable X.Y.Z, got ${pkg.version}`);
|
|
36
|
+
if (pkg.repository?.url !== expectedUrl) throw new Error(`repository.url must be ${expectedUrl}; got ${pkg.repository?.url}`);
|
|
37
|
+
const sourceSha = cp.execFileSync('git', ['rev-parse', 'HEAD'], {encoding:'utf8'}).trim();
|
|
38
|
+
let tag = '';
|
|
39
|
+
if (process.env.REF_TYPE === 'tag') {
|
|
40
|
+
tag = process.env.REF_NAME;
|
|
41
|
+
if (tag !== `v${pkg.version}`) throw new Error(`Tag ${tag} does not match package version ${pkg.version}`);
|
|
42
|
+
} else {
|
|
43
|
+
let previous = '';
|
|
44
|
+
try { previous = JSON.parse(cp.execFileSync('git', ['show', `${sourceSha}^:package.json`], {encoding:'utf8'})).version || ''; } catch {}
|
|
45
|
+
if (previous === pkg.version) { fs.appendFileSync(process.env.GITHUB_OUTPUT, 'release_tag=\n'); process.exit(0); }
|
|
46
|
+
const parts = String(pkg.version).split('.').map(Number);
|
|
47
|
+
if (previous) {
|
|
48
|
+
const old = String(previous).split('.').map(Number);
|
|
49
|
+
let cmp = 0; for (let i=0;i<3;i++) { if ((parts[i]||0)!==(old[i]||0)) { cmp=(parts[i]||0)>(old[i]||0)?1:-1; break; } }
|
|
50
|
+
if (cmp <= 0) throw new Error(`Version ${pkg.version} must be greater than first-parent version ${previous}`);
|
|
51
|
+
}
|
|
52
|
+
tag = `v${pkg.version}`;
|
|
53
|
+
const existing = cp.spawnSync('git', ['ls-remote', '--exit-code', '--refs', 'origin', `refs/tags/${tag}`], {encoding:'utf8'});
|
|
54
|
+
if (existing.status === 0) {
|
|
55
|
+
cp.execFileSync('git', ['fetch', 'origin', `refs/tags/${tag}:refs/tags/${tag}`], {stdio:'inherit'});
|
|
56
|
+
const tagged = cp.execFileSync('git', ['rev-parse', `${tag}^{commit}`], {encoding:'utf8'}).trim();
|
|
57
|
+
if (tagged !== sourceSha) throw new Error(`Immutable tag ${tag} already points at ${tagged}, not ${sourceSha}`);
|
|
58
|
+
} else {
|
|
59
|
+
cp.execFileSync('git', ['config', 'user.name', 'github-actions[bot]']);
|
|
60
|
+
cp.execFileSync('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com']);
|
|
61
|
+
cp.execFileSync('git', ['tag', '-a', tag, sourceSha, '-m', `Release ${pkg.name}@${pkg.version}`], {stdio:'inherit'});
|
|
62
|
+
cp.execFileSync('git', ['push', 'origin', `refs/tags/${tag}`], {stdio:'inherit'});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
fs.appendFileSync(process.env.GITHUB_OUTPUT, `release_tag=${tag}\n`);
|
|
66
|
+
console.log(`Validated ${pkg.name}@${pkg.version} from ${sourceSha}; release tag ${tag}`);
|
|
67
|
+
NODE
|
|
21
68
|
- uses: actions/setup-node@v4
|
|
69
|
+
if: steps.release.outputs.release_tag != ''
|
|
22
70
|
with:
|
|
23
|
-
node-version:
|
|
24
|
-
|
|
71
|
+
node-version: 24
|
|
25
72
|
- name: Ensure npm 11.5.1+ (OIDC support)
|
|
73
|
+
if: steps.release.outputs.release_tag != ''
|
|
26
74
|
run: |
|
|
27
|
-
npm install
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
unset NODE_AUTH_TOKEN NPM_TOKEN || true
|
|
33
|
-
rm -f .npmrc "$HOME/.npmrc" || true
|
|
34
|
-
npm config delete //registry.npmjs.org/:_authToken 2>/dev/null || true
|
|
35
|
-
npm config set registry https://registry.npmjs.org/
|
|
36
|
-
echo "NODE_AUTH_TOKEN=${NODE_AUTH_TOKEN-<unset>}"
|
|
37
|
-
echo "ACTIONS_ID_TOKEN_REQUEST_URL=${ACTIONS_ID_TOKEN_REQUEST_URL:+set}"
|
|
38
|
-
echo "ACTIONS_ID_TOKEN_REQUEST_TOKEN=${ACTIONS_ID_TOKEN_REQUEST_TOKEN:+set}"
|
|
39
|
-
npm config list 2>/dev/null | grep -E "registry|auth|token" || true
|
|
40
|
-
cat "$HOME/.npmrc" 2>/dev/null || echo "(no ~/.npmrc)"
|
|
41
|
-
|
|
42
|
-
- name: Publish to npmjs (Trusted Publishing / OIDC)
|
|
75
|
+
npm install --global npm@latest
|
|
76
|
+
npm --version
|
|
77
|
+
- name: Skip if version is already on npm
|
|
78
|
+
id: registry
|
|
79
|
+
if: steps.release.outputs.release_tag != ''
|
|
43
80
|
run: |
|
|
44
|
-
|
|
45
|
-
|
|
81
|
+
PACKAGE_NAME=$(node -p "require('./package.json').name")
|
|
82
|
+
PACKAGE_VERSION=$(node -p "require('./package.json').version")
|
|
83
|
+
if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version >/dev/null 2>&1; then
|
|
84
|
+
echo 'published=true' >> "$GITHUB_OUTPUT"
|
|
85
|
+
echo "$PACKAGE_NAME@$PACKAGE_VERSION is already published; skipping duplicate publish"
|
|
86
|
+
else
|
|
87
|
+
echo 'published=false' >> "$GITHUB_OUTPUT"
|
|
88
|
+
fi
|
|
89
|
+
- name: Publish to npmjs with trusted publishing
|
|
90
|
+
if: steps.release.outputs.release_tag != '' && steps.registry.outputs.published != 'true'
|
|
91
|
+
run: npm publish --access public --loglevel verbose
|
|
@@ -12,8 +12,10 @@ jobs:
|
|
|
12
12
|
steps:
|
|
13
13
|
- uses: actions/checkout@v5
|
|
14
14
|
|
|
15
|
-
- uses: actions/setup-node@v4
|
|
16
|
-
with:
|
|
17
|
-
node-version: '20.x'
|
|
18
|
-
|
|
19
|
-
- run: npm
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: '20.x'
|
|
18
|
+
|
|
19
|
+
- run: npm install --ignore-scripts --no-package-lock --no-audit --no-fund
|
|
20
|
+
|
|
21
|
+
- run: npm test
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Device Detail — sincronização do alias (nome) na UI após save
|
|
2
|
+
|
|
3
|
+
Documentação técnica da correção de feedback visual ao editar o **nome (alias)** do device na tela `/device-detail` (Manager).
|
|
4
|
+
|
|
5
|
+
**Issue de origem:** [app-community#382](https://github.com/ControleOnline/app-community/issues/382).
|
|
6
|
+
|
|
7
|
+
## Problema de negócio
|
|
8
|
+
|
|
9
|
+
Na tela de detalhe de dispositivo (`/device-detail`):
|
|
10
|
+
|
|
11
|
+
1. O usuário edita o **nome do device** e grava.
|
|
12
|
+
2. A alteração **é persistida** no backend (API `devices`).
|
|
13
|
+
3. A UI **não** refletia o novo nome até um **refresh** (F5) da página.
|
|
14
|
+
|
|
15
|
+
O feedback esperado é imediato: após save com sucesso, o valor exibido no header deve ser o novo alias **sem** reload.
|
|
16
|
+
|
|
17
|
+
## Papel no produto (`APP_TYPE`)
|
|
18
|
+
|
|
19
|
+
| Visão | O que este fluxo faz | O que **não** faz |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| **MANAGER** | Rota principal: lista Dispositivos → detalhe → editar alias → save; header atualiza na hora | Não altera permissões, tipos de device nem DeviceConfig de PDV/DISPLAY |
|
|
22
|
+
| **ADMIN** | Pode reutilizar a mesma página compartilhada de `ui-common` quando a visão importa `DeviceDetailPage` | Não é o alvo prioritário do smoke Manager |
|
|
23
|
+
| **POS / PPC / SHOP / DELIVERY / CRM** | Não expõem esta tela de edição de alias de device | Não devem assumir ownership da tela de device-detail |
|
|
24
|
+
|
|
25
|
+
Fronteiras: módulo compartilhado `ui-common`; rota consumida pelo **Manager** via `ui-manager`.
|
|
26
|
+
|
|
27
|
+
## Onde vive o código
|
|
28
|
+
|
|
29
|
+
| Peça | Caminho |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| Shell da página | `ui-common` → `src/react/pages/DeviceDetailPage.js` (orquestra hooks/seções; ≤ 500 linhas após modularização) |
|
|
32
|
+
| Header (alias edit/save) | `src/react/pages/Devices/detail/DeviceDetailHeader.js` |
|
|
33
|
+
| Ações (inclui `saveAlias`) | `src/react/pages/Devices/detail/useDeviceDetailActions.js` |
|
|
34
|
+
| Estado local + ref de skip | `src/react/pages/Devices/detail/useDeviceDetailStateA.js` (ou estado co-localizado nos hooks de detail) |
|
|
35
|
+
| Merge store pós-save | `src/react/utils/deviceAliasSync.js` → `buildDeviceAliasStoreUpdates` |
|
|
36
|
+
| Smoke browser | `src/tests/browser/manager/device-detail-alias-save.spec.js` |
|
|
37
|
+
| Unit | `src/tests/react/utils/deviceAliasSync.test.js` |
|
|
38
|
+
|
|
39
|
+
Rota Manager: `ui-manager` importa `DeviceDetailPage` de `@controleonline/ui-common`.
|
|
40
|
+
|
|
41
|
+
## Causa raiz (técnica)
|
|
42
|
+
|
|
43
|
+
Havia descompasso entre:
|
|
44
|
+
|
|
45
|
+
1. **Persistência** (`deviceActions.save({ id, alias })`) — ok no backend.
|
|
46
|
+
2. **Estado local** (`alias` / `aliasInput` no header).
|
|
47
|
+
3. **Store runtime** (`device` e `device_config`), de onde `initialAlias` é derivado:
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
initialAlias = currentDevice?.alias
|
|
51
|
+
|| currentDeviceConfig?.device?.alias
|
|
52
|
+
|| currentDevice?.device
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Um `useEffect` sincroniza o estado local a partir de `initialAlias` quando **não** está em modo edição:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (editingAlias) return;
|
|
60
|
+
if (skipAliasSyncFromStoreRef.current) {
|
|
61
|
+
skipAliasSyncFromStoreRef.current = false;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
setAlias(initialAlias || '');
|
|
65
|
+
setAliasInput(initialAlias || '');
|
|
66
|
+
}, [editingAlias, initialAlias]);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Se o save atualizava só o backend (ou só o state local) **sem** atualizar o store, na saída do modo edição o efeito reaplicava o `initialAlias` antigo → a UI “voltava” ao nome anterior até um reload completo recarregar o device do servidor.
|
|
70
|
+
|
|
71
|
+
## Correção canônica
|
|
72
|
+
|
|
73
|
+
Após `deviceActions.save` com sucesso:
|
|
74
|
+
|
|
75
|
+
1. Calcular o alias definitivo (`savedDevice?.alias || trimmed`).
|
|
76
|
+
2. Chamar `buildDeviceAliasStoreUpdates({ deviceId, nextAlias, runtimeDevice, runtimeDeviceConfig, savedDevice, normalizeEntityId })`.
|
|
77
|
+
3. Persistir no store:
|
|
78
|
+
- `deviceActions.setItem(mergedDevice)`
|
|
79
|
+
- `deviceConfigActions.setItem(nextDeviceConfig)` quando houver config aninhada com `device.alias`
|
|
80
|
+
4. Armar `skipAliasSyncFromStoreRef.current = true` **antes** de atualizar o state local (evita race com o `useEffect` no mesmo ciclo).
|
|
81
|
+
5. `setAlias(nextAlias)`, `setAliasInput(nextAlias)`, `setEditingAlias(false)`.
|
|
82
|
+
|
|
83
|
+
### `buildDeviceAliasStoreUpdates`
|
|
84
|
+
|
|
85
|
+
Responsável por manter **device** e **device_config.device** coerentes com o alias gravado, para que `initialAlias` derivado do store não “snap back” para o valor antigo.
|
|
86
|
+
|
|
87
|
+
- Mescla o device runtime (mesmo id) com o payload salvo e força `alias`.
|
|
88
|
+
- Se existir `runtimeDeviceConfig`, clona e atualiza `device.alias` aninhado.
|
|
89
|
+
|
|
90
|
+
## Fluxo (Mermaid)
|
|
91
|
+
|
|
92
|
+
```mermaid
|
|
93
|
+
sequenceDiagram
|
|
94
|
+
participant U as Usuário (Manager)
|
|
95
|
+
participant H as DeviceDetailHeader
|
|
96
|
+
participant A as useDeviceDetailActions.saveAlias
|
|
97
|
+
participant API as deviceActions.save
|
|
98
|
+
participant S as Store (device + device_config)
|
|
99
|
+
participant E as useEffect (sync initialAlias)
|
|
100
|
+
|
|
101
|
+
U->>H: Edita alias + grava
|
|
102
|
+
H->>A: onSaveAlias
|
|
103
|
+
A->>API: save({ id, alias })
|
|
104
|
+
API-->>A: savedDevice
|
|
105
|
+
A->>S: setItem(mergedDevice / nextDeviceConfig)
|
|
106
|
+
A->>A: skipAliasSyncFromStoreRef = true
|
|
107
|
+
A->>H: setAlias + setAliasInput + exit edit
|
|
108
|
+
Note over E: effect roda com editingAlias=false
|
|
109
|
+
E->>E: vê skip=true → consome flag e não sobrescreve
|
|
110
|
+
H-->>U: Header mostra novo nome (sem F5)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Critérios de aceite (referência)
|
|
114
|
+
|
|
115
|
+
- Em `/device-detail`, após editar o nome e gravar com sucesso, o nome no header passa a ser o novo valor **sem** refresh.
|
|
116
|
+
- Sem regressão em outros campos editáveis da mesma tela nem na listagem de dispositivos.
|
|
117
|
+
- Persistência no backend permanece correta.
|
|
118
|
+
- Evidência: smoke `device-detail-alias-save.spec.js` (edit → PUT → texto do header com `testID` `device-alias-text` / `device-alias-input` / `device-alias-save`).
|
|
119
|
+
|
|
120
|
+
## testIDs (smoke)
|
|
121
|
+
|
|
122
|
+
| testID | Uso |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| `device-alias-text` | Texto do alias no header (modo leitura) |
|
|
125
|
+
| `device-alias-input` | Input em modo edição |
|
|
126
|
+
| `device-alias-edit` | Botão para entrar em edição |
|
|
127
|
+
| `device-alias-save` | Botão de confirmar save |
|
|
128
|
+
|
|
129
|
+
## Fora de escopo desta página
|
|
130
|
+
|
|
131
|
+
- Redesign da tela device-detail.
|
|
132
|
+
- Novas funcionalidades de device além do feedback pós-save do alias.
|
|
133
|
+
- Contratos de API além do `save` de device já existente.
|
|
134
|
+
- Configuração de PDV/DISPLAY/PRINT (ver [Smoke helpers — device-configuracao](https://github.com/ControleOnline/app-community/wiki/Smoke-Helpers-Device-Configuracao)).
|
|
135
|
+
|
|
136
|
+
## Links cruzados
|
|
137
|
+
|
|
138
|
+
- Issue: [app-community#382](https://github.com/ControleOnline/app-community/issues/382)
|
|
139
|
+
- Wiki do app: [Home](https://github.com/ControleOnline/app-community/wiki) · ponte [ui-common](https://github.com/ControleOnline/app-community/wiki/ui-common)
|
|
140
|
+
- Smoke de listagem/navegação relacionada: `ui-common/src/tests/browser/manager/devices-current.spec.js`
|
|
141
|
+
- Code quality / limite ≤ 500 linhas: [agents-mcp code-quality](https://github.com/ControleOnline/agents-mcp/blob/master/skills/shared/code-quality.md)
|
package/package.json
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@controleonline/ui-common",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.88",
|
|
4
4
|
"author": "ControleOnline",
|
|
5
5
|
"description": "ControleOnline Common Quasar UI Component",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "src/react/index.js",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"test": "npm run test:receita-federal && npm run test:imports-status && npm run test:runtime-language && npm run test:runtime-footer && npm run test:runtime-footer-marquee && npm run test:invoice-presentation && npm run test:system-error-message && npm run test:bottom-navigation-toolbar && npm run test:copy-device-configs && npm run test:print-routing",
|
|
9
|
+
"test": "npm run test:receita-federal && npm run test:imports-status && npm run test:runtime-language && npm run test:runtime-footer && npm run test:runtime-footer-marquee && npm run test:invoice-presentation && npm run test:system-error-message && npm run test:bottom-navigation-toolbar && npm run test:copy-device-configs && npm run test:print-routing && npm run test:device-detail-dependencies",
|
|
10
10
|
"test:receita-federal": "node --test src/tests/react/pages/receitaFederalIntegrationContract.test.mjs",
|
|
11
|
-
"test:imports-status": "mkdir -p node_modules/@controleonline && ln -
|
|
11
|
+
"test:imports-status": "mkdir -p node_modules/@controleonline && rm -rf node_modules/@controleonline/ui-common && ln -s ../.. node_modules/@controleonline/ui-common && node --test src/tests/react/utils/importStatus.test.js",
|
|
12
12
|
"test:runtime-language": "node --test src/tests/react/utils/runtimeLanguage.test.js",
|
|
13
|
-
"test:runtime-footer": "node --test src/tests/react/utils/runtimeFooter.test.js",
|
|
13
|
+
"test:runtime-footer": "node --test src/tests/react/utils/runtimeFooter.test.js src/tests/react/utils/runtimeFooterText.test.js",
|
|
14
14
|
"test:invoice-presentation": "node --test src/tests/react/utils/invoicePresentation.test.mjs",
|
|
15
|
-
"test:system-error-message": "mkdir -p node_modules/@controleonline && ln -
|
|
15
|
+
"test:system-error-message": "mkdir -p node_modules/@controleonline && rm -rf node_modules/@controleonline/ui-common && ln -s ../.. node_modules/@controleonline/ui-common && node --test src/tests/react/utils/systemErrorMessage.test.js",
|
|
16
16
|
"test:coverage": "c8 --reporter=clover --report-dir coverage npm test",
|
|
17
17
|
"test:bottom-navigation-toolbar": "node --test src/tests/react/utils/bottomNavigationToolbar.test.js",
|
|
18
18
|
"test:copy-device-configs": "node --test src/tests/react/utils/copyDeviceConfigs.test.mjs",
|
|
19
19
|
"test:print-routing": "node --test src/tests/react/utils/printRouting.test.mjs src/tests/react/utils/printSpoolUtils.test.mjs",
|
|
20
|
-
"test:runtime-footer-marquee": "node --test src/tests/react/components/RuntimeFooterMarqueeText.test.js"
|
|
20
|
+
"test:runtime-footer-marquee": "node --test src/tests/react/components/RuntimeFooterMarqueeText.test.js src/tests/react/components/RuntimeFooterMarqueeText.build.test.js",
|
|
21
|
+
"test:device-detail-dependencies": "node --test src/tests/react/pages/deviceDetailLoaderDependencies.test.mjs"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
|
-
"c8": "^10.1.3"
|
|
24
|
+
"c8": "^10.1.3",
|
|
25
|
+
"react": "^19.1.0",
|
|
26
|
+
"react-test-renderer": "^19.1.0",
|
|
27
|
+
"@babel/core": "^7.26.0",
|
|
28
|
+
"babel-preset-expo": "~54.0.10"
|
|
24
29
|
},
|
|
25
30
|
"repository": {
|
|
26
31
|
"type": "git",
|
|
27
|
-
"url": "https://github.com/ControleOnline/ui-common
|
|
32
|
+
"url": "https://github.com/ControleOnline/ui-common"
|
|
28
33
|
},
|
|
29
34
|
"bugs": "",
|
|
30
35
|
"homepage": "https://www.controleonline.com/devs/plugins",
|