@chenchaolong/plugin-trade-compliance-workbench 0.1.12 → 0.1.13

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.
@@ -70,7 +70,6 @@
70
70
  const [selectedIds, setSelectedIds] = React.useState([])
71
71
  const [statusFilter, setStatusFilter] = React.useState('all')
72
72
  const [busy, setBusy] = React.useState(false)
73
- const [notice, setNotice] = React.useState('')
74
73
  const [query, setQuery] = React.useState('')
75
74
  const [showManualForm, setShowManualForm] = React.useState(false)
76
75
  const [form, setForm] = React.useState({ productName: '', hsCode: '', keywords: '', controlNote: '', enabled: true })
@@ -95,7 +94,7 @@
95
94
  }
96
95
  }, [])
97
96
 
98
- React.useEffect(reportResize, [data, activePage, selectedIds, statusFilter, busy, notice, showManualForm])
97
+ React.useEffect(reportResize, [data, activePage, selectedIds, statusFilter, busy, showManualForm])
99
98
 
100
99
  const reviewItems = data.reviewItems.filter((item) => !isPlaceholderReviewItem(item))
101
100
  const pendingItems = reviewItems.filter((item) => (item.reviewStatus || 'pending') === 'pending')
@@ -190,10 +189,12 @@
190
189
  if (!item || !item.id || busy) return
191
190
  setBusy(true)
