feat: 優化庫存調撥單操作紀錄與 UI 佈局
This commit is contained in:
@@ -30,6 +30,7 @@ class ActivityLogController extends Controller
|
|||||||
'App\Modules\Production\Models\ProductionOrderItem' => '工單品項',
|
'App\Modules\Production\Models\ProductionOrderItem' => '工單品項',
|
||||||
'App\Modules\Inventory\Models\InventoryCountDoc' => '庫存盤點單',
|
'App\Modules\Inventory\Models\InventoryCountDoc' => '庫存盤點單',
|
||||||
'App\Modules\Inventory\Models\InventoryAdjustDoc' => '庫存盤調單',
|
'App\Modules\Inventory\Models\InventoryAdjustDoc' => '庫存盤調單',
|
||||||
|
'App\Modules\Inventory\Models\InventoryTransferOrder' => '庫存調撥單',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +84,7 @@ class ActivityLogController extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
$activities = $query->paginate($perPage)
|
$activities = $query->paginate($perPage)
|
||||||
|
->withQueryString()
|
||||||
->through(function ($activity) {
|
->through(function ($activity) {
|
||||||
$subjectMap = $this->getSubjectMap();
|
$subjectMap = $this->getSubjectMap();
|
||||||
|
|
||||||
|
|||||||
@@ -82,50 +82,9 @@ class TransferOrderController extends Controller
|
|||||||
auth()->id()
|
auth()->id()
|
||||||
);
|
);
|
||||||
|
|
||||||
// 記錄活動
|
|
||||||
activity()
|
|
||||||
->performedOn($order)
|
|
||||||
->causedBy(auth()->user())
|
|
||||||
->event('created')
|
|
||||||
->withProperties([
|
|
||||||
'attributes' => $order->toArray(),
|
|
||||||
'snapshot' => [
|
|
||||||
'doc_no' => $order->doc_no,
|
|
||||||
'from_warehouse_name' => $order->fromWarehouse?->name,
|
|
||||||
'to_warehouse_name' => $order->toWarehouse?->name,
|
|
||||||
]
|
|
||||||
])
|
|
||||||
->log('created');
|
|
||||||
|
|
||||||
// 如果請求包含單筆商品資訊
|
|
||||||
if ($request->has('product_id')) {
|
|
||||||
$this->transferService->updateItems($order, [[
|
|
||||||
'product_id' => $validated['product_id'],
|
|
||||||
'quantity' => $validated['quantity'],
|
|
||||||
'batch_number' => $validated['batch_number'] ?? null,
|
|
||||||
]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是撥補單,執行直接過帳
|
|
||||||
if ($request->input('instant_post') === true) {
|
if ($request->input('instant_post') === true) {
|
||||||
try {
|
try {
|
||||||
$this->transferService->post($order, auth()->id());
|
$this->transferService->post($order, auth()->id());
|
||||||
|
|
||||||
// 記錄過帳活動
|
|
||||||
activity()
|
|
||||||
->performedOn($order)
|
|
||||||
->causedBy(auth()->user())
|
|
||||||
->event('posted')
|
|
||||||
->withProperties([
|
|
||||||
'attributes' => ['status' => 'posted'],
|
|
||||||
'old' => ['status' => 'draft'],
|
|
||||||
'snapshot' => [
|
|
||||||
'doc_no' => $order->doc_no,
|
|
||||||
'from_warehouse_name' => $order->fromWarehouse?->name,
|
|
||||||
'to_warehouse_name' => $order->toWarehouse?->name,
|
|
||||||
]
|
|
||||||
])
|
|
||||||
->log('posted');
|
|
||||||
|
|
||||||
return redirect()->back()->with('success', '撥補成功,庫存已更新');
|
return redirect()->back()->with('success', '撥補成功,庫存已更新');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
@@ -185,60 +144,35 @@ class TransferOrderController extends Controller
|
|||||||
return redirect()->back()->with('error', '只能修改草稿狀態的單據');
|
return redirect()->back()->with('error', '只能修改草稿狀態的單據');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->input('action') === 'post') {
|
|
||||||
try {
|
|
||||||
$this->transferService->post($order, auth()->id());
|
|
||||||
|
|
||||||
// 記錄活動
|
|
||||||
activity()
|
|
||||||
->performedOn($order)
|
|
||||||
->causedBy(auth()->user())
|
|
||||||
->event('posted')
|
|
||||||
->withProperties([
|
|
||||||
'attributes' => ['status' => 'posted'],
|
|
||||||
'old' => ['status' => 'draft'],
|
|
||||||
'snapshot' => [
|
|
||||||
'doc_no' => $order->doc_no,
|
|
||||||
'from_warehouse_name' => $order->fromWarehouse?->name,
|
|
||||||
'to_warehouse_name' => $order->toWarehouse?->name,
|
|
||||||
]
|
|
||||||
])
|
|
||||||
->log('posted');
|
|
||||||
|
|
||||||
return redirect()->route('inventory.transfer.index')
|
|
||||||
->with('success', '調撥單已過帳完成');
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
return redirect()->back()->withErrors(['items' => $e->getMessage()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'items' => 'array',
|
'items' => 'array',
|
||||||
'items.*.product_id' => 'required|exists:products,id',
|
'items.*.product_id' => 'required|exists:products,id',
|
||||||
'items.*.quantity' => 'required|numeric|min:0.01',
|
'items.*.quantity' => 'required|numeric|min:0.01',
|
||||||
'items.*.batch_number' => 'nullable|string',
|
'items.*.batch_number' => 'nullable|string',
|
||||||
'items.*.notes' => 'nullable|string',
|
'items.*.notes' => 'nullable|string',
|
||||||
|
'remarks' => 'nullable|string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 1. 先更新資料
|
||||||
if ($request->has('items')) {
|
if ($request->has('items')) {
|
||||||
$this->transferService->updateItems($order, $validated['items']);
|
$this->transferService->updateItems($order, $validated['items']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$order->update($request->only(['remarks']));
|
$order->fill($request->only(['remarks']));
|
||||||
|
|
||||||
|
// [IMPORTANT] 使用 touch() 確保即便只有品項異動,也會因為 updated_at 變更而觸發自動日誌
|
||||||
|
$order->touch();
|
||||||
|
|
||||||
// 記錄暫存活動
|
// 2. 判斷是否需要過帳
|
||||||
activity()
|
if ($request->input('action') === 'post') {
|
||||||
->performedOn($order)
|
try {
|
||||||
->causedBy(auth()->user())
|
$this->transferService->post($order, auth()->id());
|
||||||
->event('updated')
|
return redirect()->route('inventory.transfer.index')
|
||||||
->withProperties([
|
->with('success', '調撥單已過帳完成');
|
||||||
'snapshot' => [
|
} catch (\Exception $e) {
|
||||||
'doc_no' => $order->doc_no,
|
return redirect()->back()->withErrors(['items' => $e->getMessage()]);
|
||||||
'from_warehouse_name' => $order->fromWarehouse?->name,
|
}
|
||||||
'to_warehouse_name' => $order->toWarehouse?->name,
|
}
|
||||||
]
|
|
||||||
])
|
|
||||||
->log('updated_items');
|
|
||||||
|
|
||||||
return redirect()->back()->with('success', '儲存成功');
|
return redirect()->back()->with('success', '儲存成功');
|
||||||
}
|
}
|
||||||
@@ -249,20 +183,6 @@ class TransferOrderController extends Controller
|
|||||||
return redirect()->back()->with('error', '只能刪除草稿狀態的單據');
|
return redirect()->back()->with('error', '只能刪除草稿狀態的單據');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 記錄活動
|
|
||||||
activity()
|
|
||||||
->performedOn($order)
|
|
||||||
->causedBy(auth()->user())
|
|
||||||
->event('deleted')
|
|
||||||
->withProperties([
|
|
||||||
'snapshot' => [
|
|
||||||
'doc_no' => $order->doc_no,
|
|
||||||
'from_warehouse_name' => $order->fromWarehouse?->name,
|
|
||||||
'to_warehouse_name' => $order->toWarehouse?->name,
|
|
||||||
]
|
|
||||||
])
|
|
||||||
->log('deleted');
|
|
||||||
|
|
||||||
$order->items()->delete();
|
$order->items()->delete();
|
||||||
$order->delete();
|
$order->delete();
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,106 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Modules\Inventory\Models;
|
namespace App\Modules\Inventory\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Spatie\Activitylog\Traits\LogsActivity;
|
||||||
|
use Spatie\Activitylog\LogOptions;
|
||||||
use App\Modules\Core\Models\User;
|
use App\Modules\Core\Models\User;
|
||||||
|
|
||||||
class InventoryTransferOrder extends Model
|
class InventoryTransferOrder extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory, LogsActivity;
|
||||||
|
|
||||||
|
public function getActivitylogOptions(): LogOptions
|
||||||
|
{
|
||||||
|
return LogOptions::defaults()
|
||||||
|
->logFillable()
|
||||||
|
->dontSubmitEmptyLogs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array 暫存的活動紀錄屬性 (不會存入資料庫)
|
||||||
|
*/
|
||||||
|
public $activityProperties = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定義日誌屬性名稱解析
|
||||||
|
*/
|
||||||
|
public function tapActivity(\Spatie\Activitylog\Models\Activity $activity, string $eventName)
|
||||||
|
{
|
||||||
|
$properties = $activity->properties->toArray();
|
||||||
|
|
||||||
|
// 處置日誌事件說明
|
||||||
|
if ($eventName === 'created') {
|
||||||
|
$activity->description = 'created';
|
||||||
|
} elseif ($eventName === 'updated') {
|
||||||
|
// 如果屬性中有 status 且變更為 completed,將描述改為 posted
|
||||||
|
if (isset($properties['attributes']['status']) && $properties['attributes']['status'] === 'completed') {
|
||||||
|
$activity->description = 'posted';
|
||||||
|
$eventName = 'posted'; // 供後續快照邏輯判定
|
||||||
|
} else {
|
||||||
|
$activity->description = 'updated';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 處理倉庫 ID 轉名稱
|
||||||
|
$idToNameFields = [
|
||||||
|
'from_warehouse_id' => 'fromWarehouse',
|
||||||
|
'to_warehouse_id' => 'toWarehouse',
|
||||||
|
'created_by' => 'createdBy',
|
||||||
|
'posted_by' => 'postedBy',
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach (['attributes', 'old'] as $part) {
|
||||||
|
if (isset($properties[$part])) {
|
||||||
|
foreach ($idToNameFields as $idField => $relation) {
|
||||||
|
if (isset($properties[$part][$idField])) {
|
||||||
|
$id = $properties[$part][$idField];
|
||||||
|
$nameField = str_replace('_id', '_name', $idField);
|
||||||
|
|
||||||
|
$name = null;
|
||||||
|
if ($this->relationLoaded($relation) && $this->$relation && $this->$relation->id == $id) {
|
||||||
|
$name = $this->$relation->name;
|
||||||
|
} else {
|
||||||
|
$model = $this->$relation()->getRelated()->find($id);
|
||||||
|
$name = $model ? $model->name : "ID: $id";
|
||||||
|
}
|
||||||
|
$properties[$part][$nameField] = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基本單據資訊快照 (包含單號、來源、目的地)
|
||||||
|
if (in_array($eventName, ['created', 'updated', 'posted', 'deleted'])) {
|
||||||
|
$properties['snapshot'] = [
|
||||||
|
'doc_no' => $this->doc_no,
|
||||||
|
'from_warehouse_name' => $this->fromWarehouse?->name,
|
||||||
|
'to_warehouse_name' => $this->toWarehouse?->name,
|
||||||
|
'status' => $this->status,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除輔助欄位與雜訊
|
||||||
|
if (isset($properties['attributes'])) {
|
||||||
|
unset($properties['attributes']['from_warehouse_name']);
|
||||||
|
unset($properties['attributes']['to_warehouse_name']);
|
||||||
|
unset($properties['attributes']['activityProperties']);
|
||||||
|
unset($properties['attributes']['updated_at']);
|
||||||
|
}
|
||||||
|
if (isset($properties['old'])) {
|
||||||
|
unset($properties['old']['updated_at']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合併暫存屬性 (例如 items_diff)
|
||||||
|
if (!empty($this->activityProperties)) {
|
||||||
|
$properties = array_merge($properties, $this->activityProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
$activity->properties = collect($properties);
|
||||||
|
}
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'doc_no',
|
'doc_no',
|
||||||
|
|||||||
@@ -28,18 +28,94 @@ class TransferService
|
|||||||
/**
|
/**
|
||||||
* 更新調撥單明細
|
* 更新調撥單明細
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* 更新調撥單明細 (支援精確 Diff 與自動日誌整合)
|
||||||
|
*/
|
||||||
public function updateItems(InventoryTransferOrder $order, array $itemsData): void
|
public function updateItems(InventoryTransferOrder $order, array $itemsData): void
|
||||||
{
|
{
|
||||||
DB::transaction(function () use ($order, $itemsData) {
|
DB::transaction(function () use ($order, $itemsData) {
|
||||||
|
// 1. 準備舊資料索引 (Key: product_id . '_' . batch_number)
|
||||||
|
$oldItemsMap = $order->items->mapWithKeys(function ($item) {
|
||||||
|
$key = $item->product_id . '_' . ($item->batch_number ?? '');
|
||||||
|
return [$key => $item];
|
||||||
|
});
|
||||||
|
|
||||||
|
$diff = [
|
||||||
|
'added' => [],
|
||||||
|
'removed' => [],
|
||||||
|
'updated' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
// 2. 處理新資料 (Deleted and Re-inserted currently for simplicity, but logic simulates update)
|
||||||
|
// 為了保持 ID 當作外鍵的穩定性,最佳做法是 update 存在的,create 新的,delete 舊的。
|
||||||
|
// 但考量現有邏輯是 delete all -> create all,我們維持原策略但優化 Diff 計算。
|
||||||
|
|
||||||
|
// 由於採用全刪重建,我們必須手動計算 Diff
|
||||||
$order->items()->delete();
|
$order->items()->delete();
|
||||||
|
|
||||||
|
$newItemsKeys = [];
|
||||||
|
|
||||||
foreach ($itemsData as $data) {
|
foreach ($itemsData as $data) {
|
||||||
$order->items()->create([
|
$key = $data['product_id'] . '_' . ($data['batch_number'] ?? '');
|
||||||
|
$newItemsKeys[] = $key;
|
||||||
|
|
||||||
|
$item = $order->items()->create([
|
||||||
'product_id' => $data['product_id'],
|
'product_id' => $data['product_id'],
|
||||||
'batch_number' => $data['batch_number'] ?? null,
|
'batch_number' => $data['batch_number'] ?? null,
|
||||||
'quantity' => $data['quantity'],
|
'quantity' => $data['quantity'],
|
||||||
'notes' => $data['notes'] ?? null,
|
'notes' => $data['notes'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
// Eager load product for name
|
||||||
|
$item->load('product');
|
||||||
|
|
||||||
|
// 比對邏輯
|
||||||
|
if ($oldItemsMap->has($key)) {
|
||||||
|
$oldItem = $oldItemsMap->get($key);
|
||||||
|
// 檢查數值是否有變動
|
||||||
|
if ((float)$oldItem->quantity !== (float)$data['quantity'] ||
|
||||||
|
$oldItem->notes !== ($data['notes'] ?? null)) {
|
||||||
|
|
||||||
|
$diff['updated'][] = [
|
||||||
|
'product_name' => $item->product->name,
|
||||||
|
'old' => [
|
||||||
|
'quantity' => (float)$oldItem->quantity,
|
||||||
|
'notes' => $oldItem->notes,
|
||||||
|
],
|
||||||
|
'new' => [
|
||||||
|
'quantity' => (float)$data['quantity'],
|
||||||
|
'notes' => $item->notes,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 新增
|
||||||
|
$diff['added'][] = [
|
||||||
|
'product_name' => $item->product->name,
|
||||||
|
'new' => [
|
||||||
|
'quantity' => (float)$item->quantity,
|
||||||
|
'notes' => $item->notes,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 處理被移除的項目
|
||||||
|
foreach ($oldItemsMap as $key => $oldItem) {
|
||||||
|
if (!in_array($key, $newItemsKeys)) {
|
||||||
|
$diff['removed'][] = [
|
||||||
|
'product_name' => $oldItem->product->name,
|
||||||
|
'old' => [
|
||||||
|
'quantity' => (float)$oldItem->quantity,
|
||||||
|
'notes' => $oldItem->notes,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 將 Diff 注入到 Model 的暫存屬性中
|
||||||
|
// 如果 Diff 有內容,才注入
|
||||||
|
if (!empty($diff['added']) || !empty($diff['removed']) || !empty($diff['updated'])) {
|
||||||
|
$order->activityProperties['items_diff'] = $diff;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -49,6 +125,9 @@ class TransferService
|
|||||||
*/
|
*/
|
||||||
public function post(InventoryTransferOrder $order, int $userId): void
|
public function post(InventoryTransferOrder $order, int $userId): void
|
||||||
{
|
{
|
||||||
|
// [IMPORTANT] 強制重新載入品項,因為在 Controller 中可能剛執行過 updateItems,導致記憶體中快取的 items 是舊的或空的
|
||||||
|
$order->load('items.product');
|
||||||
|
|
||||||
DB::transaction(function () use ($order, $userId) {
|
DB::transaction(function () use ($order, $userId) {
|
||||||
$fromWarehouse = $order->fromWarehouse;
|
$fromWarehouse = $order->fromWarehouse;
|
||||||
$toWarehouse = $order->toWarehouse;
|
$toWarehouse = $order->toWarehouse;
|
||||||
@@ -131,11 +210,25 @@ class TransferService
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$order->update([
|
// 準備品項快照供日誌使用
|
||||||
'status' => 'completed',
|
$itemsSnapshot = $order->items->map(function($item) {
|
||||||
'posted_at' => now(),
|
return [
|
||||||
'posted_by' => $userId,
|
'product_name' => $item->product->name,
|
||||||
]);
|
'old' => [
|
||||||
|
'quantity' => (float)$item->quantity,
|
||||||
|
'notes' => $item->notes,
|
||||||
|
],
|
||||||
|
'new' => [
|
||||||
|
'quantity' => (float)$item->quantity,
|
||||||
|
'notes' => $item->notes,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
})->toArray();
|
||||||
|
|
||||||
|
$order->status = 'completed';
|
||||||
|
$order->posted_at = now();
|
||||||
|
$order->posted_by = $userId;
|
||||||
|
$order->save(); // 觸發自動日誌
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -171,6 +171,8 @@ const statusMap: Record<string, string> = {
|
|||||||
// 生產工單狀態
|
// 生產工單狀態
|
||||||
planned: '已計畫',
|
planned: '已計畫',
|
||||||
in_progress: '生產中',
|
in_progress: '生產中',
|
||||||
|
// 調撥單狀態
|
||||||
|
voided: '已作廢',
|
||||||
// completed 已定義
|
// completed 已定義
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -223,12 +225,21 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P
|
|||||||
return a.localeCompare(b);
|
return a.localeCompare(b);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 檢查鍵是否為快照名稱欄位的輔助函式
|
// 檢查鍵是否為快照名稱欄位或輔助名稱欄位的輔助函式
|
||||||
const isSnapshotField = (key: string) => {
|
const isSnapshotField = (key: string) => {
|
||||||
return [
|
// 隱藏快照欄位
|
||||||
|
const snapshotFields = [
|
||||||
'category_name', 'base_unit_name', 'large_unit_name', 'purchase_unit_name',
|
'category_name', 'base_unit_name', 'large_unit_name', 'purchase_unit_name',
|
||||||
'warehouse_name', 'user_name'
|
'warehouse_name', 'user_name', 'from_warehouse_name', 'to_warehouse_name',
|
||||||
].includes(key);
|
'created_by_name', 'updated_by_name', 'completed_by_name', 'posted_by_name'
|
||||||
|
];
|
||||||
|
|
||||||
|
if (snapshotFields.includes(key)) return true;
|
||||||
|
|
||||||
|
// 隱藏所有以 _name 結尾的欄位(因為它們通常是 ID 欄位的文字補充)
|
||||||
|
if (key.endsWith('_name')) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getEventBadgeClass = (event: string) => {
|
const getEventBadgeClass = (event: string) => {
|
||||||
@@ -343,7 +354,7 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-3xl max-h-[90vh] overflow-y-auto p-0 gap-0">
|
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto p-0 gap-0">
|
||||||
<DialogHeader className="p-6 pb-4 border-b pr-12">
|
<DialogHeader className="p-6 pb-4 border-b pr-12">
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
<DialogTitle className="text-xl font-bold text-gray-900">
|
<DialogTitle className="text-xl font-bold text-gray-900">
|
||||||
@@ -385,12 +396,12 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P
|
|||||||
<div className="bg-gray-50/50 p-6 min-h-[300px]">
|
<div className="bg-gray-50/50 p-6 min-h-[300px]">
|
||||||
{activity.event === 'created' ? (
|
{activity.event === 'created' ? (
|
||||||
<div className="border rounded-md overflow-hidden bg-white shadow-sm">
|
<div className="border rounded-md overflow-hidden bg-white shadow-sm">
|
||||||
<Table>
|
<Table className="table-fixed w-full">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow className="bg-gray-50/50 hover:bg-gray-50/50">
|
<TableRow className="bg-gray-50/50 hover:bg-gray-50/50">
|
||||||
<TableHead className="w-[150px]">欄位</TableHead>
|
<TableHead className="w-[140px]">欄位</TableHead>
|
||||||
<TableHead>異動前</TableHead>
|
<TableHead className="w-1/2">異動前</TableHead>
|
||||||
<TableHead>異動後</TableHead>
|
<TableHead className="w-1/2">異動後</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
@@ -398,9 +409,9 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P
|
|||||||
.filter(key => attributes[key] !== null && attributes[key] !== '' && !isSnapshotField(key))
|
.filter(key => attributes[key] !== null && attributes[key] !== '' && !isSnapshotField(key))
|
||||||
.map((key) => (
|
.map((key) => (
|
||||||
<TableRow key={key}>
|
<TableRow key={key}>
|
||||||
<TableCell className="font-medium text-gray-700 w-[120px] shrink-0">{getFieldLabel(key)}</TableCell>
|
<TableCell className="font-medium text-gray-700 w-[140px] truncate">{getFieldLabel(key)}</TableCell>
|
||||||
<TableCell className="text-gray-500 break-all min-w-[150px]">-</TableCell>
|
<TableCell className="text-gray-500">-</TableCell>
|
||||||
<TableCell className="text-gray-900 font-medium break-all min-w-[200px] whitespace-pre-wrap">
|
<TableCell className="text-gray-900 font-medium break-all whitespace-pre-wrap">
|
||||||
{getFormattedValue(key, attributes[key])}
|
{getFormattedValue(key, attributes[key])}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -417,12 +428,12 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="border rounded-md overflow-hidden bg-white shadow-sm">
|
<div className="border rounded-md overflow-hidden bg-white shadow-sm">
|
||||||
<Table>
|
<Table className="table-fixed w-full">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow className="bg-gray-50 hover:bg-gray-50">
|
<TableRow className="bg-gray-50 hover:bg-gray-50">
|
||||||
<TableHead className="w-[150px]">欄位</TableHead>
|
<TableHead className="w-[140px]">欄位</TableHead>
|
||||||
<TableHead>異動前</TableHead>
|
<TableHead className="w-1/2">異動前</TableHead>
|
||||||
<TableHead>異動後</TableHead>
|
<TableHead className="w-1/2">異動後</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
@@ -456,11 +467,11 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow key={key} className={isChanged ? 'bg-amber-50/30 hover:bg-amber-50/50' : 'hover:bg-gray-50/50'}>
|
<TableRow key={key} className={isChanged ? 'bg-amber-50/30 hover:bg-amber-50/50' : 'hover:bg-gray-50/50'}>
|
||||||
<TableCell className="font-medium text-gray-700 w-[120px] shrink-0">{getFieldLabel(key)}</TableCell>
|
<TableCell className="font-medium text-gray-700 w-[140px] truncate">{getFieldLabel(key)}</TableCell>
|
||||||
<TableCell className="text-gray-500 break-all min-w-[150px] whitespace-pre-wrap">
|
<TableCell className="text-gray-500 break-all whitespace-pre-wrap">
|
||||||
{displayBefore}
|
{displayBefore}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-gray-900 font-medium break-all min-w-[200px] whitespace-pre-wrap">
|
<TableCell className="text-gray-900 font-medium break-all whitespace-pre-wrap">
|
||||||
{displayAfter}
|
{displayAfter}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
@@ -486,12 +497,12 @@ export default function ActivityDetailDialog({ open, onOpenChange, activity }: P
|
|||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="border rounded-md overflow-hidden bg-white shadow-sm">
|
<div className="border rounded-md overflow-hidden bg-white shadow-sm">
|
||||||
<Table>
|
<Table className="table-fixed w-full">
|
||||||
<TableHeader className="bg-gray-50/50">
|
<TableHeader className="bg-gray-50/50">
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>商品名稱</TableHead>
|
<TableHead className="w-1/3">商品名稱</TableHead>
|
||||||
<TableHead className="text-center">異動類型</TableHead>
|
<TableHead className="w-[100px] text-center">異動類型</TableHead>
|
||||||
<TableHead>異動詳情 (舊 → 新)</TableHead>
|
<TableHead className="w-1/2">異動詳情 (舊 → 新)</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export default function ActivityLogIndex({ activities, filters, subject_types, u
|
|||||||
setPerPage(value);
|
setPerPage(value);
|
||||||
router.get(
|
router.get(
|
||||||
route('activity-logs.index'),
|
route('activity-logs.index'),
|
||||||
{ ...filters, per_page: value },
|
{ ...filters, per_page: value, page: 1 },
|
||||||
{ preserveState: false, replace: true, preserveScroll: true }
|
{ preserveState: false, replace: true, preserveScroll: true }
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user