[FEAT] 交易訂單與電子發票列表新增可編輯備註欄位

1. 新增共用元件 x-remark-cell:列表內嵌備註編輯,點擊文字進入編輯,儲存走 AJAX PATCH 並以 toast 提示,不重整頁面;空白時顯示淡色「新增備註」提示。
2. 交易訂單列表(tab-orders)桌面表格於「發票號碼」與「操作」間新增備註欄,手機卡片於操作鈕上方新增備註區塊,接 order.remark,並同步調整空狀態 colspan。
3. 電子發票列表(tab-invoices)桌面表格於「關聯訂單」與「操作」間新增備註欄,手機卡片新增備註區塊,接 invoice.remark,並同步調整空狀態 colspan。
4. 列表與訂單詳情面板共用同一 remark 欄位,任一處編輯皆同步。
5. 新增三語系 key「Add Remark(新增備註)」,zh_TW/en/ja 對齊排序至 2573 鍵。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sky121113 2026-06-18 11:13:41 +08:00
parent 4766ec6939
commit ae4c9fb93a
6 changed files with 83 additions and 4 deletions

View File

@ -63,6 +63,7 @@
"Add Pass Code": "Add Pass Code",
"Add Pickup Code": "Add Pickup Code",
"Add Product": "Add Product",
"Add Remark": "Add Remark",
"Add Role": "Add Role",
"Add Staff Card": "Add Staff Card",
"Add Warehouse": "Add Warehouse",

View File

@ -63,6 +63,7 @@
"Add Pass Code": "パスコード追加",
"Add Pickup Code": "受取コード追加",
"Add Product": "商品追加",
"Add Remark": "備考を追加",
"Add Role": "ロール追加",
"Add Staff Card": "スタッフカード追加",
"Add Warehouse": "倉庫追加",

View File

@ -63,6 +63,7 @@
"Add Pass Code": "新增通行碼",
"Add Pickup Code": "新增取貨碼",
"Add Product": "新增商品",
"Add Remark": "新增備註",
"Add Role": "新增角色",
"Add Staff Card": "新增員工識別卡",
"Add Warehouse": "新增倉庫",

View File

@ -175,6 +175,10 @@
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Associated Order') }}
</th>
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Remark') }}
</th>
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-right">
{{ __('Action') }}
@ -235,6 +239,9 @@
{{ $invoice->order->order_no ?? '---' }}
</span>
</td>
<td class="px-6 py-6">
<x-remark-cell :url="route('admin.sales.invoices.remark', $invoice)" :value="$invoice->remark" />
</td>
<td class="px-6 py-6 text-right" @click.stop>
<div class="flex items-center justify-end gap-1.5">
@if(in_array($st, ['pending', 'failed']))
@ -275,8 +282,8 @@
</tr>
@empty
<tr>
<td colspan="6" class="py-20 text-center">
<x-empty-state mode="table" colspan="6" :message="__('No invoices found')" />
<td colspan="8" class="py-20 text-center">
<x-empty-state mode="table" colspan="8" :message="__('No invoices found')" />
</td>
</tr>
@endforelse
@ -364,6 +371,12 @@
</div>
</div>
{{-- Remark --}}
<div class="mb-6">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Remark') }}</p>
<x-remark-cell :url="route('admin.sales.invoices.remark', $invoice)" :value="$invoice->remark" />
</div>
{{-- Action Buttons --}}
<div class="flex flex-col gap-3">
<button @click="openDetail({{ $invoice->order_id }})"

View File

@ -179,6 +179,10 @@
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Invoice Number') }}
</th>
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Remark') }}
</th>
<th
class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-right">
{{ __('Action') }}
@ -308,6 +312,9 @@
<span class="text-xs font-bold text-slate-300 dark:text-slate-700 tracking-widest">---</span>
@endif
</td>
<td class="px-6 py-6">
<x-remark-cell :url="route('admin.sales.orders.remark', $order)" :value="$order->remark" />
</td>
<td class="px-6 py-6 text-right">
<button @click.stop="openDetail({{ $order->id }})"
class="p-2.5 rounded-xl text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all group/btn"
@ -324,8 +331,8 @@
</tr>
@empty
<tr>
<td colspan="8" class="py-20 text-center">
<x-empty-state mode="table" colspan="8" :message="__('No transaction orders found')" />
<td colspan="9" class="py-20 text-center">
<x-empty-state mode="table" colspan="9" :message="__('No transaction orders found')" />
</td>
</tr>
@endforelse
@ -456,6 +463,12 @@
</div>
</div>
{{-- Remark --}}
<div class="mb-6">
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">{{ __('Remark') }}</p>
<x-remark-cell :url="route('admin.sales.orders.remark', $order)" :value="$order->remark" />
</div>
{{-- Action Buttons --}}
<div class="flex items-center gap-3">
<button @click="openDetail({{ $order->id }})"

View File

@ -0,0 +1,50 @@
@props([
'url', // PATCH 端點(更新 remark
'value' => null, // 目前備註內容
])
{{-- 列表內嵌備註編輯點擊文字進入編輯fetch 儲存後 toast。@click.stop 避免觸發整列的開啟詳情。 --}}
<div @click.stop class="min-w-[150px] max-w-[220px]"
x-data="{
editing: false,
saving: false,
value: @js($value ?? ''),
original: @js($value ?? ''),
cancel() { this.value = this.original; this.editing = false; },
save() {
this.saving = true;
fetch(@js($url), {
method: 'PATCH',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': @js(csrf_token()), 'X-Requested-With': 'XMLHttpRequest' },
body: JSON.stringify({ remark: this.value })
}).then(r => { if (!r.ok) throw 0; return r.json(); })
.then(d => { this.original = this.value; this.editing = false; window.showToast?.((d && d.message) || @js(__('Remark updated')), 'success'); })
.catch(() => window.showToast?.(@js(__('Operation failed')), 'error'))
.finally(() => this.saving = false);
}
}">
<template x-if="!editing">
<button type="button" @click="editing = true" class="group/rm flex items-start gap-1.5 text-left w-full">
<span class="text-sm font-bold whitespace-pre-wrap break-words line-clamp-2"
:class="value ? 'text-slate-600 dark:text-slate-300' : 'text-slate-300 dark:text-slate-600'"
x-text="value || @js(__('Add Remark'))"></span>
<svg class="w-3.5 h-3.5 shrink-0 mt-0.5 text-slate-300 dark:text-slate-600 opacity-0 group-hover/rm:opacity-100 transition-opacity"
fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931z" />
</svg>
</button>
</template>
<template x-if="editing">
<div class="space-y-2">
<textarea x-model="value" rows="2" maxlength="1000"
placeholder="{{ __('Enter remark (optional)') }}"
class="luxury-input w-full text-xs py-2 px-3 resize-none"></textarea>
<div class="flex items-center gap-1.5">
<button type="button" @click="save()" :disabled="saving"
class="px-3 py-1.5 rounded-lg bg-cyan-500 hover:bg-cyan-600 text-white font-black text-[10px] uppercase tracking-widest disabled:opacity-60">{{ __('Save') }}</button>
<button type="button" @click="cancel()" :disabled="saving"
class="px-3 py-1.5 rounded-lg bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 font-black text-[10px] uppercase tracking-widest disabled:opacity-60">{{ __('Cancel') }}</button>
</div>
</div>
</template>
</div>