192
191
  try {
193
- const response = await executeAction('reject_review_item', item.id, { itemId: item.id }, {})
192
+ const response = item.materializedOnly
193
+ ? await executeAction(materializedDeleteAction(item), null, { id: item.materializedId || parseMaterializedId(item.id) }, {})
194
+ : await executeAction('reject_review_item', item.id, { itemId: item.id }, {})
194
195
  assertActionSuccess(response)
195
196
  setSelectedIds(selectedIds.filter((id) => id !== item.id))
196
- showNotice('审核项已驳回。', 'success')
197
+ showNotice(item.materializedOnly ? '记录已驳回并移出正式列表。' : '审核项已驳回。', 'success')
197
198
  await reload()
198
199
  } catch (error) {
199
200
  showNotice(getErrorMessage(error), 'error')
@@ -204,12 +205,54 @@
204
205
 
205
206
  async function batchApproveSelected() {
206
207
  if (!selectedIds.length || busy) return
208
+ const rows = getSelectedRows().filter((row) => !row.materializedOnly && row.reviewStatus !== 'confirmed' && row.reviewStatus !== 'rejected')
209
+ if (!rows.length) return
207
210
  setBusy(true)
208
211
  try {
209
- const response = await executeAction('confirm_review_items', null, { itemIds: selectedIds }, {})
212
+ const response = await executeAction('confirm_review_items', null, { itemIds: rows.map((row) => row.id) }, {})
210
213
  assertActionSuccess(response)
211
214
  setSelectedIds([])
212
- showNotice(`已批量审核 ${selectedIds.length} 条记录。`, 'success')
215
+ showNotice(`已批量审核 ${rows.length} 条记录。`, 'success')
216
+ await reload()
217
+ } catch (error) {
218
+ showNotice(getErrorMessage(error), 'error')
219
+ } finally {
220
+ setBusy(false)
221
+ }
222
+ }
223
+
224
+ async function batchRejectSelected() {
225
+ const rows = getSelectedRows().filter((row) => row.reviewStatus !== 'rejected')
226
+ if (!rows.length || busy) return
227
+ setBusy(true)
228
+ try {
229
+ for (const row of rows) {
230
+ if (row.materializedOnly) {
231
+ await executeAction(materializedDeleteAction(row), null, { id: row.materializedId || parseMaterializedId(row.id) }, {})
232
+ } else {
233
+ await executeAction('reject_review_item', row.id, { itemId: row.id }, {})
234
+ }
235
+ }
236
+ setSelectedIds([])
237
+ showNotice(`已批量驳回 ${rows.length} 条记录。`, 'success')
238
+ await reload()
239
+ } catch (error) {
240
+ showNotice(getErrorMessage(error), 'error')
241
+ } finally {
242
+ setBusy(false)
243
+ }
244
+ }
245
+
246
+ async function batchDeleteSelected() {
247
+ const rows = getSelectedRows()
248
+ if (!rows.length || busy) return
249
+ setBusy(true)
250
+ try {
251
+ for (const row of rows) {
252
+ await deleteRowRequest(row)
253
+ }
254
+ setSelectedIds([])
255
+ showNotice(`已批量删除 ${rows.length} 条记录。`, 'success')
213
256
  await reload()
214
257
  } catch (error) {
215
258
  showNotice(getErrorMessage(error), 'error')
@@ -220,14 +263,9 @@
220
263
 
221
264
  async function deleteRow(item) {
222
265
  if (!item || !item.id || busy) return
223
- const actionKey = item.materializedOnly
224
- ? (item.type === 'controlled_goods' ? 'delete_controlled_goods' : 'delete_supplier_product')
225
- : 'delete_review_item'
226
- const input = item.materializedOnly ? { id: item.materializedId || parseMaterializedId(item.id) } : { itemId: item.id }
227
266
  setBusy(true)
228
267
  try {
229
- const response = await executeAction(actionKey, null, input, {})
230
- assertActionSuccess(response)
268
+ await deleteRowRequest(item)
231
269
  setSelectedIds(selectedIds.filter((id) => id !== item.id))
232
270
  showNotice('记录已删除。', 'success')
233
271
  await reload()
@@ -238,6 +276,14 @@
238
276
  }
239
277
  }
240
278
 
279
+ async function deleteRowRequest(item) {
280
+ const actionKey = item.materializedOnly ? materializedDeleteAction(item) : 'delete_review_item'
281
+ const input = item.materializedOnly ? { id: item.materializedId || parseMaterializedId(item.id) } : { itemId: item.id }
282
+ const response = await executeAction(actionKey, null, input, {})
283
+ assertActionSuccess(response)
284
+ return response
285
+ }
286
+
241
287
  function openEdit(item) {
242
288
  const data = readMerged(item)
243
289
  setEditItem(item)
@@ -340,9 +386,7 @@
340
386
  }
341
387
 
342
388
  function showNotice(message, level) {
343
- setNotice((level === 'error' ? '错误:' : '') + message)
344
389
  notify(level || 'info', message)
345
- setTimeout(() => setNotice(''), 4500)
346
390
  }
347
391
 
348
392
  function startRecognitionPolling(expectedType, previousCount) {
@@ -381,7 +425,6 @@
381
425
  h('button', { className: 'tcw-btn tcw-btn-soft', disabled: busy, onClick: () => reload() }, busy ? '处理中' : '刷新')
382
426
  )
383
427
  ),
384
- notice ? h('div', { className: 'tcw-notice' }, notice) : null,
385
428
  h('nav', { className: 'tcw-tabs tcw-frame' }, [
386
429
  tab('overview-page', '工作台总览'),
387
430
  tab('controlled-goods-page', '管控商品'),
@@ -571,7 +614,9 @@
571
614
  h('option', { value: 'rejected' }, '已驳回')
572
615
  )
573
616
  ),
574
- h('button', { className: 'tcw-btn tcw-btn-primary', disabled: busy || selectedIds.length === 0, onClick: batchApproveSelected }, `批量审核${selectedIds.length ? ` (${selectedIds.length})` : ''}`)
617
+ h('button', { className: 'tcw-btn tcw-btn-primary', disabled: busy || selectedIds.length === 0, onClick: batchApproveSelected }, `批量审核${selectedIds.length ? ` (${selectedIds.length})` : ''}`),
618
+ h('button', { className: 'tcw-btn tcw-btn-soft', disabled: busy || selectedIds.length === 0, onClick: batchRejectSelected }, '批量驳回'),
619
+ h('button', { className: 'tcw-btn tcw-btn-danger', disabled: busy || selectedIds.length === 0, onClick: batchDeleteSelected }, '批量删除')
575
620
  )
576
621
  )
