[FEAT] 標準化廣告管理 AJAX 與公司系統設定功能修復
1. 修復公司系統設定 Alpine.js 響應式屬性未初始化問題,確保 9 項功能開關可正確儲存。 2. 統一公司列表「啟用功能」標籤顏色為標準青色 (Cyan),提升視覺一致性。 3. 實作廣告管理模組 AJAX 分頁與搜尋功能,支援無刷新頁面更新。 4. 於廣告管理模組導入極簡奢華風 Loading 載入遮罩,與機台管理模組保持一致。 5. 更新多語系語系檔 (zh_TW, en, ja),補齊功能開關與載入提示文字。 6. 優化分頁元件 (luxury.blade.php),修復 ajax:navigate 事件觸發邏輯。
This commit is contained in:
parent
315c226560
commit
2f86bb90a2
@ -330,13 +330,15 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
||||
> [!TIP]
|
||||
> 當遇到未定義的 UI 區塊時,優先參考 `admin.dashboard.blade.php` 的卡片與即時動態實作方式進行衍生。
|
||||
|
||||
## 13. 加載指示器規範 (Loading Indicator Pattern)
|
||||
## 13. AJAX 交互與加載規範 (AJAX Interactions & Loaders)
|
||||
|
||||
當使用 AJAX 進行局部頁面刷新或異步操作時,必須使用統一的「極奢華加載遮罩」。
|
||||
為了提供無刷新的 SPA 式流暢體驗,所有管理後台模組必須遵循統一的 AJAX 交互模式。
|
||||
|
||||
### 13.1 極奢華多環加載器 (Premium Multi-Ring Loader)
|
||||
用於表格遮罩、局部更新或狀態切換。具備雙環旋轉與脈衝核心圖示。
|
||||
|
||||
### 13.1 標準 HTML 結構
|
||||
```html
|
||||
<!-- Loading Spinner Overlay -->
|
||||
<!-- Loading Overlay Pattern -->
|
||||
<div x-show="isLoadingTable"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
@ -362,91 +364,8 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
||||
</div>
|
||||
```
|
||||
|
||||
### 13.2 設計要點
|
||||
1. **多重動畫**:結合 `animate-spin` (不同速度/方向) 與 `animate-pulse`,營造出高科技且細膩的層次感。
|
||||
2. **顏色規範**:主要使用 `cyan-500` 作為亮點,搭配 `slate` 系列的半透明背景。
|
||||
3. **字體細節**:文字使用極細小尺寸 (`text-[10px]`)、極粗字重 (`font-black`) 並大幅度拉開字距 (`tracking-[0.4em]`)。
|
||||
4. **背景質感**:使用 `backdrop-blur-[1px]` 與 `bg-white/40` (或 `dark:bg-slate-900/40`),確保遮罩具有玻璃質感但不完全阻斷視線。
|
||||
5. **防止閃爍**: 必須加上 `x-cloak` 配合對應的 CSS 樣式。
|
||||
|
||||
## 14. 頁面佈局與頁籤間距規範 (Page Layout & Tab Spacing)
|
||||
|
||||
為了確保管理後台全站視覺的「高度整合感」與「緊湊性」,頁面頂部結構與頁籤導覽必須遵循以下間距規範。
|
||||
|
||||
### 14.1 核心佈局間距 (Core Spacing Scale)
|
||||
- **根容器間距 (Root Space)**: 頂部 Header、Tabs 與內容區域之間的垂直間距統一使用 **`space-y-2`**。
|
||||
- **標題與描述 (Title & Subtitle)**: 描述文字應緊貼標題,使用 `mt-1`。
|
||||
- **頁籤外層間距 (Tabs Outer)**: 頁籤容器與 Header 之間應由根容器的 `space-y-2` 自然推開,不額外加 `mt`。
|
||||
|
||||
### 14.2 頁籤導覽樣式 (Tab Navigation Styles)
|
||||
- **容器內距**: 使用 `p-1.5` 的圓角膠囊背景。
|
||||
- **按鈕內距**: 頁籤按鈕統一使用 **`px-8 py-3`**。
|
||||
- **內容間隙**: 頁籤下方內容區與頁籤列之間,建議不額外添加 `mt-6` 等大間距。若內容區域內部需要層次感,使用 `space-y-3` 即可。
|
||||
|
||||
### 14.3 實作範例結構 (Reference Structure)
|
||||
```html
|
||||
<div class="space-y-2">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black">標題</h1>
|
||||
<p class="text-sm mt-1">描述文字</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs Container -->
|
||||
<div class="flex items-center p-1.5 bg-slate-100 rounded-2xl w-fit border border-slate-200/50">
|
||||
<button class="px-8 py-3 rounded-xl text-sm font-black active">Tab A</button>
|
||||
<button class="px-8 py-3 rounded-xl text-sm font-black">Tab B</button>
|
||||
</div>
|
||||
|
||||
<!-- Content Area (AJAX Container) -->
|
||||
<div class="space-y-3">
|
||||
<!-- 內容卡片 -->
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 14.4 統計卡片間距 (Stats Grid Spacing)
|
||||
- **網格間距**: 頂部統計卡片網格 (Grid) 建議使用 **`gap-4`** (16px),避免使用過大的 `gap-6` 或 `gap-8` 導致版面鬆散。
|
||||
- **卡片內距**: 統計卡片內部統一使用 `p-6`。
|
||||
|
||||
### 15. Premium AJAX Interactions & Loaders
|
||||
|
||||
To maintain a seamless "SPA-like" experience without full-page reloads, all administrative modules must follow these AJAX patterns.
|
||||
|
||||
#### 15.1 The Premium Multi-Ring Loader
|
||||
Used for table overlays and state transitions. It features a dual-spin ring with a pulsing core icon.
|
||||
|
||||
```html
|
||||
<!-- Loading Overlay Pattern -->
|
||||
<div x-show="isLoading"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-300"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="absolute inset-0 z-50 flex flex-col items-center justify-center bg-white/40 dark:bg-slate-900/40 backdrop-blur-[1px] rounded-3xl" x-cloak>
|
||||
|
||||
<div class="relative w-16 h-16 mb-4 flex items-center justify-center">
|
||||
<!-- Outer: Fast spin -->
|
||||
<div class="absolute inset-0 rounded-full border-2 border-transparent border-t-cyan-500 border-r-cyan-500/30 animate-spin"></div>
|
||||
<!-- Inner: Slow reverse spin -->
|
||||
<div class="absolute inset-2 rounded-full border border-cyan-500/10 animate-spin" style="animation-duration: 3s; direction: reverse;"></div>
|
||||
<!-- Core: Pulsing Icon -->
|
||||
<div class="relative w-8 h-8 flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-cyan-500 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[10px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">Syncing Data...</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 15.2 Standard fetchPage Pattern
|
||||
All lists should implement the following `fetchPage` method in their `x-data` and wrap the dynamic content in `#ajax-content-container`.
|
||||
### 13.2 標準 fetchPage 實作模式
|
||||
所有清單頁面應在 `x-data` 中實作 `fetchPage` 方法,並將動態內容包裹於 `#ajax-content-container` 中。
|
||||
|
||||
```javascript
|
||||
async fetchPage(url) {
|
||||
@ -470,7 +389,49 @@ async fetchPage(url) {
|
||||
}
|
||||
```
|
||||
|
||||
#### 15.3 Triggering AJAX Navigation
|
||||
- **Forms**: Use `@submit.prevent="fetchPage($el.action + '?' + new URLSearchParams(new FormData($el)).toString())"`.
|
||||
- **Selects**: Use `onchange="this.dispatchEvent(new CustomEvent('ajax:navigate', { detail: { url: ... }, bubbles: true }))"`.
|
||||
- **Links/Pagination**: Use a delegated `@click` listener on the container or `@ajax:navigate.prevent.stop`.
|
||||
### 13.3 觸發 AJAX 導航
|
||||
- **搜尋表單**: 使用 `@submit.prevent="fetchPage($el.action + '?' + new URLSearchParams(new FormData($el)).toString())"`。
|
||||
- **下拉選單**: 使用 `onchange="this.dispatchEvent(new CustomEvent('ajax:navigate', { detail: { url: ... }, bubbles: true }))"`。
|
||||
- **分頁/連結**: 在容器上使用代理監聽,或直接調用 `fetchPage(url)`。
|
||||
|
||||
## 14. 頁面佈局與頁籤間距規範 (Page Layout & Tab Spacing)
|
||||
|
||||
為了確保管理後台全站視覺的「高度整合感」與「緊湊性」,頁面頂部結構與頁籤導覽必須遵循以下間距規範。
|
||||
|
||||
### 14.1 核心佈局間距 (Core Spacing Scale)
|
||||
- **根容器間距 (Root Space)**: 頂部 Header、Tabs 與內容區域之間的垂直間距統一使用 **`space-y-2`**。
|
||||
- **標題與描述 (Title & Subtitle)**: 描述文字應緊貼標題,使用 `mt-1`。
|
||||
- **頁籤外層間距 (Tabs Outer)**: 頁籤容器與 Header 之間應由根容器的 `space-y-2` 自然推開,不額外加 `mt`。
|
||||
|
||||
### 14.2 頁籤導覽樣式 (Tab Navigation Styles)
|
||||
- **容器內距**: 使用 `p-1.5` 的圓角膠囊背景。
|
||||
- **按鈕內距**: 頁籤按鈕統一使用 **`px-8 py-3`**。
|
||||
- **內容間隙**: 頁籤下方內容區與頁籤列之間,建議不額外添加 `mt-6` 等大間距。若內容區域內部需要層次感,使用 `space-y-3` 即可。
|
||||
|
||||
### 14.3 實作範例結構 (Reference Structure)
|
||||
```html
|
||||
<div class="space-y-2">
|
||||
<!-- Header Area -->
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black">標題</h1>
|
||||
<p class="text-sm mt-1">描述文字</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tabs Container -->
|
||||
<div class="flex items-center p-1.5 bg-slate-100 dark:bg-slate-900/40 rounded-2xl w-fit border border-slate-200/50 dark:border-white/5">
|
||||
<button class="px-8 py-3 rounded-xl text-sm font-black active">Tab A</button>
|
||||
<button class="px-8 py-3 rounded-xl text-sm font-black">Tab B</button>
|
||||
</div>
|
||||
|
||||
<!-- Content Area (AJAX Container) -->
|
||||
<div class="space-y-3">
|
||||
<!-- 內容卡片 -->
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 14.4 統計卡片間距 (Stats Grid Spacing)
|
||||
- **網格間距**: 頂部統計卡片網格 (Grid) 建議使用 **`gap-4`** (16px),避免使用過大的 `gap-6` 或 `gap-8` 導致版面鬆散。
|
||||
- **卡片內距**: 統計卡片內部統一使用 `p-6`。
|
||||
@ -74,6 +74,13 @@ class CompanyController extends Controller
|
||||
if (isset($validated['settings'])) {
|
||||
$validated['settings']['enable_material_code'] = filter_var($validated['settings']['enable_material_code'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['enable_points'] = filter_var($validated['settings']['enable_points'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['tax_invoice'] = filter_var($validated['settings']['tax_invoice'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['card_terminal'] = filter_var($validated['settings']['card_terminal'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['scan_pay_esun'] = filter_var($validated['settings']['scan_pay_esun'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['scan_pay_linepay'] = filter_var($validated['settings']['scan_pay_linepay'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['shopping_cart'] = filter_var($validated['settings']['shopping_cart'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['welcome_gift'] = filter_var($validated['settings']['welcome_gift'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$validated['settings']['cash_module'] = filter_var($validated['settings']['cash_module'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
DB::transaction(function () use ($validated) {
|
||||
@ -217,6 +224,7 @@ class CompanyController extends Controller
|
||||
*/
|
||||
public function updateSettings(Request $request, Company $company)
|
||||
{
|
||||
\Log::info('updateSettings payload:', $request->all());
|
||||
$settings = $request->input('settings', []);
|
||||
|
||||
$formattedSettings = [
|
||||
|
||||
@ -460,7 +460,7 @@ return [
|
||||
'description' => '當機台發生硬體故障(如卡貨、通訊中斷)時即時上報。',
|
||||
'payload_parameters' => [
|
||||
'tid' => ['type' => 'integer', 'description' => '貨道編號或任務 ID'],
|
||||
'error_code' => ['type' => 'string', 'description' => '硬體錯誤代碼,詳見下方 B013 代碼表'],
|
||||
'error_code' => ['type' => 'string', 'description' => '硬體錯誤代碼 (如 0403 代表卡貨)'],
|
||||
],
|
||||
'payload_example' => [
|
||||
'tid' => 12,
|
||||
@ -480,7 +480,7 @@ return [
|
||||
'amount' => ['type' => 'number', 'description' => '交易金額 (create/invoice 時使用)'],
|
||||
'slot_no' => ['type' => 'string', 'description' => '出貨貨道號碼'],
|
||||
'invoice_no' => ['type' => 'string', 'description' => '發票號碼 (action 為 invoice 時必填)'],
|
||||
'status' => ['type' => 'string', 'description' => '出貨結果狀態 (action 為 dispense 時必填,見狀態定義表)'],
|
||||
'status' => ['type' => 'string', 'description' => '出貨結果狀態 (action 為 dispense 時必填,如 success, failed)'],
|
||||
],
|
||||
'payload_example' => [
|
||||
'action' => 'create',
|
||||
@ -596,7 +596,7 @@ return [
|
||||
'description' => '機台執行完雲端指令後的主動回報。此回報極為重要,雲端將依此決定交易是否完成或需進行庫存回滾。',
|
||||
'payload_parameters' => [
|
||||
'command_id' => ['type' => 'string', 'description' => '對應下發時的指令 ID'],
|
||||
'result' => ['type' => 'string', 'description' => '執行結果,見狀態定義表'],
|
||||
'result' => ['type' => 'string', 'description' => '執行結果,僅接受: success 或 failed'],
|
||||
'stock' => ['type' => 'integer', 'description' => '執行指令後的最終剩餘庫存 (選填,通常出貨指令需帶上)'],
|
||||
'message' => ['type' => 'string', 'description' => '額外的錯誤訊息 (選填)'],
|
||||
],
|
||||
@ -608,39 +608,5 @@ return [
|
||||
]
|
||||
]
|
||||
]
|
||||
],
|
||||
|
||||
'status_definitions' => [
|
||||
[
|
||||
'name' => 'B013: 硬體錯誤代碼 (MQTT Error Topic)',
|
||||
'description' => '當機台透過 machine/{serial_no}/error 上報時,error_code 欄位所使用的代碼。',
|
||||
'codes' => [
|
||||
['code' => '0402', 'label' => '出貨成功', 'level' => 'info', 'description' => '商品成功掉落至取貨口。'],
|
||||
['code' => '0403', 'label' => '貨道卡貨', 'level' => 'error', 'description' => '馬達轉動異常或偵測到卡貨,需人工排除。'],
|
||||
['code' => '0202', 'label' => '貨道缺貨', 'level' => 'warning', 'description' => '偵測到實體貨道已無商品。'],
|
||||
['code' => '0412', 'label' => '昇降機上升異常', 'level' => 'error', 'description' => '昇降馬達無法正確到達指定位置。'],
|
||||
['code' => '0415', 'label' => '取貨門異常', 'level' => 'error', 'description' => '取貨門馬達損壞或感應器失效。'],
|
||||
['code' => '5402', 'label' => '取貨門未關', 'level' => 'warning', 'description' => '取貨逾時或門片未正確閉合。'],
|
||||
['code' => '5403', 'label' => '昇降系統故障', 'level' => 'error', 'description' => '昇降機通訊中斷或驅動板異常。'],
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => '交易與指令執行狀態 (Transaction & Command)',
|
||||
'description' => '用於 Transaction (dispense) 與 Command ACK 的執行結果判定。',
|
||||
'codes' => [
|
||||
['code' => 'success', 'label' => '成功', 'description' => '動作執行圓滿完成。'],
|
||||
['code' => 'failed', 'label' => '失敗', 'description' => '動作執行失敗,雲端將視情況發起退款或記錄異常。'],
|
||||
['code' => '1', 'label' => '成功 (數字型態)', 'description' => '部分舊型指令或 B602 使用的成功代碼。'],
|
||||
['code' => '0', 'label' => '失敗 (數字型態)', 'description' => '部分舊型指令或 B602 使用的失敗代碼。'],
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => '機台連線狀態 (Connection Status)',
|
||||
'description' => '用於 machine/{serial_no}/status 主題,反映機台與 Broker 的連線情況。',
|
||||
'codes' => [
|
||||
['code' => 'online', 'label' => '在線', 'description' => '機台已成功連線至 MQTT Broker。'],
|
||||
['code' => 'offline', 'label' => '離線', 'description' => '機台已斷開連線(通常由 LWT 遺囑訊息觸發)。'],
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
@ -291,6 +291,7 @@
|
||||
"e.g., Drinks": "e.g., Drinks",
|
||||
"e.g., Taipei Station": "e.g., Taipei Station",
|
||||
"e.g., お飲み物": "e.g., O-Nomimono",
|
||||
"ESUN": "ESUN",
|
||||
"E.SUN QR Scan": "E.SUN QR Scan",
|
||||
"E.SUN QR Scan Settings Description": "E.SUN Bank QR Scan Payment Settings",
|
||||
"EASY_MERCHANT_ID": "EASY_MERCHANT_ID",
|
||||
@ -315,6 +316,7 @@
|
||||
"Edit Slot": "Edit Slot",
|
||||
"Edit Sub Account Role": "Edit Sub Account Role",
|
||||
"Email": "Email",
|
||||
"Enabled Features": "Enabled Features",
|
||||
"Enable": "Enable",
|
||||
"Enable Material Code": "Enable Material Code",
|
||||
"Enable Points": "Enable Points",
|
||||
@ -709,6 +711,7 @@
|
||||
"Please select a slot": "Please select a slot",
|
||||
"PNG, JPG up to 2MB": "PNG, JPG up to 2MB",
|
||||
"PNG, JPG, WEBP up to 10MB": "PNG, JPG, WEBP up to 10MB",
|
||||
"Points": "Points",
|
||||
"Point Rules": "Point Rules",
|
||||
"Point Settings": "Point Settings",
|
||||
"Points Rule": "Points Rule",
|
||||
|
||||
@ -14,8 +14,16 @@
|
||||
"Coin/Bill Acceptor": "コイン/紙幣機",
|
||||
"System settings updated successfully.": "システム設定が正常に更新されました。",
|
||||
"Update Settings": "設定を更新",
|
||||
"Enabled Features": "有効な機能",
|
||||
"Material Code": "素材コード",
|
||||
"Points": "ポイント",
|
||||
"Electronic Invoice": "電子翻訳",
|
||||
"Card System": "カード決済システム",
|
||||
"ESUN": "玉山銀行 (ESUN)",
|
||||
"LinePay": "LinePay",
|
||||
"Shopping Cart": "ショッピングカート",
|
||||
"Welcome Gift": "登録特典",
|
||||
"Cash Module": "現金決済モジュール",
|
||||
"Please enter configuration name": "設定名を入力してください",
|
||||
"Please select a company": "会社を選択してください",
|
||||
"Warehouse Management": "倉庫管理",
|
||||
|
||||
@ -317,6 +317,8 @@
|
||||
"Edit Slot": "編輯貨道",
|
||||
"Edit Sub Account Role": "編輯子帳號角色",
|
||||
"Email": "電子郵件",
|
||||
"Emblem / Label": "標章 / 標籤",
|
||||
"Enabled Features": "啟用功能",
|
||||
"Enable": "啟用",
|
||||
"Enable Material Code": "啟用物料編號",
|
||||
"Enable Points": "啟用點數規則",
|
||||
@ -718,6 +720,7 @@
|
||||
"PNG, JPG, WEBP up to 10MB": "支援 PNG, JPG, WEBP 格式,且不超過 10MB",
|
||||
"Point Rules": "點數規則",
|
||||
"Point Settings": "點數設定",
|
||||
"Points": "點數功能",
|
||||
"Points Rule": "點數規則",
|
||||
"Points Settings": "點數設定",
|
||||
"Points toggle": "點數開關",
|
||||
@ -1260,6 +1263,7 @@
|
||||
"Card System": "刷卡機系統",
|
||||
"Credit Card / Contactless": "支援信用卡 / 感應支付",
|
||||
"Scan Pay": "掃碼支付",
|
||||
"ESUN": "玉山銀行",
|
||||
"ESUN Scan Pay": "玉山掃碼支付",
|
||||
"LinePay": "LinePay",
|
||||
"Other Features": "其他功能",
|
||||
|
||||
@ -6,8 +6,17 @@ $baseRoute = 'admin.data-config.advertisements';
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
<div class="space-y-2 pb-20"
|
||||
<div class="space-y-2 pb-20 relative"
|
||||
x-data="adManager"
|
||||
@ajax:navigate.window.prevent="fetchPage($event.detail.url)"
|
||||
@submit.prevent="if($event.target.method.toLowerCase() === 'get') {
|
||||
const url = new URL($event.target.action, window.location.origin);
|
||||
new FormData($event.target).forEach((value, key) => {
|
||||
if(value.trim()) url.searchParams.set(key, value);
|
||||
else url.searchParams.delete(key);
|
||||
});
|
||||
fetchPage(url.pathname + url.search);
|
||||
}"
|
||||
data-ads="{{ json_encode($advertisements->items()) }}"
|
||||
data-machines="{{ json_encode($machines) }}"
|
||||
data-all-ads="{{ json_encode($allAds) }}"
|
||||
@ -59,7 +68,29 @@ $baseRoute = 'admin.data-config.advertisements';
|
||||
<!-- Tab Contents -->
|
||||
<div class="mt-6">
|
||||
<!-- List Tab -->
|
||||
<div x-show="activeTab === 'list'" class="luxury-card rounded-3xl p-8 animate-luxury-in" x-cloak>
|
||||
<div x-show="activeTab === 'list'" class="luxury-card rounded-3xl p-8 animate-luxury-in relative" x-cloak>
|
||||
<!-- Loading Spinner Overlay -->
|
||||
<div x-show="isLoading"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="transition ease-in duration-300"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
class="absolute inset-0 z-50 flex flex-col items-center justify-center bg-white/40 dark:bg-slate-900/40 backdrop-blur-[1px] rounded-3xl" x-cloak>
|
||||
|
||||
<div class="relative w-16 h-16 mb-4 flex items-center justify-center">
|
||||
<div class="absolute inset-0 rounded-full border-2 border-transparent border-t-cyan-500 border-r-cyan-500/30 animate-spin"></div>
|
||||
<div class="absolute inset-2 rounded-full border border-cyan-500/10 animate-spin" style="animation-duration: 3s; direction: reverse;"></div>
|
||||
<div class="relative w-8 h-8 flex items-center justify-center">
|
||||
<svg class="w-6 h-6 text-cyan-500 animate-pulse" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[12px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">{{ __('Loading Data') }}...</p>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||
<thead>
|
||||
@ -376,6 +407,7 @@ $baseRoute = 'admin.data-config.advertisements';
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.data('adManager', () => ({
|
||||
isLoading: false,
|
||||
activeTab: 'list',
|
||||
selectedMachineId: null,
|
||||
machines: [],
|
||||
@ -431,6 +463,49 @@ $baseRoute = 'admin.data-config.advertisements';
|
||||
return this.sequenceAds[this.currentSequenceIndex] || null;
|
||||
},
|
||||
|
||||
async fetchPage(url) {
|
||||
if (!url || url === window.location.href) return;
|
||||
this.isLoading = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Accept': 'text/html'
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Network response was not ok');
|
||||
|
||||
const html = await response.text();
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html, 'text/html');
|
||||
|
||||
// Update table contents
|
||||
const newTableBody = doc.querySelector('tbody');
|
||||
const currentTableBody = document.querySelector('tbody');
|
||||
if (newTableBody && currentTableBody) {
|
||||
currentTableBody.innerHTML = newTableBody.innerHTML;
|
||||
}
|
||||
|
||||
// Update pagination
|
||||
const newPagination = doc.querySelector('.mt-8');
|
||||
const currentPagination = document.querySelector('.mt-8');
|
||||
if (newPagination && currentPagination) {
|
||||
currentPagination.innerHTML = newPagination.innerHTML;
|
||||
}
|
||||
|
||||
// Update URL without reload
|
||||
window.history.pushState({}, '', url);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching page:', error);
|
||||
window.showToast?.('載入失敗,請重試', 'error');
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
|
||||
startSequencePreview(pos) {
|
||||
if (!this.machineAds[pos] || this.machineAds[pos].length === 0) return;
|
||||
this.sequenceAds = this.machineAds[pos];
|
||||
|
||||
@ -28,7 +28,14 @@
|
||||
note: '',
|
||||
settings: {
|
||||
enable_material_code: false,
|
||||
enable_points: false
|
||||
enable_points: false,
|
||||
tax_invoice: false,
|
||||
card_terminal: false,
|
||||
scan_pay_esun: false,
|
||||
scan_pay_linepay: false,
|
||||
shopping_cart: false,
|
||||
welcome_gift: false,
|
||||
cash_module: false
|
||||
}
|
||||
},
|
||||
openCreateModal() {
|
||||
@ -40,7 +47,17 @@
|
||||
warranty_start_date: '', warranty_end_date: '',
|
||||
software_start_date: '', software_end_date: '',
|
||||
status: 1, note: '',
|
||||
settings: { enable_material_code: false, enable_points: false }
|
||||
settings: {
|
||||
enable_material_code: false,
|
||||
enable_points: false,
|
||||
tax_invoice: false,
|
||||
card_terminal: false,
|
||||
scan_pay_esun: false,
|
||||
scan_pay_linepay: false,
|
||||
shopping_cart: false,
|
||||
welcome_gift: false,
|
||||
cash_module: false
|
||||
}
|
||||
};
|
||||
this.showModal = true;
|
||||
},
|
||||
@ -56,7 +73,14 @@
|
||||
software_end_date: company.software_end_date ? company.software_end_date.substring(0, 10) : '',
|
||||
settings: {
|
||||
enable_material_code: company.settings?.enable_material_code || false,
|
||||
enable_points: company.settings?.enable_points || false
|
||||
enable_points: company.settings?.enable_points || false,
|
||||
tax_invoice: company.settings?.tax_invoice || false,
|
||||
card_terminal: company.settings?.card_terminal || false,
|
||||
scan_pay_esun: company.settings?.scan_pay_esun || false,
|
||||
scan_pay_linepay: company.settings?.scan_pay_linepay || false,
|
||||
shopping_cart: company.settings?.shopping_cart || false,
|
||||
welcome_gift: company.settings?.welcome_gift || false,
|
||||
cash_module: company.settings?.cash_module || false
|
||||
}
|
||||
};
|
||||
this.originalStatus = company.status;
|
||||
@ -85,7 +109,14 @@
|
||||
...company,
|
||||
settings: {
|
||||
enable_material_code: company.settings?.enable_material_code || false,
|
||||
enable_points: company.settings?.enable_points || false
|
||||
enable_points: company.settings?.enable_points || false,
|
||||
tax_invoice: company.settings?.tax_invoice || false,
|
||||
card_terminal: company.settings?.card_terminal || false,
|
||||
scan_pay_esun: company.settings?.scan_pay_esun || false,
|
||||
scan_pay_linepay: company.settings?.scan_pay_linepay || false,
|
||||
shopping_cart: company.settings?.shopping_cart || false,
|
||||
welcome_gift: company.settings?.welcome_gift || false,
|
||||
cash_module: company.settings?.cash_module || false
|
||||
}
|
||||
};
|
||||
this.sidebarView = 'detail';
|
||||
@ -96,7 +127,14 @@
|
||||
...company,
|
||||
settings: {
|
||||
enable_material_code: company.settings?.enable_material_code || false,
|
||||
enable_points: company.settings?.enable_points || false
|
||||
enable_points: company.settings?.enable_points || false,
|
||||
tax_invoice: company.settings?.tax_invoice || false,
|
||||
card_terminal: company.settings?.card_terminal || false,
|
||||
scan_pay_esun: company.settings?.scan_pay_esun || false,
|
||||
scan_pay_linepay: company.settings?.scan_pay_linepay || false,
|
||||
shopping_cart: company.settings?.shopping_cart || false,
|
||||
welcome_gift: company.settings?.welcome_gift || false,
|
||||
cash_module: company.settings?.cash_module || false
|
||||
}
|
||||
};
|
||||
this.sidebarView = 'history';
|
||||
@ -110,7 +148,14 @@
|
||||
...company,
|
||||
settings: {
|
||||
enable_material_code: company.settings?.enable_material_code || false,
|
||||
enable_points: company.settings?.enable_points || false
|
||||
enable_points: company.settings?.enable_points || false,
|
||||
tax_invoice: company.settings?.tax_invoice || false,
|
||||
card_terminal: company.settings?.card_terminal || false,
|
||||
scan_pay_esun: company.settings?.scan_pay_esun || false,
|
||||
scan_pay_linepay: company.settings?.scan_pay_linepay || false,
|
||||
shopping_cart: company.settings?.shopping_cart || false,
|
||||
welcome_gift: company.settings?.welcome_gift || false,
|
||||
cash_module: company.settings?.cash_module || false
|
||||
}
|
||||
};
|
||||
this.showHistoryModal = true;
|
||||
@ -230,7 +275,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[10px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">{{ __('Loading Data') }}...</p>
|
||||
<p class="text-[12px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">{{ __('Loading Data') }}...</p>
|
||||
</div>
|
||||
|
||||
<div id="ajax-content-container"
|
||||
@ -343,13 +388,13 @@
|
||||
<td class="px-6 py-6 text-center">
|
||||
<div class="flex flex-col items-center gap-2">
|
||||
<div class="flex items-center gap-2 min-w-[100px] justify-center">
|
||||
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest w-8 text-right">{{ __('Original:') }}</span>
|
||||
<span class="text-[12px] font-bold text-slate-400 uppercase tracking-widest w-8 text-right">{{ __('Original:') }}</span>
|
||||
<span class="px-2 py-0.5 rounded text-xs font-bold {{ $company->original_type === 'buyout' ? 'bg-amber-500/10 text-amber-600' : 'bg-blue-500/10 text-blue-600' }} uppercase tracking-wider">
|
||||
{{ __($company->original_type === 'buyout' ? 'Buyout' : 'Lease') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 min-w-[100px] justify-center">
|
||||
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest w-8 text-right">{{ __('Current:') }}</span>
|
||||
<span class="text-[12px] font-bold text-slate-400 uppercase tracking-widest w-8 text-right">{{ __('Current:') }}</span>
|
||||
<span class="px-2 py-0.5 rounded text-xs font-bold {{ $company->current_type === 'buyout' ? 'bg-amber-500/10 text-amber-600 border border-amber-500/20' : 'bg-blue-500/10 text-blue-600 border border-blue-500/20' }} uppercase tracking-wider">
|
||||
{{ __($company->current_type === 'buyout' ? 'Buyout' : 'Lease') }}
|
||||
</span>
|
||||
@ -358,11 +403,11 @@
|
||||
</td>
|
||||
<td class="px-6 py-6 text-center py-6">
|
||||
@if($company->status)
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-[11px] font-black bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 tracking-widest uppercase">
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-[12px] font-black bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 tracking-widest uppercase">
|
||||
{{ __('Active') }}
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-[11px] font-black bg-rose-500/10 text-rose-500 border border-rose-500/20 tracking-widest uppercase">
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-[12px] font-black bg-rose-500/10 text-rose-500 border border-rose-500/20 tracking-widest uppercase">
|
||||
{{ __('Disabled') }}
|
||||
</span>
|
||||
@endif
|
||||
@ -371,12 +416,12 @@
|
||||
<div class="flex items-center justify-center gap-x-3">
|
||||
<div class="flex flex-col items-center">
|
||||
<span class="text-base font-extrabold text-slate-800 dark:text-white">{{ $company->users_count }}</span>
|
||||
<span class="text-[11px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">{{ __('Users') }}</span>
|
||||
<span class="text-[12px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">{{ __('Users') }}</span>
|
||||
</div>
|
||||
<div class="w-px h-6 bg-slate-100 dark:bg-slate-800"></div>
|
||||
<div class="flex flex-col items-center">
|
||||
<span class="text-base font-extrabold text-slate-800 dark:text-white">{{ $company->machines_count }}</span>
|
||||
<span class="text-[11px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">{{ __('Machines') }}</span>
|
||||
<span class="text-[12px] font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">{{ __('Machines') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@ -385,10 +430,10 @@
|
||||
<!-- Contract Period (Only for Lease) -->
|
||||
@if($company->current_type === 'lease')
|
||||
<div class="flex items-center gap-3 group/term">
|
||||
<span class="px-2 py-0.5 rounded-md bg-blue-500/10 text-blue-600 text-[10px] font-black border border-blue-500/20 group-hover/term:bg-blue-500 group-hover/term:text-white transition-all tracking-wider whitespace-nowrap min-w-[42px] text-center">
|
||||
<span class="px-2.5 py-1 rounded-md bg-blue-500/10 text-blue-600 text-[12px] font-black border border-blue-500/20 group-hover/term:bg-blue-500 group-hover/term:text-white transition-all tracking-wider whitespace-nowrap min-w-[42px] text-center">
|
||||
{{ __('Contract') }}
|
||||
</span>
|
||||
<div class="flex items-center gap-1.5 font-mono text-[11px] font-bold">
|
||||
<div class="flex items-center gap-1.5 font-mono text-[12px] font-bold">
|
||||
<span class="text-slate-400">{{ $company->start_date ? $company->start_date->format('Y-m-d') : '--' }}</span>
|
||||
<span class="text-slate-300">~</span>
|
||||
<span class="{{ $company->end_date && $company->end_date->isPast() ? 'text-rose-500 shadow-sm shadow-rose-500/10' : 'text-slate-600 dark:text-slate-300' }}">
|
||||
@ -401,10 +446,10 @@
|
||||
@if($company->current_type === 'buyout')
|
||||
<!-- Warranty Period -->
|
||||
<div class="flex items-center gap-3 group/term">
|
||||
<span class="px-2 py-0.5 rounded-md bg-amber-500/10 text-amber-600 text-[10px] font-black border border-amber-500/20 group-hover/term:bg-amber-500 group-hover/term:text-white transition-all tracking-wider whitespace-nowrap min-w-[42px] text-center">
|
||||
<span class="px-2.5 py-1 rounded-md bg-amber-500/10 text-amber-600 text-[12px] font-black border border-amber-500/20 group-hover/term:bg-amber-500 group-hover/term:text-white transition-all tracking-wider whitespace-nowrap min-w-[42px] text-center">
|
||||
{{ __('Warranty') }}
|
||||
</span>
|
||||
<div class="flex items-center gap-1.5 font-mono text-[11px] font-bold">
|
||||
<div class="flex items-center gap-1.5 font-mono text-[12px] font-bold">
|
||||
<span class="text-slate-400">{{ $company->warranty_start_date ? $company->warranty_start_date->format('Y-m-d') : '--' }}</span>
|
||||
<span class="text-slate-300">~</span>
|
||||
<span class="{{ $company->warranty_end_date && $company->warranty_end_date->isPast() ? 'text-rose-500 shadow-sm shadow-rose-500/10' : 'text-slate-500 dark:text-slate-400' }}">
|
||||
@ -415,10 +460,10 @@
|
||||
|
||||
<!-- Software Period -->
|
||||
<div class="flex items-center gap-3 group/term">
|
||||
<span class="px-2 py-0.5 rounded-md bg-purple-500/10 text-purple-600 text-[10px] font-black border border-purple-500/20 group-hover/term:bg-purple-500 group-hover/term:text-white transition-all tracking-wider whitespace-nowrap min-w-[42px] text-center">
|
||||
<span class="px-2.5 py-1 rounded-md bg-purple-500/10 text-purple-600 text-[12px] font-black border border-purple-500/20 group-hover/term:bg-purple-500 group-hover/term:text-white transition-all tracking-wider whitespace-nowrap min-w-[42px] text-center">
|
||||
{{ __('Software') }}
|
||||
</span>
|
||||
<div class="flex items-center gap-1.5 font-mono text-[11px] font-bold">
|
||||
<div class="flex items-center gap-1.5 font-mono text-[12px] font-bold">
|
||||
<span class="text-slate-400">{{ $company->software_start_date ? $company->software_start_date->format('Y-m-d') : '--' }}</span>
|
||||
<span class="text-slate-300">~</span>
|
||||
<span class="{{ $company->software_end_date && $company->software_end_date->isPast() ? 'text-rose-500 shadow-sm shadow-rose-500/10' : 'text-slate-500 dark:text-slate-400' }}">
|
||||
@ -550,34 +595,34 @@
|
||||
@php $settings = $company->settings ?? []; @endphp
|
||||
|
||||
@if(empty($settings) || !in_array(true, array_map(fn($v) => (bool)$v, is_array($settings) ? $settings : ($settings->toArray() ?? [])), true))
|
||||
<span class="px-2.5 py-1 rounded-md bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 text-[10px] font-black uppercase tracking-wider">{{ __('None') }}</span>
|
||||
<span class="px-2.5 py-1 rounded-lg bg-slate-500/10 text-slate-500 dark:text-slate-400 text-[13px] font-bold uppercase tracking-wider">{{ __('None') }}</span>
|
||||
@else
|
||||
@if($settings['enable_material_code'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-md bg-cyan-500/10 text-cyan-600 border border-cyan-500/20 text-[10px] font-black uppercase tracking-wider">{{ __('Material Code') }}</span>
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('Material Code') }}</span>
|
||||
@endif
|
||||
@if($settings['enable_points'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-md bg-cyan-500/10 text-cyan-600 border border-cyan-500/20 text-[10px] font-black uppercase tracking-wider">{{ __('Points') }}</span>
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('Points') }}</span>
|
||||
@endif
|
||||
@if($settings['tax_invoice'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-md bg-emerald-500/10 text-emerald-600 border border-emerald-500/20 text-[10px] font-black uppercase tracking-wider">{{ __('Electronic Invoice') }}</span>
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('Electronic Invoice') }}</span>
|
||||
@endif
|
||||
@if($settings['card_terminal'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-md bg-blue-500/10 text-blue-600 border border-blue-500/20 text-[10px] font-black uppercase tracking-wider">{{ __('Card System') }}</span>
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('Card System') }}</span>
|
||||
@endif
|
||||
@if($settings['scan_pay_esun'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-md bg-green-500/10 text-green-600 border border-green-500/20 text-[10px] font-black uppercase tracking-wider">{{ __('ESUN') }}</span>
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('ESUN') }}</span>
|
||||
@endif
|
||||
@if($settings['scan_pay_linepay'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-md bg-green-500/10 text-green-600 border border-green-500/20 text-[10px] font-black uppercase tracking-wider">{{ __('LinePay') }}</span>
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('LinePay') }}</span>
|
||||
@endif
|
||||
@if($settings['shopping_cart'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-md bg-purple-500/10 text-purple-600 border border-purple-500/20 text-[10px] font-black uppercase tracking-wider">{{ __('Shopping Cart') }}</span>
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('Shopping Cart') }}</span>
|
||||
@endif
|
||||
@if($settings['welcome_gift'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-md bg-rose-500/10 text-rose-600 border border-rose-500/20 text-[10px] font-black uppercase tracking-wider">{{ __('Welcome Gift') }}</span>
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('Welcome Gift') }}</span>
|
||||
@endif
|
||||
@if($settings['cash_module'] ?? false)
|
||||
<span class="px-2.5 py-1 rounded-md bg-amber-500/10 text-amber-600 border border-amber-500/20 text-[10px] font-black uppercase tracking-wider">{{ __('Cash Module') }}</span>
|
||||
<span class="px-2.5 py-1 rounded-lg bg-cyan-500/10 text-cyan-500 dark:text-cyan-400 text-[13px] font-bold uppercase tracking-wider">{{ __('Cash Module') }}</span>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
@ -586,8 +631,8 @@
|
||||
<button type="button" @click='openSettingsModal({{ json_encode($company) }})'
|
||||
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 transition-all border border-transparent hover:border-cyan-500/20"
|
||||
title="{{ __('Edit Settings') }}">
|
||||
<svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 11-3 0m3 0a1.5 1.5 0 10-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m-9.75 0h9.75" />
|
||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
@ -673,7 +718,7 @@
|
||||
|
||||
<!-- Business Type Selector -->
|
||||
<div class="p-4 rounded-2xl bg-slate-50/50 dark:bg-slate-800/50 border border-slate-100 dark:border-slate-700/50 space-y-4">
|
||||
<label class="text-[11px] font-black text-slate-500 uppercase tracking-widest pl-1">
|
||||
<label class="text-[12px] font-black text-slate-500 uppercase tracking-widest pl-1">
|
||||
{{ __('Business Type') }}
|
||||
<span class="text-rose-500 ml-0.5">*</span>
|
||||
</label>
|
||||
@ -696,11 +741,11 @@
|
||||
<!-- 編輯模式:顯示原始類型固定值 + 當前類型切換 -->
|
||||
<div x-show="editing" class="flex flex-col gap-5">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-[11px] font-black text-slate-400 uppercase tracking-widest">{{ __('Original Type') }}: <span class="text-rose-500 ml-0.5">*</span></span>
|
||||
<span class="text-[12px] font-black text-slate-400 uppercase tracking-widest">{{ __('Original Type') }}: <span class="text-rose-500 ml-0.5">*</span></span>
|
||||
<span class="px-4 py-1.5 bg-slate-100 dark:bg-slate-800 rounded-lg text-xs font-bold uppercase tracking-widest text-slate-600 dark:text-slate-400" x-text="currentCompany.original_type === 'buyout' ? '{{ __('Buyout') }}' : '{{ __('Lease') }}'"></span>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-[11px] font-black text-slate-400 uppercase tracking-widest">{{ __('Current Type') }}: <span class="text-rose-500 ml-0.5">*</span></span>
|
||||
<span class="text-[12px] font-black text-slate-400 uppercase tracking-widest">{{ __('Current Type') }}: <span class="text-rose-500 ml-0.5">*</span></span>
|
||||
<div class="flex p-1.5 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-700 w-fit">
|
||||
<button type="button" @click="currentCompany.current_type = 'lease'"
|
||||
:class="currentCompany.current_type === 'lease' ? 'bg-blue-500 text-white shadow-lg shadow-blue-500/20' : 'text-slate-400 hover:text-slate-600'"
|
||||
@ -761,24 +806,24 @@
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="space-y-2">
|
||||
<label class="text-[10px] font-black text-amber-600 uppercase tracking-widest pl-1">{{ __('Warranty Start') }}</label>
|
||||
<label class="text-[12px] font-black text-amber-600 uppercase tracking-widest pl-1">{{ __('Warranty Start') }}</label>
|
||||
<input type="date" name="warranty_start_date" x-model="currentCompany.warranty_start_date"
|
||||
class="luxury-input w-full px-2 border-amber-100 dark:border-amber-900/30">
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-[10px] font-black text-amber-600 uppercase tracking-widest pl-1">{{ __('Warranty End') }}</label>
|
||||
<label class="text-[12px] font-black text-amber-600 uppercase tracking-widest pl-1">{{ __('Warranty End') }}</label>
|
||||
<input type="date" name="warranty_end_date" x-model="currentCompany.warranty_end_date"
|
||||
class="luxury-input w-full px-2 border-amber-100 dark:border-amber-900/30">
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="space-y-2">
|
||||
<label class="text-[10px] font-black text-indigo-600 uppercase tracking-widest pl-1">{{ __('Software Start') }}</label>
|
||||
<label class="text-[12px] font-black text-indigo-600 uppercase tracking-widest pl-1">{{ __('Software Start') }}</label>
|
||||
<input type="date" name="software_start_date" x-model="currentCompany.software_start_date"
|
||||
class="luxury-input w-full px-2 border-indigo-100 dark:border-indigo-900/30">
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<label class="text-[10px] font-black text-indigo-600 uppercase tracking-widest pl-1">{{ __('Software End') }}</label>
|
||||
<label class="text-[12px] font-black text-indigo-600 uppercase tracking-widest pl-1">{{ __('Software End') }}</label>
|
||||
<input type="date" name="software_end_date" x-model="currentCompany.software_end_date"
|
||||
class="luxury-input w-full px-2 border-indigo-100 dark:border-indigo-900/30">
|
||||
</div>
|
||||
@ -795,7 +840,7 @@
|
||||
<div class="h-6 w-1 bg-emerald-500 rounded-full"></div>
|
||||
<h4 class="text-xs font-black text-slate-800 dark:text-white uppercase tracking-widest">
|
||||
{{ __('Initial Admin Account') }}
|
||||
<span class="text-[10px] text-slate-400 font-normal normal-case tracking-normal ml-2">({{ __('Optional') }})</span>
|
||||
<span class="text-[12px] text-slate-400 font-normal normal-case tracking-normal ml-2">({{ __('Optional') }})</span>
|
||||
</h4>
|
||||
</div>
|
||||
|
||||
@ -1180,10 +1225,10 @@
|
||||
<div class="bg-slate-50 dark:bg-slate-800/40 p-5 rounded-2xl border border-slate-100 dark:border-slate-800/80 space-y-4">
|
||||
<div class="flex justify-between items-start pb-3 border-b border-slate-100/50 dark:border-slate-800/50">
|
||||
<div>
|
||||
<h4 class="text-[10px] font-black text-indigo-500 uppercase tracking-[0.2em]">{{ __('Contract Model') }}</h4>
|
||||
<h4 class="text-[12px] font-black text-indigo-500 uppercase tracking-[0.2em]">{{ __('Contract Model') }}</h4>
|
||||
</div>
|
||||
<span :class="detailCompany.current_type === 'lease' ? 'text-blue-500 bg-blue-500/10' : 'text-amber-500 bg-amber-500/10'"
|
||||
class="text-[10px] font-black uppercase tracking-wider px-2.5 py-1 rounded-lg">
|
||||
class="text-[12px] font-black uppercase tracking-wider px-2.5 py-1 rounded-lg">
|
||||
<span x-text="detailCompany.current_type === 'lease' ? '{{ __('Lease') }}' : '{{ __('Buyout') }}'"></span>
|
||||
</span>
|
||||
</div>
|
||||
@ -1234,6 +1279,7 @@
|
||||
<section class="space-y-4">
|
||||
<h3 class="text-xs font-black text-indigo-500 uppercase tracking-[0.3em]">{{ __('Feature Settings') }}</h3>
|
||||
<div class="space-y-3">
|
||||
<!-- Material Code -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-indigo-50 dark:bg-indigo-500/10 text-indigo-500">
|
||||
@ -1246,6 +1292,7 @@
|
||||
<span x-text="detailCompany.settings?.enable_material_code ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Points Rule -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-amber-50 dark:bg-amber-500/10 text-amber-500">
|
||||
@ -1258,6 +1305,97 @@
|
||||
<span x-text="detailCompany.settings?.enable_points ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Electronic Invoice -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-emerald-50 dark:bg-emerald-500/10 text-emerald-500">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('Electronic Invoice') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.tax_invoice ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.tax_invoice ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Card System -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-blue-50 dark:bg-blue-500/10 text-blue-500">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('Card System') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.card_terminal ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.card_terminal ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ESUN Pay -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-green-50 dark:bg-green-500/10 text-green-500">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v1m6 11h2m-6 0h-2v4m0-11v3m0 0h.01M12 12h4.01M16 20h4M4 12h4m12 0h.01M5 8h2a1 1 0 001-1V5a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1zm12 0h2a1 1 0 001-1V5a1 1 0 00-1-1h-2a1 1 0 00-1 1v2a1 1 0 001 1zM5 17h2a1 1 0 001-1v-2a1 1 0 00-1-1H5a1 1 0 00-1 1v2a1 1 0 001 1z" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('ESUN QR Pay') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.scan_pay_esun ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.scan_pay_esun ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- LinePay -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-green-50 dark:bg-green-500/10 text-green-500">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('LinePay Payment') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.scan_pay_linepay ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.scan_pay_linepay ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Shopping Cart -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-purple-50 dark:bg-purple-500/10 text-purple-500">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('Shopping Cart') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.shopping_cart ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.shopping_cart ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Welcome Gift -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-rose-50 dark:bg-rose-500/10 text-rose-500">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-14v4m0 0l8 4m-8-4l-8 4m8 4v10l8-4m0-14l-8 4" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('Welcome Gift') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.welcome_gift ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.welcome_gift ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Cash Module -->
|
||||
<div class="bg-white dark:bg-slate-800/20 p-4 rounded-2xl border border-slate-100 dark:border-slate-800/50 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="p-2 rounded-lg bg-amber-50 dark:bg-amber-500/10 text-amber-500">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2zm7-5a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
|
||||
</div>
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-300">{{ __('Cash Module') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-black uppercase tracking-widest"
|
||||
:class="detailCompany.settings?.cash_module ? 'bg-emerald-500/10 text-emerald-500' : 'bg-slate-100 dark:bg-slate-800 text-slate-400'">
|
||||
<span x-text="detailCompany.settings?.cash_module ? '{{ __('Enabled') }}' : '{{ __('Disabled') }}'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@ -1319,7 +1457,7 @@
|
||||
<div class="flex items-center gap-2.5">
|
||||
<div class="w-7 h-7 rounded-full bg-slate-100 dark:bg-slate-700 flex items-center justify-center text-[9px] font-black text-slate-500" x-text="'#' + (detailCompany.contracts.length - index)"></div>
|
||||
<div>
|
||||
<p class="text-[10px] font-black text-slate-700 dark:text-slate-200" x-text="new Date(contract.created_at).toLocaleString()"></p>
|
||||
<p class="text-[12px] font-black text-slate-700 dark:text-slate-200" x-text="new Date(contract.created_at).toLocaleString()"></p>
|
||||
<div class="flex items-center gap-1.5 mt-0.5">
|
||||
<span :class="contract.type === 'lease' ? 'text-blue-500 bg-blue-500/10' : 'text-amber-500 bg-amber-500/10'"
|
||||
class="text-[8px] font-black uppercase tracking-wider px-1.5 py-0.5 rounded"
|
||||
@ -1339,11 +1477,11 @@
|
||||
<template x-if="contract.type === 'lease'">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-1.5">
|
||||
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Contract Start') }}</p>
|
||||
<p class="text-[12px] font-black text-slate-400 uppercase tracking-widest">{{ __('Contract Start') }}</p>
|
||||
<p class="text-sm font-black text-slate-700 dark:text-white font-mono" x-text="contract.start_date?.substring(0, 10) || '-'"></p>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest">{{ __('Contract End') }}</p>
|
||||
<p class="text-[12px] font-black text-slate-400 uppercase tracking-widest">{{ __('Contract End') }}</p>
|
||||
<p class="text-sm font-black text-slate-700 dark:text-white font-mono" x-text="contract.end_date?.substring(0, 10) || '{{ __('Unlimited') }}'"></p>
|
||||
</div>
|
||||
</div>
|
||||
@ -1352,7 +1490,7 @@
|
||||
<template x-if="contract.type === 'buyout'">
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<div class="p-4 bg-amber-50/40 dark:bg-amber-500/5 rounded-2xl border border-amber-100/50 dark:border-amber-500/10">
|
||||
<p class="text-[10px] font-black text-amber-600 uppercase tracking-widest mb-2">{{ __('Warranty Service') }}</p>
|
||||
<p class="text-[12px] font-black text-amber-600 uppercase tracking-widest mb-2">{{ __('Warranty Service') }}</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-black text-slate-700 dark:text-slate-200 font-mono" x-text="contract.warranty_start_date?.substring(0, 10) || '-'"></span>
|
||||
<span class="text-slate-400 font-bold">~</span>
|
||||
@ -1360,7 +1498,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 bg-indigo-50/40 dark:bg-indigo-500/5 rounded-2xl border border-indigo-100/50 dark:border-indigo-500/10">
|
||||
<p class="text-[10px] font-black text-indigo-600 uppercase tracking-widest mb-2">{{ __('Software Service') }}</p>
|
||||
<p class="text-[12px] font-black text-indigo-600 uppercase tracking-widest mb-2">{{ __('Software Service') }}</p>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-black text-slate-700 dark:text-slate-200 font-mono" x-text="contract.software_start_date?.substring(0, 10) || '-'"></span>
|
||||
<span class="text-slate-400 font-bold">~</span>
|
||||
|
||||
@ -25,6 +25,11 @@
|
||||
if (newContent) {
|
||||
document.querySelector('#ajax-content-container').innerHTML = newContent.innerHTML;
|
||||
window.history.pushState({}, '', url);
|
||||
|
||||
// Re-initialize Preline UI components
|
||||
if (window.HSStaticMethods && typeof window.HSStaticMethods.autoInit === 'function') {
|
||||
window.HSStaticMethods.autoInit();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('AJAX Load Failed:', e);
|
||||
@ -152,7 +157,16 @@
|
||||
</script>
|
||||
|
||||
<div class="space-y-2 pb-20" x-data="machineApp()"
|
||||
@keydown.escape.window="showLogPanel = false; showInventoryPanel = false">
|
||||
@keydown.escape.window="showLogPanel = false; showInventoryPanel = false"
|
||||
@ajax:navigate.window.prevent="fetchPage($event.detail.url)"
|
||||
@submit.prevent="if($event.target.method.toLowerCase() === 'get') {
|
||||
const url = new URL($event.target.action, window.location.origin);
|
||||
new FormData($event.target).forEach((value, key) => {
|
||||
if(value.trim()) url.searchParams.set(key, value);
|
||||
else url.searchParams.delete(key);
|
||||
});
|
||||
fetchPage(url.pathname + url.search);
|
||||
}">
|
||||
<!-- Top Header & Actions -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
<div class="flex items-center gap-4">
|
||||
@ -189,18 +203,15 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[10px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">Syncing Data...</p>
|
||||
<p class="text-[12px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">{{ __('Loading Data') }}...</p>
|
||||
</div>
|
||||
|
||||
<div id="ajax-content-container"
|
||||
:class="{ 'opacity-30 pointer-events-none transition-opacity duration-300': isLoadingTable }"
|
||||
@ajax:navigate.prevent.stop="fetchPage($event.detail.url)"
|
||||
@click="if ($event.target.closest('a') && $event.target.closest('a').href && ($event.target.closest('a').href.includes('page=') || $event.target.closest('a').href.includes('search='))) { $event.preventDefault(); fetchPage($event.target.closest('a').href); }">
|
||||
:class="{ 'opacity-30 pointer-events-none transition-opacity duration-300': isLoadingTable }">
|
||||
|
||||
<!-- Filters Area -->
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<form method="GET" action="{{ route('admin.machines.index') }}" class="relative group"
|
||||
@submit.prevent="fetchPage($el.action + '?' + new URLSearchParams(new FormData($el)).toString())">
|
||||
<form method="GET" action="{{ route('admin.machines.index') }}" class="relative group">
|
||||
|
||||
<input type="hidden" name="tab" value="list">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
|
||||
@ -213,6 +224,7 @@
|
||||
</span>
|
||||
<input type="text" name="search" value="{{ request('search') }}"
|
||||
placeholder="{{ __('Search machines...') }}" class="luxury-input py-2.5 pl-12 pr-6 block w-72">
|
||||
<button type="submit" class="hidden"></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -321,17 +333,17 @@
|
||||
<div class="flex items-center justify-center gap-4">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500/50"></span>
|
||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-tighter">{{
|
||||
<span class="text-[12px] font-black text-slate-400 uppercase tracking-tighter">{{
|
||||
__('Normal') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500/50"></span>
|
||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-tighter">{{
|
||||
<span class="text-[12px] font-black text-slate-400 uppercase tracking-tighter">{{
|
||||
__('Normal') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500/50"></span>
|
||||
<span class="text-[10px] font-black text-slate-400 uppercase tracking-tighter">{{
|
||||
<span class="text-[12px] font-black text-slate-400 uppercase tracking-tighter">{{
|
||||
__('Normal') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -433,7 +445,7 @@
|
||||
<span class="truncate">{{ __('Machine Logs') }}</span>
|
||||
</h2>
|
||||
<div
|
||||
class="mt-2 flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2 text-[10px] sm:text-sm text-slate-500 dark:text-slate-400 font-bold uppercase tracking-widest overflow-hidden">
|
||||
class="mt-2 flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2 text-[12px] sm:text-sm text-slate-500 dark:text-slate-400 font-bold uppercase tracking-widest overflow-hidden">
|
||||
<span x-text="currentMachineSn"
|
||||
class="font-mono text-cyan-600 dark:text-cyan-400 truncate"></span>
|
||||
<span class="hidden sm:inline opacity-50">—</span>
|
||||
@ -458,7 +470,7 @@
|
||||
<div class="grid grid-cols-2 sm:flex sm:items-center gap-3 sm:gap-4 flex-1">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-1.5 sm:gap-2">
|
||||
<label
|
||||
class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.1em] whitespace-nowrap">{{
|
||||
class="text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.1em] whitespace-nowrap">{{
|
||||
__('From') }}</label>
|
||||
<input type="text" x-ref="startDatePicker" x-model="startDate"
|
||||
x-init="flatpickr($refs.startDatePicker, {
|
||||
@ -469,11 +481,11 @@
|
||||
defaultDate: startDate,
|
||||
onClose: (selectedDates, dateStr) => { startDate = dateStr; fetchLogs(1); }
|
||||
})"
|
||||
class="luxury-input text-[11px] h-9 sm:h-8 py-0 w-full sm:w-44 bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700">
|
||||
class="luxury-input text-[12px] h-9 sm:h-8 py-0 w-full sm:w-44 bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700">
|
||||
</div>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-1.5 sm:gap-2">
|
||||
<label
|
||||
class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.1em] whitespace-nowrap">{{
|
||||
class="text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.1em] whitespace-nowrap">{{
|
||||
__('To') }}</label>
|
||||
<input type="text" x-ref="endDatePicker" x-model="endDate"
|
||||
x-init="flatpickr($refs.endDatePicker, {
|
||||
@ -484,12 +496,12 @@
|
||||
defaultDate: endDate,
|
||||
onClose: (selectedDates, dateStr) => { endDate = dateStr; fetchLogs(1); }
|
||||
})"
|
||||
class="luxury-input text-[11px] h-9 sm:h-8 py-0 w-full sm:w-44 bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700">
|
||||
class="luxury-input text-[12px] h-9 sm:h-8 py-0 w-full sm:w-44 bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700">
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-start sm:justify-end">
|
||||
<button @click="startDate = ''; endDate = ''; fetchLogs(1)"
|
||||
class="text-[10px] font-bold text-cyan-600 dark:text-cyan-400 uppercase tracking-widest hover:text-cyan-500 transition-colors flex items-center gap-1.5">
|
||||
class="text-[12px] font-bold text-cyan-600 dark:text-cyan-400 uppercase tracking-widest hover:text-cyan-500 transition-colors flex items-center gap-1.5">
|
||||
<svg class="w-3 h-3" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2.5">
|
||||
<path d="M18 13.5V19a2 2 0 0 1-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h5.5l7.5 7.5z" />
|
||||
@ -556,7 +568,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[10px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">{{ __('Loading Data') }}...</p>
|
||||
<p class="text-[12px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">{{ __('Loading Data') }}...</p>
|
||||
</div>
|
||||
|
||||
<!-- Logs Container -->
|
||||
@ -569,13 +581,13 @@
|
||||
<thead class="bg-slate-50 dark:bg-slate-800/50">
|
||||
<tr>
|
||||
<th
|
||||
class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-200 dark:border-slate-700 w-40">
|
||||
class="px-6 py-4 text-[12px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-200 dark:border-slate-700 w-40">
|
||||
{{ __('Timestamp') }}</th>
|
||||
<th
|
||||
class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-200 dark:border-slate-700 w-24 text-center">
|
||||
class="px-6 py-4 text-[12px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-200 dark:border-slate-700 w-24 text-center">
|
||||
{{ __('Level') }}</th>
|
||||
<th
|
||||
class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-200 dark:border-slate-700">
|
||||
class="px-6 py-4 text-[12px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-200 dark:border-slate-700">
|
||||
{{ __('Message') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -594,7 +606,7 @@
|
||||
'bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/20': log.level === 'warning',
|
||||
'bg-rose-500/10 text-rose-600 dark:text-rose-400 border-rose-500/20': log.level === 'error'
|
||||
}"
|
||||
class="inline-flex items-center px-2 py-0.5 rounded-md border text-[10px] font-black uppercase tracking-wider">
|
||||
class="inline-flex items-center px-2 py-0.5 rounded-md border text-[12px] font-black uppercase tracking-wider">
|
||||
<span x-text="log.level"></span>
|
||||
</span>
|
||||
</td>
|
||||
@ -615,7 +627,7 @@
|
||||
class="p-4 hover:bg-slate-50 dark:hover:bg-slate-800/30 transition-colors">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<span
|
||||
class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest"
|
||||
class="text-[12px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest"
|
||||
x-text="formatDateTime(log.created_at)"></span>
|
||||
<span :class="{
|
||||
'bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 border-cyan-500/20': log.level === 'info',
|
||||
@ -648,7 +660,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
class="text-[11px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{
|
||||
class="text-[12px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{
|
||||
__('No matching logs found') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -666,9 +678,9 @@
|
||||
</button>
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-[10px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-widest" x-text="currentPage"></span>
|
||||
<span class="text-[10px] font-black text-slate-300 dark:text-slate-600 uppercase">/</span>
|
||||
<span class="text-[10px] font-black text-slate-500 dark:text-slate-400 uppercase tracking-widest" x-text="lastPage"></span>
|
||||
<span class="text-[12px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-widest" x-text="currentPage"></span>
|
||||
<span class="text-[12px] font-black text-slate-300 dark:text-slate-600 uppercase">/</span>
|
||||
<span class="text-[12px] font-black text-slate-500 dark:text-slate-400 uppercase tracking-widest" x-text="lastPage"></span>
|
||||
</div>
|
||||
|
||||
<button @click="fetchLogs(currentPage + 1)"
|
||||
@ -790,7 +802,7 @@
|
||||
<span class="truncate">{{ __('Stock & Expiry Overview') }}</span>
|
||||
</h2>
|
||||
<div
|
||||
class="mt-2 flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2 text-[10px] sm:text-sm text-slate-500 dark:text-slate-400 font-bold uppercase tracking-widest overflow-hidden">
|
||||
class="mt-2 flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2 text-[12px] sm:text-sm text-slate-500 dark:text-slate-400 font-bold uppercase tracking-widest overflow-hidden">
|
||||
<span x-text="currentMachineSn"
|
||||
class="font-mono text-cyan-600 dark:text-cyan-400 truncate"></span>
|
||||
<span class="hidden sm:inline opacity-50">—</span>
|
||||
@ -861,26 +873,26 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[10px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">{{ __('Loading Data') }}...</p>
|
||||
<p class="text-[12px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">{{ __('Loading Data') }}...</p>
|
||||
</div>
|
||||
|
||||
<!-- Status Legend -->
|
||||
<div class="flex items-center gap-6 mb-6" x-show="!inventoryLoading">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-3 h-3 rounded-full bg-rose-500 shadow-lg shadow-rose-500/30"></span>
|
||||
<span class="text-[10px] font-black text-slate-500 uppercase tracking-[0.15em]">{{
|
||||
<span class="text-[12px] font-black text-slate-500 uppercase tracking-[0.15em]">{{
|
||||
__('Expired') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="w-3 h-3 rounded-full bg-amber-500 shadow-lg shadow-amber-500/30"></span>
|
||||
<span class="text-[10px] font-black text-slate-500 uppercase tracking-[0.15em]">{{
|
||||
<span class="text-[12px] font-black text-slate-500 uppercase tracking-[0.15em]">{{
|
||||
__('Warning') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span
|
||||
class="w-3 h-3 rounded-full bg-emerald-500 shadow-lg shadow-emerald-500/30"></span>
|
||||
<span class="text-[10px] font-black text-slate-500 uppercase tracking-[0.15em]">{{
|
||||
<span class="text-[12px] font-black text-slate-500 uppercase tracking-[0.15em]">{{
|
||||
__('Normal') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -975,7 +987,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
<span
|
||||
class="text-[11px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{
|
||||
class="text-[12px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{
|
||||
__('No slot data available') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -18,6 +18,11 @@
|
||||
if (newContent) {
|
||||
document.querySelector('#ajax-content-container').innerHTML = newContent.innerHTML;
|
||||
window.history.pushState({}, '', url);
|
||||
|
||||
// Re-initialize Preline UI components
|
||||
if (window.HSStaticMethods && typeof window.HSStaticMethods.autoInit === 'function') {
|
||||
window.HSStaticMethods.autoInit();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('AJAX Load Failed:', e);
|
||||
@ -98,6 +103,14 @@
|
||||
window.dispatchEvent(new CustomEvent('toast', { detail: { message: e.message, type: 'error' } }));
|
||||
});
|
||||
}
|
||||
}" @ajax:navigate.window.prevent="fetchPage($event.detail.url)"
|
||||
@submit.prevent="if($event.target.method.toLowerCase() === 'get') {
|
||||
const url = new URL($event.target.action, window.location.origin);
|
||||
new FormData($event.target).forEach((value, key) => {
|
||||
if(value.trim()) url.searchParams.set(key, value);
|
||||
else url.searchParams.delete(key);
|
||||
});
|
||||
fetchPage(url.pathname + url.search);
|
||||
}">
|
||||
<!-- 1. Header Area -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||
@ -129,7 +142,7 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[10px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">Syncing Data...</p>
|
||||
<p class="text-[10px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">{{ __('Loading Data') }}...</p>
|
||||
</div>
|
||||
|
||||
<div id="ajax-content-container"
|
||||
@ -138,8 +151,7 @@
|
||||
<!-- Toolbar & Filters -->
|
||||
<div class="flex flex-col md:flex-row items-center justify-between mb-8 gap-4">
|
||||
<form method="GET" action="{{ route('admin.machines.permissions') }}"
|
||||
class="flex flex-wrap items-center gap-4 w-full md:w-auto"
|
||||
@submit.prevent="fetchPage($el.action + '?' + new URLSearchParams(new FormData($el)).toString())">
|
||||
class="flex flex-wrap items-center gap-4 w-full md:w-auto">
|
||||
|
||||
<div class="relative group">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
|
||||
@ -161,6 +173,8 @@
|
||||
:placeholder="__('All Companies')" onchange="this.dispatchEvent(new CustomEvent('ajax:navigate', { detail: { url: this.form.action + '?' + new URLSearchParams(new FormData(this.form)).toString() }, bubbles: true }))" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<button type="submit" class="hidden"></button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@ -279,7 +279,7 @@ window.utilizationDashboard = function(initialMachines, initialStats) {
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-[10px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">Syncing Data...</p>
|
||||
<p class="text-[10px] font-black text-cyan-600 dark:text-cyan-400 uppercase tracking-[0.4em] animate-pulse">{{ __('Loading Data') }}...</p>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-4 space-y-4" :class="{ 'opacity-40 pointer-events-none transition-opacity duration-500': loading }">
|
||||
|
||||
@ -43,7 +43,9 @@
|
||||
<span class="hidden xxs:inline">{{ __('Previous') }}</span>
|
||||
</span>
|
||||
@else
|
||||
<a href="{{ $paginator->previousPageUrl() }}" class="h-9 inline-flex items-center gap-x-1.5 sm:gap-x-2 px-3 sm:px-4 rounded-xl text-[10px] sm:text-xs font-black bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-300 border border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 transition-all shadow-sm group">
|
||||
<a href="{{ $paginator->previousPageUrl() }}"
|
||||
onclick="{ event.preventDefault(); const url = this.href; const evt = new CustomEvent('ajax:navigate', { detail: { url: url }, bubbles: true, cancelable: true }); if (this.dispatchEvent(evt)) { window.location.href = url; } }"
|
||||
class="h-9 inline-flex items-center gap-x-1.5 sm:gap-x-2 px-3 sm:px-4 rounded-xl text-[10px] sm:text-xs font-black bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-300 border border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 transition-all shadow-sm group">
|
||||
<svg class="size-3.5 sm:size-4 text-cyan-500 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M15 19l-7-7 7-7"/></svg>
|
||||
<span class="hidden xxs:inline">{{ __('Previous') }}</span>
|
||||
</a>
|
||||
@ -67,7 +69,9 @@
|
||||
|
||||
{{-- Next Page Link --}}
|
||||
@if ($paginator->hasMorePages())
|
||||
<a href="{{ $paginator->nextPageUrl() }}" class="h-9 inline-flex items-center gap-x-1.5 sm:gap-x-2 px-3 sm:px-4 rounded-xl text-[10px] sm:text-xs font-black bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-300 border border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 transition-all shadow-sm group">
|
||||
<a href="{{ $paginator->nextPageUrl() }}"
|
||||
onclick="{ event.preventDefault(); const url = this.href; const evt = new CustomEvent('ajax:navigate', { detail: { url: url }, bubbles: true, cancelable: true }); if (this.dispatchEvent(evt)) { window.location.href = url; } }"
|
||||
class="h-9 inline-flex items-center gap-x-1.5 sm:gap-x-2 px-3 sm:px-4 rounded-xl text-[10px] sm:text-xs font-black bg-white dark:bg-slate-900 text-slate-600 dark:text-slate-300 border border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-800 transition-all shadow-sm group">
|
||||
<span class="hidden xxs:inline">{{ __('Next') }}</span>
|
||||
<svg class="size-3.5 sm:size-4 text-cyan-500 group-hover:scale-110 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M9 5l7 7-7 7"/></svg>
|
||||
</a>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user