577
622
  }
@@ -619,7 +664,7 @@
619
664
  className: 'row-selection-checkbox',
620
665
  type: 'checkbox',
621
666
  checked: selectedIds.includes(row.id),
622
- disabled: !row.id || row.materializedOnly || row.reviewStatus === 'confirmed',
667
+ disabled: !row.id,
623
668
  onChange: (event) => toggleSelected(row.id, event.target.checked)
624
669
  }),
625
670
  ...mapRow(row),
@@ -647,10 +692,26 @@
647
692
 
648
693
  function getSelectableRowIds(rows) {
649
694
  return (rows || [])
650
- .filter((row) => row.id && !row.materializedOnly && row.reviewStatus !== 'confirmed')
695
+ .filter((row) => row.id)
651
696
  .map((row) => row.id)
652
697
  }
653
698
 
699
+ function getSelectedRows() {
700
+ const rows = activePage === 'controlled-goods-page'
701
+ ? controlledReviews
702
+ : activePage === 'products-page'
703
+ ? supplierReviews
704
+ : activePage === 'workbooks-page'
705
+ ? salesReviews
706
+ : []
707
+ const selected = new Set(selectedIds)
708
+ return rows.filter((row) => selected.has(row.id))
709
+ }
710
+
711
+ function materializedDeleteAction(item) {
712
+ return item.type === 'controlled_goods' ? 'delete_controlled_goods' : 'delete_supplier_product'
713
+ }
714
+
654
715
  function paginateRows(listKey, rows) {
655
716
  const pageSize = 10
656
717
  const total = rows.length
@@ -1065,10 +1126,10 @@ button, input { font: inherit; letter-spacing: 0; }
1065
1126
  .tcw-btn-primary { border-color: var(--tcw-primary); background: var(--tcw-primary); color: #fff; }
1066
1127
  .tcw-btn-primary:hover:not(:disabled) { background: var(--tcw-primary-dark); }
1067
1128
  .tcw-btn-soft { background: var(--tcw-soft); }
1129
+ .tcw-btn-danger { border-color: color-mix(in srgb, var(--tcw-danger) 35%, var(--tcw-border)); background: color-mix(in srgb, var(--tcw-danger) 6%, var(--tcw-card)); color: var(--tcw-danger); }
1068
1130
  .tcw-wide { width: 100%; }
1069
1131
  .tcw-upload { border-style: dashed; border-color: color-mix(in srgb, var(--tcw-primary) 42%, var(--tcw-border)); color: var(--tcw-primary); font-weight: 800; }
1070
1132
  .tcw-upload input { display: none; }
1071
- .tcw-notice { border: 1px solid color-mix(in srgb, var(--tcw-primary) 24%, var(--tcw-border)); border-radius: 6px; background: color-mix(in srgb, var(--tcw-primary) 7%, var(--tcw-card)); padding: 10px 12px; color: var(--tcw-primary-dark); font-size: 13px; }
1072
1133
  .tcw-tabs { display: flex; gap: 6px; overflow-x: auto; border-bottom: 1px solid var(--tcw-border); padding: 0 2px; }
1073
1134
  .tcw-tab { min-height: 34px; border: 0; border-bottom: 3px solid transparent; background: transparent; color: var(--tcw-muted); padding: 7px 12px; cursor: pointer; font-weight: 900; }
1074
1135
  .tcw-tab.active { border-color: var(--tcw-primary); color: var(--tcw-primary); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chenchaolong/plugin-trade-compliance-workbench",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Trade Compliance Workbench app plugin for controlled goods review, supplier product management, and customs workbook generation.",
5
5
  "author": {
6
6
  "name": "XpertAI",