[FEAT] 實作角色權限分類、租戶角控管理與介面多語系優化
1. [FEAT] 權限劃分為「系統層級」與「客戶層級」,並在後端強制過濾跨權限分配。 2. [FEAT] 整合選單權限至主選單層級 (基本設定、權限設定),簡化角色管理 UI。 3. [STYLE] 側邊欄優化:補齊多語系翻譯,並為基本設定子選單增加視覺圖示。 4. [REFACTOR] 更新 RoleSeeder,將 tenant-admin 重新分類為客戶層級角色。
This commit is contained in:
@@ -14,7 +14,8 @@ trigger: always_on
|
||||
* **核心組件**:Redis (用於高併發 IoT 隊列與快取,為系統穩定之必要條件)
|
||||
* **前端視圖 (View)**:Laravel Blade
|
||||
* **前端互動 (JS)**:Alpine.js (專注於行為,不負責渲染)
|
||||
* **介面與樣式 (CSS)**:Tailwind CSS + Preline UI (直接寫作於 Blade 模板中)
|
||||
* **介面與樣式 (CSS)**:Tailwind CSS + Preline UI (直接寫作於 Blade 模板中)。
|
||||
* **重要規範**:Preline UI 僅作為「原子組件」與「JS 互動邏輯」的參考庫。整體的「佈局」與「美學」必須嚴格遵守「極簡奢華風 UI 實作規範 (SKILL.md)」。
|
||||
* **前端建置工具**:Vite
|
||||
* **資料庫**:MySQL 8.0
|
||||
* **開發環境**:Laravel Sail (Docker / WSL2)
|
||||
@@ -64,6 +65,7 @@ trigger: always_on
|
||||
* **角色設定**:你是一位專業的全端開發工程師助手。
|
||||
* **代碼生成指令**:
|
||||
* 所有的解釋說明請使用 **繁體中文**。
|
||||
* **【警告:Preline 冗餘】** Preline UI 的官方範例常包含多餘的控制項(如頂部筆數切換)。**嚴禁**照抄其佈局,必須確保頂部工具列(Header/Toolbar)維持極簡,重複功能一律收納至底部。
|
||||
* **【警告】** 此專案前端禁用 React / Vue / Inertia.js。所有的前端頁面生成必須使用 **Blade 模板** 結合 **Tailwind CSS** 與 **Alpine.js**。
|
||||
* **【多語系強制要求】** 任何新增的 Blade UI 區塊,禁止硬編碼 (Hard-coded) 中文或英文。必須使用 `__('...')` 並同步在 `lang/*.json` 補上翻譯。
|
||||
* 生成 UI 區塊時,必須優先參考與產生 **Preline UI** 風格與結構的標記語法。
|
||||
|
||||
@@ -58,7 +58,11 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
||||
## 3. 動畫與互動
|
||||
|
||||
### 進場動畫
|
||||
- **`.animate-luxury-in`**: 所有的主內容區域或卡片在頁面載入時,應具備由下而上的淡入效果。
|
||||
- **`.animate-luxury-in`**: <EFBFBD><EFBFBD><EFBFBD>厩<EFBFBD>銝餃<EFBFBD>摰孵<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>∠<EFBFBD><EFBFBD>券<EFBFBD><EFBFBD>Z<EFBFBD><EFBFBD>交<EFBFBD>嚗峕<EFBFBD><EFBFBD>瑕<EFBFBD><EFBFBD>曹<EFBFBD><EFBFBD>諹<EFBFBD><EFBFBD><EFBFBD>楚<EFBFBD>交<EFBFBD><EFBFBD>栶<EFBFBD><EFBFBD>
|
||||
|
||||
### 鈭鍦<E988AD><E98DA6>擧腹 (Transitions)
|
||||
- **璅蹱<E79285><E8B9B1><EFBFBD><EFBFBD>**: <20><><EFBFBD>厩<EFBFBD><E58EA9>詨<EFBFBD><E8A9A8><EFBFBD>𠧧敶抵<E695B6><E68AB5>𤤿<EFBFBD><F0A4A4BF>擧腹<E693A7><E885B9><EFBFBD>嚗𣬚絞銝<E7B59E>撱箄降雿輻鍂 **`duration-300`** (300ms)<29><>
|
||||
- **靘见<E99D98>**: 璆萄<E79286>蝝啣凝<E595A3><E5879D><EFBFBD>𤩺<EFBFBD>摨西<E691A8><E8A5BF>硋虾蝮桃<E89DAE><E6A183><EFBFBD> `150ms`嚗䔶<EFBFBD>瘨匧<EFBFBD><EFBFBD>峕艶<EFBFBD>脰<EFBFBD>雿滨宏<EFBFBD><EFBFBD><EFBFBD><EFBFBD>蓥<EFBFBD>敺衤誑 `300ms` <20>箸<EFBFBD><E7AEB8><EFBFBD>
|
||||
|
||||
### Alpine.js 互動模式 (以時間選擇器為例)
|
||||
- **互動原則**: 點擊觸發下拉選單時,必須使用 `x-transition` 且帶有 `scale` 偏移。
|
||||
@@ -67,6 +71,7 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
||||
- [ ] **列表佈局**: 是否採用「整合式卡片」結構且內距設為 `p-8`?
|
||||
- [ ] **分頁與總數**: 列表底部是否正確召喚 `vendor.pagination.luxury`?
|
||||
- [ ] **文字色階**: 符合標題 `slate-900/white` 與標籤 `slate-500` 的對比度。
|
||||
- [ ] **<EFBFBD>航<EFBFBD><EFBFBD>扳炎<EFBFBD><EFBFBD>**: 鈭𣬚<E988AD>鞈<EFBFBD><E99E88><EFBFBD>臬炏<E887AC>𥪜<EFBFBD> `text-xs` (12px) 銝娍<E98A9D><E5A88D>滢<EFBFBD>頞<EFBFBD><E9A09E> `font-bold`嚗<EFBFBD>
|
||||
|
||||
## 5. 開發注意事項 (Important Notes)
|
||||
|
||||
@@ -152,34 +157,54 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
||||
</select>
|
||||
```
|
||||
|
||||
## 8. 蝺刻摩<E588BB><E691A9>底<EFBFBD><E5BA95><EFBFBD>閬讐<E996AC> (Detail & Edit Views)
|
||||
|
||||
<EFBFBD>箔<EFBFBD>霈枏<EFBFBD>撅方<EFBFBD>閮𦠜凒<EFBFBD>瑁<EFBFBD>閬箏<EFBFBD>撠𠬍<EFBFBD><EFBFBD><EFBFBD><EFBFBD>见<EFBFBD>憛<EFBFBD> (Section) <20><><EFBFBD>蝷箸<E89DB7><E7AEB8>∠鍂銝滚<E98A9D><E6BB9A><EFBFBD><EFBFBD><EFBFBD>脫<EFBFBD>鞊~<E99E8A><EFBD9E>
|
||||
|
||||
### <20><>憛𠰴<E6869B>蝷箄𠧧敶拇<E695B6>鞊<EFBFBD> (Section Icon Palette)
|
||||
- **<EFBFBD>箸𧋦鞈<EFBFBD><EFBFBD> (Basic Info)**: **蝧删<E89DA7><E588A0><EFBFBD> (`Emerald`)**<EFBFBD><EFBFBD>誨銵冽瓲敹<EFBFBD><EFBFBD><EFBFBD>帘摰朞<EFBFBD>韏琿<EFBFBD><EFBFBD><EFBFBD>
|
||||
- 璅<><E79285>: `bg-emerald-500/10 text-emerald-500`
|
||||
- **蝖祇<E89D96>/<2F>埝局閮剖<E996AE>**: **<EFBFBD>亦<EFBFBD><EFBFBD><EFBFBD> (`Amber/Orange`)**<2A><>誨銵典<E98AB5>雿栶<E99BBF><E6A0B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𦻖<EFBFBD><F0A6BB96>′擃磰郎<E7A3B0>𨳍<EFBFBD><F0A8B38D>
|
||||
- 璅<><E79285>: `bg-amber-500/10 text-amber-500`
|
||||
- **蝟餌絞/<2F>脤<EFBFBD>閮剖<E996AE>**: **<EFBFBD>𥡝<EFBFBD><EFBFBD><EFBFBD> (`Indigo`)**<2A><>誨銵券<E98AB5>頛胯<E9A09B><E883AF><EFBFBD><EFBFBD>鞱<EFBFBD>瘛勗惜<E58B97>滨蔭<E6BBA8><E894AD>
|
||||
- 璅<><E79285>: `bg-indigo-500/10 text-indigo-500`
|
||||
- **<EFBFBD>梢麬/蝘駁膄<E9A781>蓥<EFBFBD>**: **<EFBFBD>怎麯蝝<EFBFBD> (`Rose`)**<2A><>誨銵函聦憯墧<E686AF>扳<EFBFBD>雿栶<E99BBF><E6A0B6>
|
||||
- 璅<><E79285>: `bg-rose-500/10 text-rose-500`
|
||||
```
|
||||
|
||||
## 8. 資料表格規範 (Data Tables)
|
||||
|
||||
為了確保管理後台資料的可讀性與精密感,表格內的所有文字級別必須對齊以下規範:
|
||||
|
||||
### 文字大小與權重 (Typography Hierarchy)
|
||||
- **表頭 (Table Header)**:
|
||||
- 類別: `text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em]`
|
||||
- 作用: 提供清晰的欄位定義而不奪取資料視覺焦點。
|
||||
- 憿𧼮ê̌: `text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em]`
|
||||
- 雿𦦵鍂: <20>𣂷<EFBFBD>皜<EFBFBD>苊<EFBFBD><E88B8A><EFBFBD>雿滚<E99BBF>蝢抵<E89DA2>䔶<EFBFBD>憟芸<E6869F>鞈<EFBFBD><E99E88>閬𤥁死<F0A4A581>阡<EFBFBD><E998A1><EFBFBD><EFBFBD><EFBFBD>躰雲憭惩<E686AD>瘥𥪜漲<F0A5AA9C><E6BCB2>
|
||||
- **主標題 (Primary Item)**:
|
||||
- 類別: `text-base font-extrabold text-slate-800 dark:text-slate-100`
|
||||
- 範例: 公司名稱、機體名稱。
|
||||
- **次要資訊 (Secondary Info)**:
|
||||
- 類別: `text-[11px] font-bold text-slate-400 dark:text-slate-500 tracking-[0.1em]`
|
||||
- 憿𧼮ê̌: `text-xs font-bold text-slate-500 dark:text-slate-400 tracking-wide`
|
||||
- 範例: 使用者帳號、備註、權限名稱。
|
||||
- **狀態標籤 (Status Badge)**:
|
||||
- 範例: 啟用 (`emerald`)、禁用 (`rose`) / 角色名稱 (`sky`/`indigo`)。
|
||||
- 特性: `px-2.5 py-1 rounded-lg text-[11px] font-black border tracking-wider`
|
||||
- <EFBFBD>寞<EFBFBD><EFBFBD>: `px-2.5 py-1 rounded-lg text-xs font-bold border tracking-wider`
|
||||
|
||||
### 空間與反應 (Spacing & Interaction)
|
||||
- **單元格內距**: 統一使用 `px-6 py-6`。
|
||||
- **懸停反應**: 必須在 `tr` 套用 `group` 且子元素套用 `group-hover:bg-slate-50/80` (深色: `dark:group-hover:bg-slate-800/40`) 以提供高級的互動感知。
|
||||
- **<EFBFBD>𣇉內摰孵膥<EFBFBD>詨<EFBFBD> (Icon Hover Palette)**:
|
||||
- <20>𡑒”撌血<E6928C><E8A180><EFBFBD>蜓<EFBFBD>𣇉內摰孵膥<E5ADB5><E886A5> `group-hover` <20><><EFBFBD><EFBFBD>厩眏瘛∟𠧧<E2889F>峕艶頧厩<E9A0A7> **撖阡<E69296>銝駁<E98A9D><E9A781><EFBFBD>**<EFBFBD><EFBFBD>
|
||||
- 憿𧼮ê̌: `group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300`<EFBFBD><EFBFBD>
|
||||
- **<EFBFBD><EFBFBD><EFBFBD><EFBFBD>峕郊霈𡃏𠧧**:
|
||||
- 銝餅<E98A9D>憿峕<E686BF>摮堒銁 `group-hover` <20><><EFBFBD><EFBFBD>峕郊霈𡃏𠧧嚗䔶誑撘瑕<E69298>暺墧<E69ABA>撘訫<E69298><E8A8AB><EFBFBD>
|
||||
- 憿𧼮ê̌: `group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors`<EFBFBD><EFBFBD>
|
||||
|
||||
### 分頁與列表控制項 (Pagination & Controls)
|
||||
為了維持操作一致性,所有列表的分頁與切換組件必須遵循以下「Luxury Jump」模式:
|
||||
- **統一高度**: 所有控制項(按鈕、下拉選單)固定為 `h-9` (36px)。
|
||||
- **筆數切換 (Limit Selector)**:
|
||||
- 樣式: 使用 `bg-slate-50` (深色: `dark:bg-slate-800`) 配合 `text-[11px] font-black`。
|
||||
- 位置: 位於表格右上方。
|
||||
- 閬讐<EFBFBD>: **蝳<>迫**<EFBFBD>刻”<EFBFBD>潔<EFBFBD><EFBFBD>對<EFBFBD>Header/Toolbar嚗厰<E59A97>銴<EFBFBD>𦆮蝵桃<E89DB5><E6A183>詨<EFBFBD><E8A9A8>偦<EFBFBD><E581A6>柴<EFBFBD><E69FB4>絞銝<E7B59E><E98A9D>嗥<EFBFBD><E597A5>澆<EFBFBD><E6BE86>典<EFBFBD><E585B8><EFBFBD><EFBFBD>雿溻<E99BBF><E6BABB>
|
||||
- **分頁導航 (Luxury Jump)**:
|
||||
- 模式: 捨棄傳統頁碼按鈕,全端統一使用「跳轉選單」。
|
||||
- 寬度: 下拉選單內部 Padding 為 `pl-4 pr-10`。
|
||||
@@ -189,30 +214,38 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
||||
- 數字顏色對齊 `text-slate-600` (深色: `text-slate-300`)。
|
||||
|
||||
### 底部清單控制項 (Bottom List Controls)
|
||||
為了確保長列表的操作便利,清單底部必須具備以下元素:
|
||||
- **位置**: 卡片底部,內距 `px-8 py-6`,並帶有 `border-t border-slate-100/50 dark:border-slate-800/50`。
|
||||
- **左側:每頁筆數 (Per Page Selector)**:
|
||||
- 樣式: `luxury-select` (緊湊型),高度固定為 `h-9`。
|
||||
- 規範: 提供 `20, 50, 100` 等選項,並在變更時立即提交。
|
||||
- **中央:資料指示 (Info)**:
|
||||
- 樣式: `text-[11px] font-bold tracking-widest uppercase text-slate-400`。
|
||||
- 內容: `Showing X to Y of Z results`。
|
||||
- **右側:分頁導航 (Pagination)**:
|
||||
- 模式: 優先使用 `Luxury Jump` (跳轉下拉選單) 以節省空間並提升效率。
|
||||
<EFBFBD>箔<EFBFBD>蝣箔<EFBFBD><EFBFBD>瑕<EFBFBD>銵函<EFBFBD><EFBFBD>滢<EFBFBD>靘踹⏚嚗峕<EFBFBD><EFBFBD>桀<EFBFBD><EFBFBD>### 璅蹱<E79285><E8B9B1>滢<EFBFBD><E6BBA2>厰<EFBFBD> (Standard Action Icons)
|
||||
銵冽聢<EFBFBD>抒<EFBFBD><EFBFBD>滢<EFBFBD>甈<EFBFBD><EFBFBD>嚗<EFBFBD><EFBFBD><EFBFBD>𣬚楊頛胯<EFBFBD>溻<EFBFBD><EFBFBD><EFBFBD><EFBFBD>⏛<EFBFBD>扎<EFBFBD>溻<EFBFBD><EFBFBD><EFBFBD>諹底<EFBFBD><EFBFBD><EFBFBD>㵪<EFBFBD>敹<EFBFBD><EFBFBD>雿輻鍂隞乩<EFBFBD>摰𡁶儔銋<EFBFBD> **<EFBFBD>屸<EFBFBD><EFBFBD>烐<EFBFBD>皞<EFBFBD> (Gold Standard)<29><>**嚗<>
|
||||
|
||||
### 標準清單萬用模板 (Standard List View Bible)
|
||||
建立新列表頁面時,**必須**以此結構為基底:
|
||||
- **<EFBFBD>勗<EFBFBD>璅<EFBFBD><EFBFBD>**:
|
||||
- 摰孵膥: `p-2 rounded-lg bg-slate-50 dark:bg-slate-800`
|
||||
- 銝餉𠧧: `text-slate-400`
|
||||
- <20>𦠜<EFBFBD>: `border border-transparent` (<28>脤<EFBFBD><E884A4>滩<EFBFBD><E6BBA9><EFBFBD>)
|
||||
- <20>擧腹: `transition-all` (雿輻鍂<E8BCBB>鞱身<E99EB1>笔漲隞亦Ⅱ靽苷<E99DBD><E88BB7>賣<EFBFBD>)
|
||||
- <20>𣇉內蝎㛖敦: `stroke-width="2.5"`
|
||||
- 撠箏站: `w-4 h-4`
|
||||
|
||||
```html
|
||||
<div class="space-y-10 pb-20">
|
||||
<!-- 1. Header Area: 標題與全局按鈕 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Title') }}</h1>
|
||||
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Subtitle') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn-luxury-primary items-center gap-2">...</button>
|
||||
- **蝺刻摩<E588BB>厰<EFBFBD> (Edit)**:
|
||||
- <20>詨<EFBFBD><E8A9A8>寞<EFBFBD>: `hover:text-cyan-500 hover:bg-cyan-500/5 hover:border-cyan-500/20`
|
||||
- SVG 頝臬<E9A09D>:
|
||||
```html
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><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>
|
||||
```
|
||||
|
||||
- **<EFBFBD>亦<EFBFBD>閰單<EFBFBD> (View/Detail)**:
|
||||
- <20>詨<EFBFBD><E8A9A8>寞<EFBFBD>: `hover:text-indigo-500 hover:bg-indigo-500/5 hover:border-indigo-500/20`
|
||||
- SVG 頝臬<E9A09D>:
|
||||
```html
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"/></svg>
|
||||
```
|
||||
|
||||
- **<EFBFBD>芷膄<EFBFBD>厰<EFBFBD> (Delete)**:
|
||||
- <20>詨<EFBFBD><E8A9A8>寞<EFBFBD>: `hover:text-rose-500 hover:bg-rose-500/5 hover:border-rose-500/20`
|
||||
- SVG 頝臬<E9A09D>:
|
||||
```html
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>
|
||||
```
|
||||
y items-center gap-2">...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -220,8 +253,9 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||
<!-- Toolbar & Filters (mb-10) -->
|
||||
<div class="flex items-center justify-between mb-10">
|
||||
<form class="flex items-center gap-4">
|
||||
<!-- luxury-select & luxury-input -->
|
||||
<form class="relative group">
|
||||
<!-- <EFBFBD><EFBFBD><EFBFBD><EFBFBD>蹱<EFBFBD>撠𧢲<EFBFBD><EFBFBD>硋<EFBFBD>閬<EFBFBD><EFBFBD><EFBFBD>擧蕪<EFBFBD>剁<EFBFBD>蝳<EFBFBD>迫<EFBFBD>滩<EFBFBD>蝑<EFBFBD>彍<EFBFBD><EFBFBD><EFBFBD> -->
|
||||
<input type="text" class="luxury-input pl-12 pr-6 w-64" placeholder="{{ __('Search...') }}">
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\BasicSettings;
|
||||
|
||||
use App\Http\Controllers\Admin\AdminController;
|
||||
use App\Models\Machine\Machine;
|
||||
use App\Models\Machine\MachineModel;
|
||||
use App\Models\System\PaymentConfig;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class MachineSettingController extends AdminController
|
||||
{
|
||||
/**
|
||||
* 顯示機台設定列表
|
||||
*/
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$per_page = $request->input('per_page', 20);
|
||||
$query = Machine::query()->with(['machineModel', 'paymentConfig', 'company']);
|
||||
|
||||
// 搜尋:名稱或序號
|
||||
if ($search = $request->input('search')) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%")
|
||||
->orWhere('serial_no', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
$machines = $query->latest()
|
||||
->paginate($per_page)
|
||||
->withQueryString();
|
||||
|
||||
$models = MachineModel::select('id', 'name')->get();
|
||||
$paymentConfigs = PaymentConfig::select('id', 'name')->get();
|
||||
// 這裡應根據租戶 (Company) 決定可用的選項,暫採簡單模擬或從 Auth 取得
|
||||
$companies = \App\Models\System\Company::select('id', 'name')->get();
|
||||
|
||||
return view('admin.basic-settings.machines.index', compact('machines', 'models', 'paymentConfigs', 'companies'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 儲存新機台 (僅核心欄位)
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'serial_no' => 'required|string|unique:machines,serial_no',
|
||||
'company_id' => 'required|exists:companies,id',
|
||||
'machine_model_id' => 'required|exists:machine_models,id',
|
||||
'payment_config_id' => 'nullable|exists:payment_configs,id',
|
||||
'images.*' => 'image|mimes:jpeg,png,jpg,gif|max:2048',
|
||||
]);
|
||||
|
||||
$imagePaths = [];
|
||||
if ($request->hasFile('images')) {
|
||||
foreach (array_slice($request->file('images'), 0, 3) as $image) {
|
||||
$imagePaths[] = $this->processAndStoreImage($image);
|
||||
}
|
||||
}
|
||||
|
||||
$machine = Machine::create(array_merge($validated, [
|
||||
'status' => 'offline',
|
||||
'creator_id' => auth()->id(),
|
||||
'updater_id' => auth()->id(),
|
||||
'card_reader_seconds' => 30, // 預設值
|
||||
'card_reader_checkout_time_1' => '22:30:00',
|
||||
'card_reader_checkout_time_2' => '23:45:00',
|
||||
'payment_buffer_seconds' => 5,
|
||||
'images' => $imagePaths,
|
||||
]));
|
||||
|
||||
return redirect()->route('admin.basic-settings.machines.index')
|
||||
->with('success', __('Machine created successfully.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 顯示詳細編輯頁面
|
||||
*/
|
||||
public function edit(Machine $machine): View
|
||||
{
|
||||
$models = MachineModel::select('id', 'name')->get();
|
||||
$paymentConfigs = PaymentConfig::select('id', 'name')->get();
|
||||
$companies = \App\Models\System\Company::select('id', 'name')->get();
|
||||
|
||||
return view('admin.basic-settings.machines.edit', compact('machine', 'models', 'paymentConfigs', 'companies'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新機台詳細參數
|
||||
*/
|
||||
public function update(Request $request, Machine $machine): RedirectResponse
|
||||
{
|
||||
Log::info('Machine Update Request', ['machine_id' => $machine->id, 'data' => $request->all()]);
|
||||
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'card_reader_seconds' => 'required|integer|min:0',
|
||||
'payment_buffer_seconds' => 'required|integer|min:0',
|
||||
'card_reader_checkout_time_1' => 'nullable|string',
|
||||
'card_reader_checkout_time_2' => 'nullable|string',
|
||||
'heating_start_time' => 'nullable|string',
|
||||
'heating_end_time' => 'nullable|string',
|
||||
'card_reader_no' => 'nullable|string|max:255',
|
||||
'key_no' => 'nullable|string|max:255',
|
||||
'invoice_status' => 'required|integer|in:0,1,2',
|
||||
'welcome_gift_enabled' => 'boolean',
|
||||
'is_spring_slot_1_10' => 'boolean',
|
||||
'is_spring_slot_11_20' => 'boolean',
|
||||
'is_spring_slot_21_30' => 'boolean',
|
||||
'is_spring_slot_31_40' => 'boolean',
|
||||
'is_spring_slot_41_50' => 'boolean',
|
||||
'is_spring_slot_51_60' => 'boolean',
|
||||
'member_system_enabled' => 'boolean',
|
||||
'machine_model_id' => 'required|exists:machine_models,id',
|
||||
'payment_config_id' => 'nullable|exists:payment_configs,id',
|
||||
]);
|
||||
|
||||
Log::info('Machine Update Validated Data', ['data' => $validated]);
|
||||
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||
Log::error('Machine Update Validation Failed', ['errors' => $e->errors()]);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$machine->update(array_merge($validated, [
|
||||
'updater_id' => auth()->id(),
|
||||
]));
|
||||
|
||||
// 處理圖片更新 (若有上傳新圖片,則替換或附加,這裡採簡單邏輯:若有傳 images 則全換)
|
||||
if ($request->hasFile('images')) {
|
||||
// 刪除舊圖
|
||||
if (!empty($machine->images)) {
|
||||
foreach ($machine->images as $oldPath) {
|
||||
Storage::disk('public')->delete($oldPath);
|
||||
}
|
||||
}
|
||||
|
||||
$imagePaths = [];
|
||||
foreach (array_slice($request->file('images'), 0, 3) as $image) {
|
||||
$imagePaths[] = $this->processAndStoreImage($image);
|
||||
}
|
||||
$machine->update(['images' => $imagePaths]);
|
||||
}
|
||||
|
||||
return redirect()->route('admin.basic-settings.machines.index')
|
||||
->with('success', __('Machine settings updated successfully.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 處理圖片並轉換為 WebP
|
||||
*/
|
||||
private function processAndStoreImage($file): string
|
||||
{
|
||||
$filename = Str::random(40) . '.webp';
|
||||
$path = 'machines/' . $filename;
|
||||
|
||||
// 建立圖資源
|
||||
$image = null;
|
||||
$extension = strtolower($file->getClientOriginalExtension());
|
||||
|
||||
switch ($extension) {
|
||||
case 'jpeg':
|
||||
case 'jpg':
|
||||
$image = imagecreatefromjpeg($file->getRealPath());
|
||||
break;
|
||||
case 'png':
|
||||
$image = imagecreatefrompng($file->getRealPath());
|
||||
break;
|
||||
case 'gif':
|
||||
$image = imagecreatefromgif($file->getRealPath());
|
||||
break;
|
||||
case 'webp':
|
||||
$image = imagecreatefromwebp($file->getRealPath());
|
||||
break;
|
||||
}
|
||||
|
||||
if ($image) {
|
||||
// 確保目錄存在
|
||||
Storage::disk('public')->makeDirectory('machines');
|
||||
$fullPath = Storage::disk('public')->path($path);
|
||||
|
||||
// 轉換並儲存
|
||||
imagewebp($image, $fullPath, 80); // 品質 80
|
||||
imagedestroy($image);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
// Fallback to standard store if GD fails
|
||||
return $file->store('machines', 'public');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\BasicSettings;
|
||||
|
||||
use App\Http\Controllers\Admin\AdminController;
|
||||
use App\Models\System\PaymentConfig;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
||||
class PaymentConfigController extends AdminController
|
||||
{
|
||||
/**
|
||||
* 顯示金流配置列表
|
||||
*/
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$per_page = $request->input('per_page', 20);
|
||||
$configs = PaymentConfig::query()
|
||||
->with(['company', 'creator'])
|
||||
->latest()
|
||||
->paginate($per_page)
|
||||
->withQueryString();
|
||||
|
||||
return view('admin.basic-settings.payment-configs.index', [
|
||||
'paymentConfigs' => $configs
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 顯示新增頁面
|
||||
*/
|
||||
public function create(): View
|
||||
{
|
||||
$companies = \App\Models\System\Company::select('id', 'name')->get();
|
||||
return view('admin.basic-settings.payment-configs.create', compact('companies'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 儲存金流配置
|
||||
*/
|
||||
public function store(Request $request): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'company_id' => 'required|exists:companies,id',
|
||||
'settings' => 'required|array',
|
||||
]);
|
||||
|
||||
PaymentConfig::create([
|
||||
'name' => $request->name,
|
||||
'company_id' => $request->company_id,
|
||||
'settings' => $request->settings,
|
||||
'creator_id' => auth()->id(),
|
||||
'updater_id' => auth()->id(),
|
||||
]);
|
||||
|
||||
return redirect()->route('admin.basic-settings.payment-configs.index')
|
||||
->with('success', __('Payment Configuration created successfully.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 顯示編輯頁面
|
||||
*/
|
||||
public function edit(PaymentConfig $paymentConfig): View
|
||||
{
|
||||
$companies = \App\Models\System\Company::select('id', 'name')->get();
|
||||
return view('admin.basic-settings.payment-configs.edit', compact('paymentConfig', 'companies'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新金流配置
|
||||
*/
|
||||
public function update(Request $request, PaymentConfig $paymentConfig): RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'settings' => 'required|array',
|
||||
]);
|
||||
|
||||
$paymentConfig->update([
|
||||
'name' => $request->name,
|
||||
'settings' => $request->settings,
|
||||
'updater_id' => auth()->id(),
|
||||
]);
|
||||
|
||||
return redirect()->route('admin.basic-settings.payment-configs.index')
|
||||
->with('success', __('Payment Configuration updated successfully.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 刪除金流配置
|
||||
*/
|
||||
public function destroy(PaymentConfig $paymentConfig): RedirectResponse
|
||||
{
|
||||
$paymentConfig->delete();
|
||||
return redirect()->route('admin.basic-settings.payment-configs.index')
|
||||
->with('success', __('Payment Configuration deleted successfully.'));
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ class CompanyController extends Controller
|
||||
'name' => 'required|string|max:255',
|
||||
'code' => 'required|string|max:50|unique:companies,code,' . $company->id,
|
||||
'tax_id' => 'nullable|string|max:50',
|
||||
'contact_name' => 'required|string|max:255',
|
||||
'contact_name' => 'nullable|string|max:255',
|
||||
'contact_phone' => 'nullable|string|max:50',
|
||||
'contact_email' => 'nullable|email|max:255',
|
||||
'valid_until' => 'nullable|date',
|
||||
|
||||
@@ -14,8 +14,17 @@ class MachineController extends AdminController
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$per_page = $request->input('per_page', 10);
|
||||
$machines = Machine::query()
|
||||
->when($request->status, function ($query, $status) {
|
||||
$query = Machine::query();
|
||||
|
||||
// 搜尋:名稱或序號
|
||||
if ($search = $request->input('search')) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%")
|
||||
->orWhere('serial_no', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
$machines = $query->when($request->status, function ($query, $status) {
|
||||
return $query->where('status', $status);
|
||||
})
|
||||
->latest()
|
||||
|
||||
@@ -11,15 +11,46 @@ class PermissionController extends Controller
|
||||
public function roles()
|
||||
{
|
||||
$per_page = request()->input('per_page', 10);
|
||||
$roles = \Spatie\Permission\Models\Role::with(['permissions', 'users'])->latest()->paginate($per_page)->withQueryString();
|
||||
$all_permissions = \Spatie\Permission\Models\Permission::all()->groupBy(function($perm) {
|
||||
if (str_starts_with($perm->name, 'menu.')) {
|
||||
return 'menu';
|
||||
}
|
||||
return 'other';
|
||||
});
|
||||
$user = auth()->user();
|
||||
$query = \App\Models\System\Role::query()->with(['permissions', 'users']);
|
||||
|
||||
// 租戶隔離:租戶只能看到自己公司的角色 + 系統角色 (company_id is null)
|
||||
if (!$user->isSystemAdmin()) {
|
||||
$query->where(function($q) use ($user) {
|
||||
$q->where('company_id', $user->company_id)
|
||||
->orWhereNull('company_id');
|
||||
});
|
||||
}
|
||||
|
||||
// 搜尋:角色名稱
|
||||
if ($search = request()->input('search')) {
|
||||
$query->where('name', 'like', "%{$search}%");
|
||||
}
|
||||
|
||||
$roles = $query->latest()->paginate($per_page)->withQueryString();
|
||||
$all_permissions = \Spatie\Permission\Models\Permission::all()
|
||||
->filter(function($perm) {
|
||||
// 排除子項目的權限,只顯示主選單權限
|
||||
$excluded = [
|
||||
'menu.basic.machines',
|
||||
'menu.basic.payment-configs',
|
||||
'menu.companies',
|
||||
'menu.accounts',
|
||||
'menu.roles',
|
||||
];
|
||||
return !in_array($perm->name, $excluded);
|
||||
})
|
||||
->groupBy(function($perm) {
|
||||
if (str_starts_with($perm->name, 'menu.')) {
|
||||
return 'menu';
|
||||
}
|
||||
return 'other';
|
||||
});
|
||||
|
||||
return view('admin.permission.roles', compact('roles', 'all_permissions'));
|
||||
// 根據路由決定標題
|
||||
$title = request()->routeIs('*.sub-account-roles') ? __('Sub Account Roles') : __('Role Settings');
|
||||
|
||||
return view('admin.permission.roles', compact('roles', 'all_permissions', 'title'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,14 +64,22 @@ class PermissionController extends Controller
|
||||
'permissions.*' => 'string|exists:permissions,name',
|
||||
]);
|
||||
|
||||
$role = \Spatie\Permission\Models\Role::create([
|
||||
$is_system = auth()->user()->isSystemAdmin() && $request->boolean('is_system');
|
||||
|
||||
$role = \App\Models\System\Role::create([
|
||||
'name' => $validated['name'],
|
||||
'guard_name' => 'web',
|
||||
'is_system' => false,
|
||||
'company_id' => $is_system ? null : auth()->user()->company_id,
|
||||
'is_system' => $is_system,
|
||||
]);
|
||||
|
||||
if (!empty($validated['permissions'])) {
|
||||
$role->syncPermissions($validated['permissions']);
|
||||
$perms = $validated['permissions'];
|
||||
// 如果不是系統角色,排除主選單的系統權限
|
||||
if (!$is_system) {
|
||||
$perms = array_diff($perms, ['menu.basic-settings', 'menu.permissions']);
|
||||
}
|
||||
$role->syncPermissions($perms);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Role created successfully.'));
|
||||
@@ -51,7 +90,7 @@ class PermissionController extends Controller
|
||||
*/
|
||||
public function updateRole(Request $request, $id)
|
||||
{
|
||||
$role = \Spatie\Permission\Models\Role::findOrFail($id);
|
||||
$role = \App\Models\System\Role::findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255|unique:roles,name,' . $id,
|
||||
@@ -59,11 +98,28 @@ class PermissionController extends Controller
|
||||
'permissions.*' => 'string|exists:permissions,name',
|
||||
]);
|
||||
|
||||
if (!$role->is_system) {
|
||||
$role->update(['name' => $validated['name']]);
|
||||
if ($role->name === 'super-admin') {
|
||||
return redirect()->back()->with('error', __('The Super Admin role is immutable.'));
|
||||
}
|
||||
|
||||
$role->syncPermissions($validated['permissions'] ?? []);
|
||||
if (!auth()->user()->isSystemAdmin() && $role->is_system) {
|
||||
return redirect()->back()->with('error', __('System roles cannot be modified by tenant administrators.'));
|
||||
}
|
||||
|
||||
$is_system = auth()->user()->isSystemAdmin() ? $request->boolean('is_system') : $role->is_system;
|
||||
|
||||
$role->update([
|
||||
'name' => $validated['name'],
|
||||
'is_system' => $is_system,
|
||||
'company_id' => $is_system ? null : $role->company_id,
|
||||
]);
|
||||
|
||||
$perms = $validated['permissions'] ?? [];
|
||||
// 如果不是系統角色,排除主選單的系統權限
|
||||
if (!$is_system) {
|
||||
$perms = array_diff($perms, ['menu.basic-settings', 'menu.permissions']);
|
||||
}
|
||||
$role->syncPermissions($perms);
|
||||
|
||||
return redirect()->back()->with('success', __('Role updated successfully.'));
|
||||
}
|
||||
@@ -73,10 +129,14 @@ class PermissionController extends Controller
|
||||
*/
|
||||
public function destroyRole($id)
|
||||
{
|
||||
$role = \Spatie\Permission\Models\Role::findOrFail($id);
|
||||
$role = \App\Models\System\Role::findOrFail($id);
|
||||
|
||||
if ($role->is_system) {
|
||||
return redirect()->back()->with('error', __('System roles cannot be deleted.'));
|
||||
if ($role->name === 'super-admin') {
|
||||
return redirect()->back()->with('error', __('The Super Admin role cannot be deleted.'));
|
||||
}
|
||||
|
||||
if (!auth()->user()->isSystemAdmin() && $role->is_system) {
|
||||
return redirect()->back()->with('error', __('System roles cannot be deleted by tenant administrators.'));
|
||||
}
|
||||
|
||||
if ($role->users()->count() > 0) {
|
||||
@@ -115,9 +175,19 @@ class PermissionController extends Controller
|
||||
$per_page = $request->input('per_page', 10);
|
||||
$users = $query->latest()->paginate($per_page)->withQueryString();
|
||||
$companies = auth()->user()->isSystemAdmin() ? \App\Models\System\Company::all() : collect();
|
||||
$roles = \Spatie\Permission\Models\Role::all();
|
||||
$roles_query = \App\Models\System\Role::where('name', '!=', 'super-admin');
|
||||
if (!auth()->user()->isSystemAdmin()) {
|
||||
$roles_query->where(function($q) {
|
||||
$q->where('company_id', auth()->user()->company_id)
|
||||
->orWhereNull('company_id');
|
||||
});
|
||||
}
|
||||
$roles = $roles_query->get();
|
||||
|
||||
return view('admin.data-config.accounts', compact('users', 'companies', 'roles'));
|
||||
// 根據路由決定標題
|
||||
$title = request()->routeIs('*.sub-accounts') ? __('Sub Account Management') : __('Account Management');
|
||||
|
||||
return view('admin.data-config.accounts', compact('users', 'companies', 'roles', 'title'));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,6 +228,10 @@ class PermissionController extends Controller
|
||||
{
|
||||
$user = \App\Models\System\User::findOrFail($id);
|
||||
|
||||
if ($user->hasRole('super-admin')) {
|
||||
return redirect()->back()->with('error', __('System super admin accounts cannot be modified via this interface.'));
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'username' => 'required|string|max:255|unique:users,username,' . $id,
|
||||
@@ -178,7 +252,13 @@ class PermissionController extends Controller
|
||||
];
|
||||
|
||||
if (auth()->user()->isSystemAdmin()) {
|
||||
$updateData['company_id'] = $validated['company_id'];
|
||||
// 防止超級管理員不小心把自己綁定到租客公司或降級
|
||||
if ($user->id === auth()->id()) {
|
||||
$updateData['company_id'] = null;
|
||||
$validated['role'] = 'super-admin';
|
||||
} else {
|
||||
$updateData['company_id'] = $validated['company_id'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($validated['password'])) {
|
||||
@@ -187,7 +267,12 @@ class PermissionController extends Controller
|
||||
|
||||
$user->update($updateData);
|
||||
|
||||
$user->syncRoles([$validated['role']]);
|
||||
// 如果是編輯自己且原本是超級管理員,強制保留 super-admin 角色
|
||||
if ($user->id === auth()->id() && auth()->user()->isSystemAdmin()) {
|
||||
$user->syncRoles(['super-admin']);
|
||||
} else {
|
||||
$user->syncRoles([$validated['role']]);
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', __('Account updated successfully.'));
|
||||
}
|
||||
@@ -199,6 +284,10 @@ class PermissionController extends Controller
|
||||
{
|
||||
$user = \App\Models\System\User::findOrFail($id);
|
||||
|
||||
if ($user->hasRole('super-admin')) {
|
||||
return redirect()->back()->with('error', __('System super admin accounts cannot be deleted.'));
|
||||
}
|
||||
|
||||
if ($user->id === auth()->id()) {
|
||||
return redirect()->back()->with('error', __('You cannot delete your own account.'));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Models\UserLoginLog;
|
||||
use App\Models\System\UserLoginLog;
|
||||
use Illuminate\Auth\Events\Login;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
||||
@@ -25,15 +25,78 @@ class Machine extends Model
|
||||
'firmware_version',
|
||||
'api_token',
|
||||
'last_heartbeat_at',
|
||||
'card_reader_seconds',
|
||||
'card_reader_checkout_time_1',
|
||||
'card_reader_checkout_time_2',
|
||||
'heating_start_time',
|
||||
'heating_end_time',
|
||||
'payment_buffer_seconds',
|
||||
'card_reader_no',
|
||||
'key_no',
|
||||
'invoice_status',
|
||||
'welcome_gift_enabled',
|
||||
'is_spring_slot_1_10',
|
||||
'is_spring_slot_11_20',
|
||||
'is_spring_slot_21_30',
|
||||
'is_spring_slot_31_40',
|
||||
'is_spring_slot_41_50',
|
||||
'is_spring_slot_51_60',
|
||||
'member_system_enabled',
|
||||
'payment_config_id',
|
||||
'machine_model_id',
|
||||
'images',
|
||||
'creator_id',
|
||||
'updater_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'last_heartbeat_at' => 'datetime',
|
||||
'welcome_gift_enabled' => 'boolean',
|
||||
'is_spring_slot_1_10' => 'boolean',
|
||||
'is_spring_slot_11_20' => 'boolean',
|
||||
'is_spring_slot_21_30' => 'boolean',
|
||||
'is_spring_slot_31_40' => 'boolean',
|
||||
'is_spring_slot_41_50' => 'boolean',
|
||||
'is_spring_slot_51_60' => 'boolean',
|
||||
'member_system_enabled' => 'boolean',
|
||||
'images' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get machine images absolute URLs
|
||||
*/
|
||||
public function getImageUrlsAttribute(): array
|
||||
{
|
||||
if (empty($this->images)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(fn($path) => \Illuminate\Support\Facades\Storage::disk('public')->url($path), $this->images);
|
||||
}
|
||||
|
||||
public function logs()
|
||||
{
|
||||
return $this->hasMany(MachineLog::class);
|
||||
}
|
||||
|
||||
public function machineModel()
|
||||
{
|
||||
return $this->belongsTo(MachineModel::class);
|
||||
}
|
||||
|
||||
public function paymentConfig()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\System\PaymentConfig::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\System\User::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function updater()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\System\User::class, 'updater_id');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
35
app/Models/Machine/MachineModel.php
Normal file
35
app/Models/Machine/MachineModel.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Machine;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class MachineModel extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'company_id',
|
||||
'creator_id',
|
||||
'updater_id',
|
||||
];
|
||||
|
||||
public function machines()
|
||||
{
|
||||
return $this->hasMany(Machine::class);
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\System\Company::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\System\User::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function updater()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\System\User::class, 'updater_id');
|
||||
}
|
||||
}
|
||||
40
app/Models/System/PaymentConfig.php
Normal file
40
app/Models/System/PaymentConfig.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\System;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class PaymentConfig extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'company_id',
|
||||
'name',
|
||||
'settings',
|
||||
'creator_id',
|
||||
'updater_id',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'settings' => 'array',
|
||||
];
|
||||
|
||||
public function machines()
|
||||
{
|
||||
return $this->hasMany(\App\Models\Machine\Machine::class);
|
||||
}
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(\App\Models\System\Company::class);
|
||||
}
|
||||
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'creator_id');
|
||||
}
|
||||
|
||||
public function updater()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'updater_id');
|
||||
}
|
||||
}
|
||||
34
app/Models/System/Role.php
Normal file
34
app/Models/System/Role.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\System;
|
||||
|
||||
use Spatie\Permission\Models\Role as SpatieRole;
|
||||
|
||||
class Role extends SpatieRole
|
||||
{
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'guard_name',
|
||||
'company_id',
|
||||
'is_system',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the company that owns the role.
|
||||
*/
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a query to only include roles for a specific company or system roles.
|
||||
*/
|
||||
public function scopeForCompany($query, $company_id)
|
||||
{
|
||||
return $query->where(function($q) use ($company_id) {
|
||||
$q->where('company_id', $company_id)
|
||||
->orWhereNull('company_id');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ class User extends Authenticatable
|
||||
*/
|
||||
public function loginLogs()
|
||||
{
|
||||
return $this->hasMany(\App\Models\UserLoginLog::class);
|
||||
return $this->hasMany(UserLoginLog::class);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
namespace App\Models\System;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory; // Added this line
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
class UserLoginLog extends Model
|
||||
{
|
||||
@@ -24,7 +24,7 @@ return [
|
||||
* `Spatie\Permission\Contracts\Role` contract.
|
||||
*/
|
||||
|
||||
'role' => Spatie\Permission\Models\Role::class,
|
||||
'role' => \App\Models\System\Role::class,
|
||||
|
||||
],
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('machine_models', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->comment('型號名稱');
|
||||
$table->foreignId('company_id')->nullable()->constrained()->onDelete('cascade')->comment('關聯公司');
|
||||
$table->foreignId('creator_id')->nullable()->constrained('users')->nullOnDelete()->comment('建立者');
|
||||
$table->foreignId('updater_id')->nullable()->constrained('users')->nullOnDelete()->comment('修改者');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('machine_models');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('payment_configs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignId('company_id')->constrained()->onDelete('cascade')->comment('關聯公司');
|
||||
$table->string('name')->comment('組合名稱');
|
||||
$table->json('settings')->nullable()->comment('金流參數 (JSON)');
|
||||
$table->foreignId('creator_id')->nullable()->constrained('users')->nullOnDelete()->comment('建立者');
|
||||
$table->foreignId('updater_id')->nullable()->constrained('users')->nullOnDelete()->comment('修改者');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('payment_configs');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('machines', function (Blueprint $table) {
|
||||
$table->integer('card_reader_seconds')->nullable()->default(30)->comment('刷卡機秒數');
|
||||
$table->time('card_reader_checkout_time_1')->nullable()->default('22:30:00')->comment('卡機結帳時間1');
|
||||
$table->time('card_reader_checkout_time_2')->nullable()->default('23:45:00')->comment('卡機結帳時間2');
|
||||
$table->time('heating_start_time')->default('00:00:00')->comment('開啟-加熱時間');
|
||||
$table->time('heating_end_time')->default('00:00:00')->comment('關閉-加熱時間');
|
||||
$table->integer('payment_buffer_seconds')->nullable()->default(5)->comment('金流緩衝時間(s)');
|
||||
$table->string('card_reader_no')->nullable()->comment('刷卡機編號');
|
||||
$table->string('key_no')->nullable()->comment('鑰匙編號');
|
||||
$table->tinyInteger('invoice_status')->default(0)->comment('發票狀態碼: 0=不開發票, 1=預設捐, 2=預設不捐');
|
||||
$table->boolean('welcome_gift_enabled')->default(0)->comment('來店禮開關');
|
||||
$table->boolean('is_spring_slot_1_10')->default(0)->comment('貨道類型(1~10): 0=履帶, 1=彈簧');
|
||||
$table->boolean('is_spring_slot_11_20')->default(0)->comment('貨道類型(11~20): 0=履帶, 1=彈簧');
|
||||
$table->boolean('is_spring_slot_21_30')->default(0)->comment('貨道類型(21~30): 0=履帶, 1=彈簧');
|
||||
$table->boolean('is_spring_slot_31_40')->default(0)->comment('貨道類型(31~40): 0=履帶, 1=彈簧');
|
||||
$table->boolean('is_spring_slot_41_50')->default(0)->comment('貨道類型(41~50): 0=履帶, 1=彈簧');
|
||||
$table->boolean('is_spring_slot_51_60')->default(0)->comment('貨道類型(51~60): 0=履帶, 1=彈簧');
|
||||
$table->boolean('member_system_enabled')->default(0)->comment('會員系統開關');
|
||||
|
||||
$table->foreignId('payment_config_id')->nullable()->constrained('payment_configs')->nullOnDelete()->comment('關聯金流參數組合');
|
||||
$table->foreignId('machine_model_id')->nullable()->constrained('machine_models')->nullOnDelete()->comment('關類型號組合');
|
||||
$table->foreignId('creator_id')->nullable()->constrained('users')->nullOnDelete()->comment('建立者');
|
||||
$table->foreignId('updater_id')->nullable()->constrained('users')->nullOnDelete()->comment('修改者');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('machines', function (Blueprint $table) {
|
||||
$table->dropForeign(['payment_config_id']);
|
||||
$table->dropForeign(['machine_model_id']);
|
||||
$table->dropForeign(['creator_id']);
|
||||
$table->dropForeign(['updater_id']);
|
||||
|
||||
$table->dropColumn([
|
||||
'card_reader_seconds',
|
||||
'card_reader_checkout_time_1',
|
||||
'card_reader_checkout_time_2',
|
||||
'heating_start_time',
|
||||
'heating_end_time',
|
||||
'payment_buffer_seconds',
|
||||
'card_reader_no',
|
||||
'key_no',
|
||||
'invoice_status',
|
||||
'welcome_gift_enabled',
|
||||
'is_spring_slot_1_10',
|
||||
'is_spring_slot_11_20',
|
||||
'is_spring_slot_21_30',
|
||||
'is_spring_slot_31_40',
|
||||
'is_spring_slot_41_50',
|
||||
'is_spring_slot_51_60',
|
||||
'member_system_enabled',
|
||||
'payment_config_id',
|
||||
'machine_model_id',
|
||||
'creator_id',
|
||||
'updater_id',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('machines', function (Blueprint $table) {
|
||||
$table->json('images')->after('firmware_version')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('machines', function (Blueprint $table) {
|
||||
$table->dropColumn('images');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -31,9 +31,8 @@ class RoleSeeder extends Seeder
|
||||
'menu.line',
|
||||
'menu.reservation',
|
||||
'menu.special-permission',
|
||||
'menu.companies',
|
||||
'menu.accounts',
|
||||
'menu.roles',
|
||||
'menu.basic-settings',
|
||||
'menu.permissions',
|
||||
];
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
@@ -47,9 +46,23 @@ class RoleSeeder extends Seeder
|
||||
);
|
||||
$superAdmin->syncPermissions(Permission::all());
|
||||
|
||||
Role::updateOrCreate(
|
||||
$tenantAdmin = Role::updateOrCreate(
|
||||
['name' => 'tenant-admin'],
|
||||
['is_system' => true]
|
||||
['is_system' => false]
|
||||
);
|
||||
$tenantAdmin->syncPermissions([
|
||||
'menu.members',
|
||||
'menu.machines',
|
||||
'menu.app',
|
||||
'menu.warehouses',
|
||||
'menu.sales',
|
||||
'menu.analysis',
|
||||
'menu.audit',
|
||||
'menu.data-config',
|
||||
'menu.remote',
|
||||
'menu.line',
|
||||
'menu.reservation',
|
||||
'menu.special-permission',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,18 +187,59 @@ Spatie 預設的 roles 表必須加上多租戶設計,確保留戶只能管理
|
||||
|
||||
---
|
||||
|
||||
#### 🟡 `machines` 表 — 需擴充欄位
|
||||
### 機台設定與參數模型 (Configuration & Parameters)
|
||||
|
||||
現有 `machines` 表僅有基礎欄位,需為 B010 API 補充:
|
||||
為了提升機台的可維護性與金流設定的靈活性,系統引入了以下結構:
|
||||
|
||||
| 新增欄位 | 類型 | 說明 | 來源 API |
|
||||
|----------|------|------|----------|
|
||||
| `serial_no` | `VARCHAR UNIQUE` | 機台序號(API 用此識別) | B010 `machine` |
|
||||
| `model` | `VARCHAR` | 機台型號 | B010 `M_Stus` |
|
||||
| `current_page` | `TINYINT` | 當前頁面狀態碼 | B010 `M_Stus2` |
|
||||
| `door_status` | `VARCHAR` | 門禁狀態 | B010 `door` |
|
||||
| `is_online` | `BOOLEAN` | 是否在線(心跳超時判斷) | 計算欄位 |
|
||||
| `api_token` | `VARCHAR` | 機台專屬 API Token | 取代硬編碼 key |
|
||||
#### 1. 機台型號設定表 (machine_models)
|
||||
記錄系統支援的機台型號,供機台綁定基礎參數。
|
||||
|
||||
| 欄位 | 類型 | 說明 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | — |
|
||||
| `name` | VARCHAR(255) | 型號名稱 (例如: "B010-STD") |
|
||||
| `company_id` | BIGINT FK | 所屬公司 (支援多租戶隔離) |
|
||||
| `creator_id / updater_id` | BIGINT FK | 審計記錄 (建立者/修改者) |
|
||||
| `timestamps` | — | — |
|
||||
|
||||
---
|
||||
|
||||
#### 2. 金流參數組合表 (payment_configs)
|
||||
**範本化設計**:由公司建立支付參數組合範本,機台端選擇套用。
|
||||
|
||||
| 欄位 | 類型 | 說明 |
|
||||
|------|------|------|
|
||||
| `id` | BIGINT PK | — |
|
||||
| `company_id` | BIGINT FK | 所屬公司 |
|
||||
| `name` | VARCHAR(255) | 組合名稱 (例如: "玉山+綠界組合A") |
|
||||
| `settings` | **JSON** | 儲存各支付平台 API Key (Ecpay, Esun, Tappay, LinePay 等) |
|
||||
| `creator_id / updater_id` | BIGINT FK | 審計記錄 |
|
||||
| `timestamps` | — | — |
|
||||
|
||||
---
|
||||
|
||||
#### 3. 機台主表擴充 (machines)
|
||||
針對 `machines` 表擴充以下欄位以支援 IoT 通訊與進階營運設定:
|
||||
|
||||
| 類別 | 欄位 | 類型 | 說明 |
|
||||
|------|------|------|------|
|
||||
| **基礎識別** | `serial_no` | `VARCHAR` | 機台序號 (API 唯一識別碼) |
|
||||
| **狀態監控** | `current_page` | `VARCHAR` | 機台當前畫面停留頁面 |
|
||||
| | `door_status` | `VARCHAR` | 門禁狀態 (open/closed) |
|
||||
| | `temperature` | `DECIMAL` | 機器溫度 (來自硬體回傳) |
|
||||
| **金流設定** | `payment_config_id` | `BIGINT FK` | 關聯金流參數樣本 (`payment_configs`) |
|
||||
| | `card_reader_seconds` | `INT` | 刷卡機秒數 (預設 30) |
|
||||
| | `card_reader_checkout_time_1 / 2` | `TIME` | 卡機結帳清機時間 |
|
||||
| | `payment_buffer_seconds` | `INT` | 金流回傳緩衝時間 (預設 5) |
|
||||
| **硬體與營運**| `machine_model_id` | `BIGINT FK` | 關聯機台型號 (`machine_models`) |
|
||||
| | `heating_start / end_time` | `TIME` | 加熱自動排程開啟與關閉時間 |
|
||||
| | `card_reader_no / key_no` | `VARCHAR` | 刷卡機編號與鑰匙編號 |
|
||||
| | `invoice_status` | `TINYINT` | 發票狀態 (0:不開, 1:預設捐, 2:預設不捐) |
|
||||
| | `welcome_gift_enabled` | `BOOLEAN` | 來店禮開關 |
|
||||
| | `member_system_enabled` | `BOOLEAN` | 會員系統開關 |
|
||||
| | `is_spring_slot_1_10` ~ `60` | `BOOLEAN` | 貨道類型標記 (0=履帶, 1=彈簧) |
|
||||
| **審計權限** | `company_id` | `BIGINT FK` | 關聯所屬公司 (租戶隔離) |
|
||||
| | `creator_id / updater_id` | `BIGINT FK` | 建立者與最後修改者 ID |
|
||||
|
||||
---
|
||||
|
||||
|
||||
18
lang/en.json
18
lang/en.json
@@ -140,13 +140,15 @@
|
||||
"Clear Stock": "Clear Stock",
|
||||
"APK Versions": "APK Versions",
|
||||
"Discord Notifications": "Discord Notifications",
|
||||
"Basic Settings": "Basic Settings",
|
||||
"Machine Settings": "Machine Settings",
|
||||
"Permission Settings": "Permission Settings",
|
||||
"APP Features": "APP Features",
|
||||
"Sales": "Sales",
|
||||
"Others": "Others",
|
||||
"AI Prediction": "AI Prediction",
|
||||
"Roles": "Roles",
|
||||
"Role Management": "Role Management",
|
||||
"Roles": "Role Permissions",
|
||||
"Role Management": "Role Permission Management",
|
||||
"Define and manage security roles and permissions.": "Define and manage security roles and permissions.",
|
||||
"Search roles...": "Search roles...",
|
||||
"No permissions": "No permissions",
|
||||
@@ -162,6 +164,11 @@
|
||||
"Permissions": "Permissions",
|
||||
"Users": "Users",
|
||||
"System role name cannot be modified.": "System role name cannot be modified.",
|
||||
"The Super Admin role name cannot be modified.": "The Super Admin role name cannot be modified.",
|
||||
"System Level": "System Level",
|
||||
"Company Level": "Company Level",
|
||||
"Global roles accessible by all administrators.": "Global roles accessible by all administrators.",
|
||||
"Roles scoped to specific customer companies.": "Roles scoped to specific customer companies.",
|
||||
"members": "Member Management",
|
||||
"machines": "Machine Management",
|
||||
"app": "APP Management",
|
||||
@@ -176,8 +183,9 @@
|
||||
"special-permission": "Special Permission",
|
||||
"companies": "Customer Management",
|
||||
"accounts": "Account Management",
|
||||
"roles": "Role Settings",
|
||||
"Role Settings": "Role Settings",
|
||||
"roles": "Role Permissions",
|
||||
"Role Permissions": "Role Permissions",
|
||||
"Role Settings": "Role Permissions",
|
||||
"No login history yet": "No login history yet",
|
||||
"Signed in as": "Signed in as",
|
||||
"Logout": "Logout",
|
||||
@@ -268,5 +276,7 @@
|
||||
"Unknown": "Unknown",
|
||||
"Info": "Info",
|
||||
"Warning": "Warning",
|
||||
"basic-settings": "Basic Settings",
|
||||
"permissions": "Permission Settings",
|
||||
"Error": "Error"
|
||||
}
|
||||
15
lang/ja.json
15
lang/ja.json
@@ -140,13 +140,15 @@
|
||||
"Clear Stock": "在庫クリア",
|
||||
"APK Versions": "APKバージョン",
|
||||
"Discord Notifications": "Discord通知",
|
||||
"Basic Settings": "基本設定",
|
||||
"Machine Settings": "機台設定",
|
||||
"Permission Settings": "権限設定",
|
||||
"APP Features": "APP機能",
|
||||
"Sales": "販売",
|
||||
"Others": "その他",
|
||||
"AI Prediction": "AI予測",
|
||||
"Roles": "ロール",
|
||||
"Role Management": "ロール管理",
|
||||
"Roles": "ロール権限",
|
||||
"Role Management": "ロール権限管理",
|
||||
"Define and manage security roles and permissions.": "システムのセキュリティロールと権限を定義および管理します。",
|
||||
"Search roles...": "ロールを検索...",
|
||||
"No permissions": "権限項目なし",
|
||||
@@ -162,6 +164,8 @@
|
||||
"Permissions": "権限",
|
||||
"Users": "ユーザー数",
|
||||
"System role name cannot be modified.": "システムロール名は変更できません。",
|
||||
"System Level": "システムレベル",
|
||||
"Company Level": "顧客レベル",
|
||||
"Menu Permissions": "メニュー権限",
|
||||
"Save Changes": "変更を保存",
|
||||
"members": "会員管理",
|
||||
@@ -178,8 +182,9 @@
|
||||
"special-permission": "特別権限",
|
||||
"companies": "顧客管理",
|
||||
"accounts": "アカウント管理",
|
||||
"roles": "ロール設定",
|
||||
"Role Settings": "ロール設定",
|
||||
"roles": "ロール権限",
|
||||
"Role Permissions": "ロール権限",
|
||||
"Role Settings": "ロール權限",
|
||||
"No login history yet": "ログイン履歴はまだありません",
|
||||
"Signed in as": "ログイン中",
|
||||
"Logout": "ログアウト",
|
||||
@@ -269,5 +274,7 @@
|
||||
"Unknown": "不明",
|
||||
"Info": "情報",
|
||||
"Warning": "警告",
|
||||
"basic-settings": "基本設定",
|
||||
"permissions": "權限設定",
|
||||
"Error": "エラー"
|
||||
}
|
||||
@@ -140,13 +140,15 @@
|
||||
"Clear Stock": "庫存清空",
|
||||
"APK Versions": "APK版本",
|
||||
"Discord Notifications": "Discord通知",
|
||||
"Basic Settings": "基本設定",
|
||||
"Machine Settings": "機台設定",
|
||||
"Permission Settings": "權限設定",
|
||||
"APP Features": "APP功能",
|
||||
"Sales": "銷售管理",
|
||||
"Others": "其他功能",
|
||||
"AI Prediction": "AI智能預測",
|
||||
"Roles": "角色設定",
|
||||
"Role Management": "角色管理",
|
||||
"Roles": "角色權限",
|
||||
"Role Management": "角色權限管理",
|
||||
"Define and manage security roles and permissions.": "定義並管理系統安全角色與權限。",
|
||||
"Search roles...": "搜尋角色...",
|
||||
"No permissions": "無權限項目",
|
||||
@@ -162,6 +164,11 @@
|
||||
"Permissions": "權限",
|
||||
"Users": "帳號數",
|
||||
"System role name cannot be modified.": "內建系統角色的名稱無法修改。",
|
||||
"The Super Admin role name cannot be modified.": "超級管理員角色的名稱無法修改。",
|
||||
"System Level": "系統層級",
|
||||
"Company Level": "客戶層級",
|
||||
"Global roles accessible by all administrators.": "適用於所有管理者的全域角色。",
|
||||
"Roles scoped to specific customer companies.": "適用於各個客戶單位的特定角色。",
|
||||
"members": "會員管理",
|
||||
"machines": "機台管理",
|
||||
"app": "APP 管理",
|
||||
@@ -176,8 +183,9 @@
|
||||
"special-permission": "特殊權限",
|
||||
"companies": "客戶管理",
|
||||
"accounts": "帳號管理",
|
||||
"roles": "角色設定",
|
||||
"Role Settings": "角色設定",
|
||||
"roles": "角色權限",
|
||||
"Role Permissions": "角色權限",
|
||||
"Role Settings": "角色權限",
|
||||
"No login history yet": "尚無登入紀錄",
|
||||
"Signed in as": "登入身份",
|
||||
"Logout": "登出",
|
||||
@@ -273,5 +281,78 @@
|
||||
"Unknown": "未知",
|
||||
"Info": "一般",
|
||||
"Warning": "警告",
|
||||
"Error": "錯誤"
|
||||
"Error": "錯誤",
|
||||
"Management of operational parameters": "機台運作參數管理",
|
||||
"Add Machine": "新增機台",
|
||||
"Search machines...": "搜尋機台...",
|
||||
"Items": "項",
|
||||
"Machine Name": "機台名稱",
|
||||
"Serial No": "機台序號",
|
||||
"Owner": "所屬客戶",
|
||||
"Model": "機台型號",
|
||||
"Action": "操作",
|
||||
"No location set": "尚未設定位置",
|
||||
"Edit Settings": "編輯設定",
|
||||
"Enter machine name": "請輸入機台名稱",
|
||||
"Enter serial number": "請輸入機台序號",
|
||||
"Select Owner": "請選擇所屬客戶",
|
||||
"Select Model": "請選擇機台型號",
|
||||
"Customer Payment Config": "客戶金流設定",
|
||||
"Not Used": "不使用",
|
||||
"Edit Machine Settings": "編輯機台設定",
|
||||
"Operational Parameters": "運作參數",
|
||||
"Card Reader Seconds": "刷卡機秒數",
|
||||
"Payment Buffer Seconds": "金流緩衝時間(s)",
|
||||
"Checkout Time 1": "卡機結帳時間1",
|
||||
"Checkout Time 2": "卡機結帳時間2",
|
||||
"Heating Start Time": "開啟-加熱時間",
|
||||
"Heating End Time": "關閉-加熱時間",
|
||||
"Hardware & Slots": "硬體與貨道設定",
|
||||
"Card Reader No": "刷卡機編號",
|
||||
"Key No": "鑰匙編號",
|
||||
"Slot Mechanism (default: Conveyor, check for Spring)": "貨道類型 (預設履帶,勾選為彈簧)",
|
||||
"Payment & Invoice": "金流與發票",
|
||||
"Invoice Status": "發票狀態碼",
|
||||
"No Invoice": "不開發票",
|
||||
"Default Donate": "開發票預設捐",
|
||||
"Default Not Donate": "開發票預設不捐",
|
||||
"Member & External": "會員與外部系統",
|
||||
"Welcome Gift": "來店禮開關",
|
||||
"Enabled/Disabled": "啟用/禁用",
|
||||
"Member System": "會員系統",
|
||||
"Payment Configuration": "客戶金流設定",
|
||||
"Merchant payment gateway settings management": "特約商店支付網關參數管理",
|
||||
"Create Config": "建立配置",
|
||||
"Config Name": "配置名稱",
|
||||
"Last Updated": "最後更新日期",
|
||||
"Are you sure you want to delete this configuration?": "您確定要刪除此金流配置嗎?",
|
||||
"Create Payment Config": "建立金流配置",
|
||||
"Define new third-party payment parameters": "定義新的第三方支付介接參數",
|
||||
"Save Config": "儲存配置",
|
||||
"Configuration Name": "金流組合名稱",
|
||||
"Belongs To Company": "所屬客戶公司",
|
||||
"ECPay Invoice": "綠界發票",
|
||||
"Store ID": "特約商店代號 (MerchantID)",
|
||||
"HashKey": "HashKey",
|
||||
"HashIV": "HashIV",
|
||||
"E.SUN QR Scan": "玉山掃碼",
|
||||
"StoreID": "商店代號 (StoreID)",
|
||||
"TermID": "終端代號 (TermID)",
|
||||
"Key": "金鑰 (Key)",
|
||||
"LINE Pay Direct": "Line官方支付",
|
||||
"ChannelId": "ChannelId",
|
||||
"ChannelSecret": "ChannelSecret",
|
||||
"TapPay Integration": "TapPay 整合支付",
|
||||
"PARTNER_KEY": "PARTNER_KEY",
|
||||
"APP_ID": "APP_ID",
|
||||
"APP_KEY": "APP_KEY",
|
||||
"Merchant IDs": "特約商店代號 (Merchant IDs)",
|
||||
"LINE_MERCHANT_ID": "LINE Pay 商店代號",
|
||||
"JKO_MERCHANT_ID": "街口支付 商店代號",
|
||||
"PI_MERCHANT_ID": "Pi 拍錢包 商店代號",
|
||||
"PS_MERCHANT_ID": "全盈+Pay 商店代號",
|
||||
"EASY_MERCHANT_ID": "悠遊付 商店代號",
|
||||
"basic-settings": "基本設定",
|
||||
"permissions": "權限設定",
|
||||
"Edit Payment Config": "編輯金流配置"
|
||||
}
|
||||
@@ -99,9 +99,9 @@
|
||||
|
||||
@layer components {
|
||||
.luxury-nav-item {
|
||||
@apply flex items-center gap-x-3.5 py-2.5 px-4 text-sm font-medium rounded-xl transition-all duration-200;
|
||||
@apply text-slate-500 hover:text-slate-900 hover:bg-slate-100;
|
||||
@apply dark:text-slate-400 dark:hover:text-white dark:hover:bg-white/5;
|
||||
@apply flex items-center gap-x-3.5 py-2.5 px-4 text-sm font-semibold rounded-xl transition-all duration-200;
|
||||
@apply text-slate-600 hover:text-slate-900 hover:bg-slate-100;
|
||||
@apply dark:text-slate-300 dark:hover:text-white dark:hover:bg-white/5;
|
||||
}
|
||||
|
||||
.luxury-nav-item.active {
|
||||
@@ -184,13 +184,17 @@
|
||||
@apply dark:ring-cyan-500/20 dark:border-cyan-400/50;
|
||||
}
|
||||
|
||||
/* Date Input Calendar Icon Optimization */
|
||||
.luxury-input[type="date"]::-webkit-calendar-picker-indicator {
|
||||
/* Input Icon Optimization (Date/Time) */
|
||||
.luxury-input[type="date"]::-webkit-calendar-picker-indicator,
|
||||
.luxury-input[type="time"]::-webkit-calendar-picker-indicator,
|
||||
.luxury-input[type="datetime-local"]::-webkit-calendar-picker-indicator {
|
||||
@apply cursor-pointer transition-opacity hover:opacity-100;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.dark .luxury-input[type="date"]::-webkit-calendar-picker-indicator {
|
||||
.dark .luxury-input[type="date"]::-webkit-calendar-picker-indicator,
|
||||
.dark .luxury-input[type="time"]::-webkit-calendar-picker-indicator,
|
||||
.dark .luxury-input[type="datetime-local"]::-webkit-calendar-picker-indicator {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
|
||||
251
resources/views/admin/basic-settings/machines/edit.blade.php
Normal file
251
resources/views/admin/basic-settings/machines/edit.blade.php
Normal file
@@ -0,0 +1,251 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
<div class="space-y-10 pb-20">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="{{ route('admin.basic-settings.machines.index') }}" class="p-2.5 rounded-xl bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 text-slate-500 hover:text-cyan-500 transition-all shadow-sm">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18"/></svg>
|
||||
</a>
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Edit Machine Settings') }}</h1>
|
||||
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ $machine->name }} / {{ $machine->serial_no }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button type="submit" form="edit-form" class="btn-luxury-primary px-8 flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"/></svg>
|
||||
<span>{{ __('Save Changes') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="edit-form" action="{{ route('admin.basic-settings.machines.update', $machine) }}" method="POST" enctype="multipart/form-data" class="space-y-8">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<!-- Validation Errors -->
|
||||
@if ($errors->any())
|
||||
<div class="luxury-card p-6 bg-rose-50/50 dark:bg-rose-500/5 border-rose-100 dark:border-rose-500/20 mb-8 animate-luxury-in">
|
||||
<div class="flex items-center gap-3 mb-4 text-rose-600 dark:text-rose-400">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"/></svg>
|
||||
<h4 class="font-black text-sm tracking-tight italic">{{ __('Some fields need attention') }}</h4>
|
||||
</div>
|
||||
<ul class="space-y-1">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li class="text-[11px] font-bold text-rose-500/80">{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
||||
<!-- Left: Basic info & Hardware -->
|
||||
<div class="lg:col-span-2 space-y-8">
|
||||
<!-- Basic Information -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||
<div class="flex items-center gap-3 mb-8">
|
||||
<div class="w-10 h-10 rounded-xl bg-emerald-500/10 flex items-center justify-center text-emerald-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 18.75 4.5H5.25a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z"/></svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('Basic Information') }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Machine Name') }}</label>
|
||||
<input type="text" name="name" value="{{ old('name', $machine->name) }}" class="luxury-input w-full" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Serial Number') }}</label>
|
||||
<input type="text" value="{{ $machine->serial_no }}" class="luxury-input w-full bg-slate-50/50 dark:bg-slate-900/50 text-slate-400 cursor-not-allowed" readonly>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Machine Model') }}</label>
|
||||
<select name="machine_model_id" class="luxury-select w-full" required>
|
||||
@foreach($models as $model)
|
||||
<option value="{{ $model->id }}" {{ old('machine_model_id', $machine->machine_model_id) == $model->id ? 'selected' : '' }}>{{ $model->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Operational Parameters -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 50ms">
|
||||
<div class="flex items-center gap-3 mb-8">
|
||||
<div class="w-10 h-10 rounded-xl bg-cyan-500/10 flex items-center justify-center text-cyan-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 12h7.5"/></svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('Operational Parameters') }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Card Reader Seconds') }}</label>
|
||||
<input type="number" name="card_reader_seconds" value="{{ $machine->card_reader_seconds }}" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Payment Buffer Seconds') }}</label>
|
||||
<input type="number" name="payment_buffer_seconds" value="{{ $machine->payment_buffer_seconds }}" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Checkout Time 1') }}</label>
|
||||
<x-luxury-time-input name="card_reader_checkout_time_1" value="{{ $machine->card_reader_checkout_time_1 }}" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Checkout Time 2') }}</label>
|
||||
<x-luxury-time-input name="card_reader_checkout_time_2" value="{{ $machine->card_reader_checkout_time_2 }}" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Heating Start Time') }}</label>
|
||||
<x-luxury-time-input name="heating_start_time" value="{{ $machine->heating_start_time }}" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Heating End Time') }}</label>
|
||||
<x-luxury-time-input name="heating_end_time" value="{{ $machine->heating_end_time }}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- hardware & slot types -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 150ms">
|
||||
<div class="flex items-center gap-3 mb-8">
|
||||
<div class="w-10 h-10 rounded-xl bg-indigo-500/10 flex items-center justify-center text-indigo-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M21 7.5V18a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 18V7.5m0-4.5h18M3 7.5h18M3 12h18M3 16.5h18"/></svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('Hardware & Slots') }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="space-y-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Card Reader No') }}</label>
|
||||
<input type="text" name="card_reader_no" value="{{ $machine->card_reader_no }}" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Key No') }}</label>
|
||||
<input type="text" name="key_no" value="{{ $machine->key_no }}" class="luxury-input w-full">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-4">{{ __('Slot Mechanism (default: Conveyor, check for Spring)') }}</label>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-4">
|
||||
@foreach([['1-10', 'is_spring_slot_1_10'], ['11-20', 'is_spring_slot_11_20'], ['21-30', 'is_spring_slot_21_30'], ['31-40', 'is_spring_slot_31_40'], ['41-50', 'is_spring_slot_41_50'], ['51-60', 'is_spring_slot_51_60']] as $slot)
|
||||
<label class="flex items-center gap-3 p-3 rounded-xl border border-slate-100 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-900/50 cursor-pointer hover:border-cyan-500/30 transition-all">
|
||||
<input type="hidden" name="{{ $slot[1] }}" value="0">
|
||||
<input type="checkbox" name="{{ $slot[1] }}" value="1" {{ $machine->{$slot[1]} ? 'checked' : '' }} class="w-4 h-4 rounded text-cyan-500 focus:ring-cyan-500/20">
|
||||
<span class="text-xs font-black text-slate-600 dark:text-slate-400">{{ $slot[0] }}</span>
|
||||
</label>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: System & Payment -->
|
||||
<div class="space-y-8">
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 200ms">
|
||||
<div class="flex items-center gap-3 mb-8">
|
||||
<div class="w-10 h-10 rounded-xl bg-emerald-500/10 flex items-center justify-center text-emerald-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M2.25 18.75a60.07 60.07 0 0 1 15.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75m0-1.5a.75.75 0 0 1 .75.75v.75m-.75 0H3m.75 0h.75m-1.5 0a.75.75 0 0 1-.75-.75V3M3 10.5v1.5m10.5-3v-4.5m0 4.5h5.25m-5.25 0V10.5m0-1.5a.75.75 0 0 1 .75-.75h.75m-1.5 0H12m.75 0h.75m-1.5 0a.75.75 0 0 1-.75-.75V3.75M12 4.5H3.75a2.25 2.25 0 0 0-2.25 2.25v10.5a2.25 2.25 0 0 0 2.25 2.25h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 18.75 4.5Z"/></svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('Payment & Invoice') }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Payment Config') }}</label>
|
||||
<select name="payment_config_id" class="luxury-select w-full">
|
||||
<option value="">{{ __('Not Used') }}</option>
|
||||
@foreach($paymentConfigs as $config)
|
||||
<option value="{{ $config->id }}" {{ $machine->payment_config_id == $config->id ? 'selected' : '' }}>{{ $config->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice Status') }}</label>
|
||||
<select name="invoice_status" class="luxury-select w-full">
|
||||
<option value="0" {{ $machine->invoice_status == 0 ? 'selected' : '' }}>{{ __('No Invoice') }}</option>
|
||||
<option value="1" {{ $machine->invoice_status == 1 ? 'selected' : '' }}>{{ __('Default Donate') }}</option>
|
||||
<option value="2" {{ $machine->invoice_status == 2 ? 'selected' : '' }}>{{ __('Default Not Donate') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 300ms">
|
||||
<div class="flex items-center gap-3 mb-8">
|
||||
<div class="w-10 h-10 rounded-xl bg-sky-500/10 flex items-center justify-center text-sky-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z"/></svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('Member & External') }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<label class="flex items-center justify-between p-4 rounded-2xl border border-slate-100 dark:border-slate-800 bg-slate-50/30 dark:bg-slate-900/30 cursor-pointer">
|
||||
<div>
|
||||
<span class="block text-sm font-black text-slate-700 dark:text-slate-200">{{ __('Welcome Gift') }}</span>
|
||||
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{{ __('Enabled/Disabled') }}</span>
|
||||
</div>
|
||||
<div class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="hidden" name="welcome_gift_enabled" value="0">
|
||||
<input type="checkbox" name="welcome_gift_enabled" value="1" {{ $machine->welcome_gift_enabled ? 'checked' : '' }} class="sr-only peer">
|
||||
<div class="w-11 h-6 bg-slate-200 rounded-full peer peer-focus:ring-4 peer-focus:ring-cyan-300 dark:peer-focus:ring-cyan-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="flex items-center justify-between p-4 rounded-2xl border border-slate-100 dark:border-slate-800 bg-slate-50/30 dark:bg-slate-900/30 cursor-pointer">
|
||||
<div>
|
||||
<span class="block text-sm font-black text-slate-700 dark:text-slate-200">{{ __('Member System') }}</span>
|
||||
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{{ __('Enabled/Disabled') }}</span>
|
||||
</div>
|
||||
<div class="relative inline-flex items-center cursor-pointer">
|
||||
<input type="hidden" name="member_system_enabled" value="0">
|
||||
<input type="checkbox" name="member_system_enabled" value="1" {{ $machine->member_system_enabled ? 'checked' : '' }} class="sr-only peer">
|
||||
<div class="w-11 h-6 bg-slate-200 rounded-full peer peer-focus:ring-4 peer-focus:ring-cyan-300 dark:peer-focus:ring-cyan-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500"></div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Machine Images Management -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 400ms">
|
||||
<div class="flex items-center gap-3 mb-8">
|
||||
<div class="w-10 h-10 rounded-xl bg-indigo-500/10 flex items-center justify-center text-indigo-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"/></svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('Machine Images') }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
@if(!empty($machine->image_urls))
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
@foreach($machine->image_urls as $url)
|
||||
<div class="relative aspect-square rounded-2xl overflow-hidden border border-slate-100 dark:border-slate-800 shadow-sm group">
|
||||
<img src="{{ $url }}" class="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-110">
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="p-6 rounded-2xl border border-dashed border-slate-200 dark:border-slate-800 text-center">
|
||||
<p class="text-xs font-bold text-slate-400 capitalize">{{ __('No images uploaded') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Upload New Images') }} ({{ __('Max 3') }})</label>
|
||||
<input type="file" name="images[]" multiple accept="image/*" class="luxury-input w-full text-xs py-2">
|
||||
<p class="text-[10px] text-slate-400 mt-2 italic">* {{ __('Uploading new images will replace all existing images.') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
317
resources/views/admin/basic-settings/machines/index.blade.php
Normal file
317
resources/views/admin/basic-settings/machines/index.blade.php
Normal file
@@ -0,0 +1,317 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
<div class="space-y-10 pb-20" x-data="{
|
||||
showCreateModal: false,
|
||||
showDetailDrawer: false,
|
||||
currentMachine: null,
|
||||
openDetail(machine) {
|
||||
this.currentMachine = machine;
|
||||
this.showDetailDrawer = true;
|
||||
}
|
||||
}">
|
||||
<!-- 1. Header Area -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Machine Settings') }}</h1>
|
||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Management of operational parameters') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<button @click="showCreateModal = true" class="btn-luxury-primary flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
||||
<span>{{ __('Add Machine') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. Main Integrated Card -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||
<!-- Toolbar & Filters -->
|
||||
<div class="flex items-center justify-between mb-10">
|
||||
<form method="GET" action="{{ route('admin.basic-settings.machines.index') }}" class="relative group">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
|
||||
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
</span>
|
||||
<input type="text" name="search" value="{{ request('search') }}" placeholder="{{ __('Search machines...') }}" class="luxury-input py-2.5 pl-12 pr-6 block w-64">
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- scrollable table -->
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||
<thead>
|
||||
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Machine Info') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Status') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Card Reader') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Owner') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Action') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
||||
@foreach($machines as $machine)
|
||||
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
||||
<td class="px-6 py-6 cursor-pointer" @click="openDetail({{ $machine->toJson() }})">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">{{ $machine->name }}</div>
|
||||
<div class="flex items-center gap-2 mt-0.5">
|
||||
<span class="text-xs font-mono font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">{{ $machine->serial_no }}</span>
|
||||
<span class="text-xs text-slate-300 dark:text-slate-700">•</span>
|
||||
<span class="text-xs font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">{{ $machine->machineModel->name ?? '--' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-6">
|
||||
@php
|
||||
$isOnline = $machine->last_heartbeat_at && $machine->last_heartbeat_at->diffInMinutes() < 5;
|
||||
@endphp
|
||||
<div class="flex items-center gap-2.5">
|
||||
<div class="relative flex h-2.5 w-2.5">
|
||||
@if($isOnline)
|
||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
||||
<span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500"></span>
|
||||
@else
|
||||
<span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-slate-300 dark:bg-slate-600"></span>
|
||||
@endif
|
||||
</div>
|
||||
<span class="text-xs font-bold uppercase tracking-wider {{ $isOnline ? 'text-emerald-500' : 'text-slate-500 dark:text-slate-400' }}">
|
||||
{{ $isOnline ? __('Online') : __('Offline') }}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-6">
|
||||
<div class="text-sm font-bold text-slate-700 dark:text-slate-200">
|
||||
{{ $machine->card_reader_seconds ?? 0 }}s <span class="text-slate-300 dark:text-slate-700 mx-1.5">/</span> <span class="text-xs text-slate-500 dark:text-slate-400 font-medium">No.{{ $machine->card_reader_no ?? '--' }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-6">
|
||||
<span class="px-2.5 py-1 rounded-lg text-xs font-bold border border-sky-100 dark:border-sky-900/30 bg-sky-50 dark:bg-sky-900/20 text-sky-600 dark:text-sky-400 tracking-wide">
|
||||
{{ $machine->company->name ?? __('None') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-6 text-right space-x-2">
|
||||
<button @click="openDetail({{ json_encode($machine->only(['name', 'serial_no', 'status', 'location', 'last_heartbeat_at', 'card_reader_no', 'card_reader_seconds', 'firmware_version', 'api_token', 'heating_start_time', 'heating_end_time', 'image_urls'])) }})"
|
||||
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-indigo-500 hover:bg-indigo-500/5 border border-transparent hover:border-indigo-500/20 transition-all inline-flex" title="{{ __('View Details') }}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"/></svg>
|
||||
</button>
|
||||
<a href="{{ route('admin.basic-settings.machines.edit', $machine) }}"
|
||||
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 border border-transparent hover:border-cyan-500/20 transition-all inline-flex" title="{{ __('Edit Settings') }}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><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>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 3. Standard Pagination Footer -->
|
||||
<div class="mt-8 border-t border-slate-100/50 dark:border-slate-800/50 pt-6">
|
||||
{{ $machines->links('vendor.pagination.luxury') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Modal -->
|
||||
<template x-teleport="body">
|
||||
<div x-show="showCreateModal"
|
||||
class="fixed inset-0 z-[100] overflow-y-auto"
|
||||
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-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
x-cloak>
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pb-20 text-center sm:block sm:p-0">
|
||||
<div class="fixed inset-0 transition-opacity" aria-hidden="true" @click="showCreateModal = false">
|
||||
<div class="absolute inset-0 bg-slate-900/60 backdrop-blur-sm"></div>
|
||||
</div>
|
||||
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||
|
||||
<div class="inline-block align-bottom bg-white dark:bg-slate-900 rounded-3xl text-left overflow-hidden shadow-2xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full animate-luxury-in border border-slate-100 dark:border-slate-800">
|
||||
<div class="px-8 pt-8 pb-6 border-b border-slate-50 dark:border-slate-800/50 flex justify-between items-center">
|
||||
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Add Machine') }}</h3>
|
||||
<button @click="showCreateModal = false" class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition-colors">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form action="{{ route('admin.basic-settings.machines.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="px-8 py-8 space-y-6">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Machine Name') }}</label>
|
||||
<input type="text" name="name" required class="luxury-input w-full" placeholder="{{ __('Enter machine name') }}">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Serial No') }}</label>
|
||||
<input type="text" name="serial_no" required class="luxury-input w-full" placeholder="{{ __('Enter serial number') }}">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Owner') }}</label>
|
||||
<select name="company_id" required class="luxury-select w-full">
|
||||
<option value="">{{ __('Select Owner') }}</option>
|
||||
@foreach($companies as $company)
|
||||
<option value="{{ $company->id }}">{{ $company->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Model') }}</label>
|
||||
<select name="machine_model_id" required class="luxury-select w-full">
|
||||
<option value="">{{ __('Select Model') }}</option>
|
||||
@foreach($models as $model)
|
||||
<option value="{{ $model->id }}">{{ $model->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Customer Payment Config') }}</label>
|
||||
<select name="payment_config_id" class="luxury-select w-full">
|
||||
<option value="">{{ __('Not Used') }}</option>
|
||||
@foreach($paymentConfigs as $config)
|
||||
<option value="{{ $config->id }}">{{ $config->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Machine Images') }} ({{ __('Max 3') }})</label>
|
||||
<input type="file" name="images[]" multiple accept="image/*" class="luxury-input w-full text-xs py-2">
|
||||
<p class="text-[10px] text-slate-400 mt-2 italic">* {{ __('Will be automatically converted to WebP for optimization') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-8 py-6 bg-slate-50 dark:bg-slate-900/50 flex justify-end gap-3 rounded-b-3xl border-t border-slate-100 dark:border-slate-800">
|
||||
<button type="button" @click="showCreateModal = false" class="btn-luxury-ghost">{{ __('Cancel') }}</button>
|
||||
<button type="submit" class="btn-luxury-primary px-8">{{ __('Save') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Detail Drawer -->
|
||||
<template x-teleport="body">
|
||||
<div x-show="showDetailDrawer"
|
||||
class="fixed inset-0 z-[150]"
|
||||
x-cloak>
|
||||
<div class="absolute inset-0 bg-slate-900/40 backdrop-blur-sm transition-opacity"
|
||||
x-show="showDetailDrawer"
|
||||
x-transition:enter="ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click="showDetailDrawer = false"></div>
|
||||
|
||||
<div class="fixed inset-y-0 right-0 max-w-full flex">
|
||||
<div class="w-screen max-w-md"
|
||||
x-show="showDetailDrawer"
|
||||
x-transition:enter="transform transition ease-in-out duration-500"
|
||||
x-transition:enter-start="translate-x-full"
|
||||
x-transition:enter-end="translate-x-0"
|
||||
x-transition:leave="transform transition ease-in-out duration-500"
|
||||
x-transition:leave-start="translate-x-0"
|
||||
x-transition:leave-end="translate-x-full">
|
||||
<div class="h-full flex flex-col bg-white dark:bg-slate-900 shadow-2xl border-l border-slate-100 dark:border-slate-800">
|
||||
<!-- Drawer Header -->
|
||||
<div class="px-6 py-8 border-b border-slate-100 dark:border-slate-800 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-xl font-black text-slate-800 dark:text-white">{{ __('Parameters') }}</h2>
|
||||
<p class="text-[10px] font-bold text-slate-400 uppercase tracking-[0.2em] mt-1" x-text="currentMachine?.name"></p>
|
||||
</div>
|
||||
<button @click="showDetailDrawer = false" class="p-2 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-800 transition-colors">
|
||||
<svg class="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Drawer Content -->
|
||||
<div class="flex-1 overflow-y-auto px-6 py-8 space-y-10 custom-scrollbar">
|
||||
<!-- Machine Images Gallery -->
|
||||
<template x-if="currentMachine?.image_urls && currentMachine.image_urls.length > 0">
|
||||
<section class="space-y-4">
|
||||
<h3 class="text-[11px] font-black text-indigo-500 uppercase tracking-[0.3em]">{{ __('Machine Images') }}</h3>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<template x-for="(url, index) in currentMachine.image_urls" :key="index">
|
||||
<div class="relative group aspect-square rounded-2xl overflow-hidden border border-slate-100 dark:border-slate-800 shadow-sm bg-slate-50 dark:bg-slate-800/50">
|
||||
<img :src="url" class="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-110">
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<!-- Technical Specs -->
|
||||
<section class="space-y-6">
|
||||
<h3 class="text-[11px] font-black text-cyan-500 uppercase tracking-[0.3em]">{{ __('Hardware & Network') }}</h3>
|
||||
<div class="grid grid-cols-1 gap-4">
|
||||
<div class="bg-slate-50 dark:bg-slate-800/40 p-5 rounded-2xl border border-slate-100 dark:border-slate-800/80">
|
||||
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest block mb-1.5">{{ __('Serial & Version') }}</span>
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-xs font-mono font-bold text-slate-700 dark:text-slate-300" x-text="currentMachine?.serial_no"></div>
|
||||
<span class="px-2 py-0.5 rounded-md bg-white dark:bg-slate-900 text-[9px] font-black text-slate-500 border border-slate-100 dark:border-slate-800" x-text="'v' + (currentMachine?.firmware_version || '1.0')"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-slate-50 dark:bg-slate-800/40 p-5 rounded-2xl border border-slate-100 dark:border-slate-800/80">
|
||||
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest block mb-1.5">{{ __('Heartbeat') }}</span>
|
||||
<div class="text-xs font-bold text-slate-700 dark:text-slate-300" x-text="currentMachine?.last_heartbeat_at ? new Date(currentMachine.last_heartbeat_at).toLocaleString() : '--'"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Operational Settings -->
|
||||
<section class="space-y-6">
|
||||
<h3 class="text-[11px] font-black text-amber-500 uppercase tracking-[0.3em]">{{ __('Operations') }}</h3>
|
||||
<div class="space-y-4">
|
||||
<div class="flex items-center justify-between p-2 border-b border-slate-50 dark:border-white/5">
|
||||
<span class="text-xs font-bold text-slate-500">{{ __('Heating Range') }}</span>
|
||||
<span class="text-xs font-black text-slate-700 dark:text-slate-300" x-text="(currentMachine?.heating_start_time ? currentMachine.heating_start_time.substring(0, 5) : '00:00') + ' ~ ' + (currentMachine?.heating_end_time ? currentMachine.heating_end_time.substring(0, 5) : '00:00')"></span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-2 border-b border-slate-50 dark:border-white/5">
|
||||
<span class="text-xs font-bold text-slate-500">{{ __('Card Reader No') }}</span>
|
||||
<span class="text-xs font-black text-slate-700 dark:text-slate-300" x-text="currentMachine?.card_reader_no || '--'"></span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-2 border-b border-slate-50 dark:border-white/5">
|
||||
<span class="text-xs font-bold text-slate-500">{{ __('API Token') }}</span>
|
||||
<span class="text-[10px] font-mono text-slate-400 truncate max-w-[150px]" x-text="currentMachine?.api_token || '--'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Location -->
|
||||
<section class="space-y-4">
|
||||
<h3 class="text-[11px] font-black text-emerald-500 uppercase tracking-[0.3em]">{{ __('Location') }}</h3>
|
||||
<div class="p-4 bg-emerald-50/30 dark:bg-emerald-500/5 rounded-2xl border border-emerald-100/50 dark:border-emerald-500/10">
|
||||
<p class="text-xs text-emerald-700 dark:text-emerald-400 leading-relaxed font-bold" x-text="currentMachine?.location || '{{ __('No location set') }}'"></p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- Drawer Footer -->
|
||||
<div class="p-6 border-t border-slate-100 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-900/50">
|
||||
<button @click="showDetailDrawer = false" class="w-full btn-luxury-ghost">{{ __('Close Panel') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,187 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
<div class="space-y-10 pb-20">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="{{ route('admin.basic-settings.payment-configs.index') }}" class="p-2.5 rounded-xl bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 text-slate-500 hover:text-cyan-500 transition-all shadow-sm">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18"/></svg>
|
||||
</a>
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Create Payment Config') }}</h1>
|
||||
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Define new third-party payment parameters') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button type="submit" form="create-form" class="btn-luxury-primary px-8 flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"/></svg>
|
||||
<span>{{ __('Save Config') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="create-form" action="{{ route('admin.basic-settings.payment-configs.store') }}" method="POST" class="space-y-8">
|
||||
@csrf
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
|
||||
<!-- Left Column: Primary Info -->
|
||||
<div class="lg:col-span-12 space-y-8">
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Configuration Name') }} <span class="text-rose-500">*</span></label>
|
||||
<input type="text" name="name" required class="luxury-input w-full" placeholder="{{ __('e.g., Company Standard Pay') }}">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Belongs To Company') }} <span class="text-rose-500">*</span></label>
|
||||
<select name="company_id" required class="luxury-select w-full">
|
||||
<option value="">{{ __('Select Company') }}</option>
|
||||
@foreach($companies as $company)
|
||||
<option value="{{ $company->id }}">{{ $company->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- provider groups -->
|
||||
<div class="lg:col-span-6 space-y-8">
|
||||
<!-- ECPay -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 50ms">
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<div class="w-12 h-12 rounded-2xl bg-emerald-50 dark:bg-emerald-500/10 flex items-center justify-center text-emerald-500 border border-emerald-100 dark:border-emerald-500/20 shadow-sm shadow-emerald-500/10">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('ECPay Invoice') }}</h3>
|
||||
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">綠界科技電子發票設定</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Store ID') }}</label>
|
||||
<input type="text" name="settings[ecpay_invoice][store_id]" class="luxury-input w-full" placeholder="2000132">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('HashKey') }}</label>
|
||||
<input type="text" name="settings[ecpay_invoice][hash_key]" class="luxury-input w-full" placeholder="ej67pDIFpSST6p4q">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('HashIV') }}</label>
|
||||
<input type="text" name="settings[ecpay_invoice][hash_iv]" class="luxury-input w-full" placeholder="q9m3S9p3S9999999">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- E.SUN -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 100ms">
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<div class="w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-500/10 flex items-center justify-center text-indigo-500 border border-indigo-100 dark:border-indigo-500/20 shadow-sm shadow-indigo-500/10">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M16.875 12h1.125a3.375 3.375 0 0 1 3.375 3.375v1.125m-11.25 4.5h1.125a3.375 3.375 0 0 0 3.375-3.375V16.5a3.375 3.375 0 0 1 3.375-3.375h1.125m-11.25 4.5h1.125a3.375 3.375 0 0 1 3.375 3.375v1.125m-3.375-1.125h.008v.008h-.008v-.008Zm3.375 3.375h.008v.008h-.008v-.008Zm0-3.375h.008v.008h-.008v-.008Zm-3.375-3.375h.008v.008h-.008v-.008Zm0 3.375h.008v.008h-.008v-.008Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('E.SUN QR Scan') }}</h3>
|
||||
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">玉山銀行掃碼支付設定</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('StoreID') }}</label>
|
||||
<input type="text" name="settings[esun_scan][store_id]" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('TermID') }}</label>
|
||||
<input type="text" name="settings[esun_scan][term_id]" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Key') }}</label>
|
||||
<input type="text" name="settings[esun_scan][key]" class="luxury-input w-full">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LINE Pay Direct -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 150ms">
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<div class="w-12 h-12 rounded-2xl bg-emerald-50 dark:bg-emerald-500/10 flex items-center justify-center text-emerald-500 border border-emerald-100 dark:border-emerald-500/20 shadow-sm shadow-emerald-500/10">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m24.408 0a8.959 8.959 0 0 1 .284-2.253m0 2.253C20.46 17.1 16.485 19.5 12 19.5S3.538 17.1 2.284 14.253"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('LINE Pay Direct') }}</h3>
|
||||
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">LINE Pay 官方直連設定</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('ChannelId') }}</label>
|
||||
<input type="text" name="settings[line_pay][channel_id]" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('ChannelSecret') }}</label>
|
||||
<input type="text" name="settings[line_pay][channel_secret]" class="luxury-input w-full">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-6 space-y-8">
|
||||
<!-- Tappay -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 200ms">
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<div class="w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-500/10 flex items-center justify-center text-indigo-500 border border-indigo-100 dark:border-indigo-500/20 shadow-sm shadow-indigo-500/10">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('TapPay Integration') }}</h3>
|
||||
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">喬睿科技支付串接設定</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('PARTNER_KEY') }}</label>
|
||||
<input type="text" name="settings[tappay][partner_key]" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('APP_ID') }}</label>
|
||||
<input type="text" name="settings[tappay][app_id]" class="luxury-input w-full">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('APP_KEY') }}</label>
|
||||
<input type="text" name="settings[tappay][app_key]" class="luxury-input w-full">
|
||||
</div>
|
||||
<div class="pt-6 border-t border-slate-100 dark:border-slate-800/80">
|
||||
<p class="text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-6 font-display">{{ __('Merchant IDs (商店代號群)') }}</p>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1">{{ __('LINE_MERCHANT_ID') }}</label>
|
||||
<input type="text" name="settings[tappay][line_merchant_id]" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1">{{ __('JKO_MERCHANT_ID') }}</label>
|
||||
<input type="text" name="settings[tappay][jko_merchant_id]" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1">{{ __('PI_MERCHANT_ID') }}</label>
|
||||
<input type="text" name="settings[tappay][pi_merchant_id]" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1">{{ __('PS_MERCHANT_ID') }} (全盈+Pay)</label>
|
||||
<input type="text" name="settings[tappay][ps_merchant_id]" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1">{{ __('EASY_MERCHANT_ID') }} (悠遊付)</label>
|
||||
<input type="text" name="settings[tappay][easy_merchant_id]" class="luxury-input w-full">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,191 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
<div class="space-y-10 pb-20">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="{{ route('admin.basic-settings.payment-configs.index') }}" class="p-2.5 rounded-xl bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 text-slate-500 hover:text-cyan-500 transition-all shadow-sm">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18"/></svg>
|
||||
</a>
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Edit Payment Config') }}</h1>
|
||||
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ $paymentConfig->name }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button type="submit" form="edit-form" class="btn-luxury-primary px-8 flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"/></svg>
|
||||
<span>{{ __('Save Changes') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="edit-form" action="{{ route('admin.basic-settings.payment-configs.update', $paymentConfig) }}" method="POST" class="space-y-8">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
|
||||
<!-- Left Column: Primary Info -->
|
||||
<div class="lg:col-span-12 space-y-8">
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Configuration Name') }} <span class="text-rose-500">*</span></label>
|
||||
<input type="text" name="name" value="{{ $paymentConfig->name }}" required class="luxury-input w-full" placeholder="{{ __('e.g., Company Standard Pay') }}">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Belongs To Company') }} <span class="text-rose-500">*</span></label>
|
||||
<select name="company_id" required class="luxury-select w-full">
|
||||
<option value="">{{ __('Select Company') }}</option>
|
||||
@foreach($companies as $company)
|
||||
<option value="{{ $company->id }}" {{ $paymentConfig->company_id == $company->id ? 'selected' : '' }}>{{ $company->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- provider groups -->
|
||||
@php
|
||||
$settings = $paymentConfig->settings ?? [];
|
||||
@endphp
|
||||
<div class="lg:col-span-6 space-y-8">
|
||||
<!-- ECPay -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 50ms">
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<div class="w-12 h-12 rounded-2xl bg-emerald-50 dark:bg-emerald-500/10 flex items-center justify-center text-emerald-500 border border-emerald-100 dark:border-emerald-500/20 shadow-sm shadow-emerald-500/10">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('ECPay Invoice') }}</h3>
|
||||
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">綠界科技電子發票設定</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Store ID') }}</label>
|
||||
<input type="text" name="settings[ecpay_invoice][store_id]" value="{{ $settings['ecpay_invoice']['store_id'] ?? '' }}" class="luxury-input w-full" placeholder="2000132">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('HashKey') }}</label>
|
||||
<input type="text" name="settings[ecpay_invoice][hash_key]" value="{{ $settings['ecpay_invoice']['hash_key'] ?? '' }}" class="luxury-input w-full" placeholder="ej67pDIFpSST6p4q">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('HashIV') }}</label>
|
||||
<input type="text" name="settings[ecpay_invoice][hash_iv]" value="{{ $settings['ecpay_invoice']['hash_iv'] ?? '' }}" class="luxury-input w-full" placeholder="q9m3S9p3S9999999">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- E.SUN -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 100ms">
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<div class="w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-500/10 flex items-center justify-center text-indigo-500 border border-indigo-100 dark:border-indigo-500/20 shadow-sm shadow-indigo-500/10">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M16.875 12h1.125a3.375 3.375 0 0 1 3.375 3.375v1.125m-11.25 4.5h1.125a3.375 3.375 0 0 0 3.375-3.375V16.5a3.375 3.375 0 0 1 3.375-3.375h1.125m-11.25 4.5h1.125a3.375 3.375 0 0 1 3.375 3.375v1.125m-3.375-1.125h.008v.008h-.008v-.008Zm3.375 3.375h.008v.008h-.008v-.008Zm0-3.375h.008v.008h-.008v-.008Zm-3.375-3.375h.008v.008h-.008v-.008Zm0 3.375h.008v.008h-.008v-.008Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('E.SUN QR Scan') }}</h3>
|
||||
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">玉山銀行掃碼支付設定</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('StoreID') }}</label>
|
||||
<input type="text" name="settings[esun_scan][store_id]" value="{{ $settings['esun_scan']['store_id'] ?? '' }}" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('TermID') }}</label>
|
||||
<input type="text" name="settings[esun_scan][term_id]" value="{{ $settings['esun_scan']['term_id'] ?? '' }}" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Key') }}</label>
|
||||
<input type="text" name="settings[esun_scan][key]" value="{{ $settings['esun_scan']['key'] ?? '' }}" class="luxury-input w-full">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LINE Pay Direct -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 150ms">
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<div class="w-12 h-12 rounded-2xl bg-emerald-50 dark:bg-emerald-500/10 flex items-center justify-center text-emerald-500 border border-emerald-100 dark:border-emerald-500/20 shadow-sm shadow-emerald-500/10">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m24.408 0a8.959 8.959 0 0 1 .284-2.253m0 2.253C20.46 17.1 16.485 19.5 12 19.5S3.538 17.1 2.284 14.253"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('LINE Pay Direct') }}</h3>
|
||||
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">LINE Pay 官方直連設定</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('ChannelId') }}</label>
|
||||
<input type="text" name="settings[line_pay][channel_id]" value="{{ $settings['line_pay']['channel_id'] ?? '' }}" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('ChannelSecret') }}</label>
|
||||
<input type="text" name="settings[line_pay][channel_secret]" value="{{ $settings['line_pay']['channel_secret'] ?? '' }}" class="luxury-input w-full">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lg:col-span-6 space-y-8">
|
||||
<!-- Tappay -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 200ms">
|
||||
<div class="flex items-center gap-4 mb-8">
|
||||
<div class="w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-500/10 flex items-center justify-center text-indigo-500 border border-indigo-100 dark:border-indigo-500/20 shadow-sm shadow-indigo-500/10">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight italic">{{ __('TapPay Integration') }}</h3>
|
||||
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">喬睿科技支付串接設定</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-6">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('PARTNER_KEY') }}</label>
|
||||
<input type="text" name="settings[tappay][partner_key]" value="{{ $settings['tappay']['partner_key'] ?? '' }}" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('APP_ID') }}</label>
|
||||
<input type="text" name="settings[tappay][app_id]" value="{{ $settings['tappay']['app_id'] ?? '' }}" class="luxury-input w-full">
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('APP_KEY') }}</label>
|
||||
<input type="text" name="settings[tappay][app_key]" value="{{ $settings['tappay']['app_key'] ?? '' }}" class="luxury-input w-full">
|
||||
</div>
|
||||
<div class="pt-6 border-t border-slate-100 dark:border-slate-800/80">
|
||||
<p class="text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-6 font-display">{{ __('Merchant IDs (商店代號群)') }}</p>
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1">{{ __('LINE_MERCHANT_ID') }}</label>
|
||||
<input type="text" name="settings[tappay][line_merchant_id]" value="{{ $settings['tappay']['line_merchant_id'] ?? '' }}" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1">{{ __('JKO_MERCHANT_ID') }}</label>
|
||||
<input type="text" name="settings[tappay][jko_merchant_id]" value="{{ $settings['tappay']['jko_merchant_id'] ?? '' }}" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1">{{ __('PI_MERCHANT_ID') }}</label>
|
||||
<input type="text" name="settings[tappay][pi_merchant_id]" value="{{ $settings['tappay']['pi_merchant_id'] ?? '' }}" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1">{{ __('PS_MERCHANT_ID') }} (全盈+Pay)</label>
|
||||
<input type="text" name="settings[tappay][ps_merchant_id]" value="{{ $settings['tappay']['ps_merchant_id'] ?? '' }}" class="luxury-input w-full">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-[10px] font-bold text-slate-400 uppercase tracking-widest mb-1">{{ __('EASY_MERCHANT_ID') }} (悠遊付)</label>
|
||||
<input type="text" name="settings[tappay][easy_merchant_id]" value="{{ $settings['tappay']['easy_merchant_id'] ?? '' }}" class="luxury-input w-full">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,82 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
<div class="space-y-10 pb-20">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Payment Configuration') }}</h1>
|
||||
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Merchant payment gateway settings management') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ route('admin.basic-settings.payment-configs.create') }}" class="btn-luxury-primary flex items-center gap-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
||||
<span>{{ __('Create Config') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Card -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||
<thead>
|
||||
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Config Name') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Belongs To') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Last Updated') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Action') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
||||
@foreach($paymentConfigs as $config)
|
||||
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
||||
<td class="px-6 py-6 font-extrabold text-slate-800 dark:text-slate-100 italic">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300">
|
||||
<svg class="w-5 h-5" 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>
|
||||
<div>
|
||||
<div class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors italic whitespace-nowrap">{{ $config->name }}</div>
|
||||
<div class="text-[10px] font-mono font-medium text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">ID: {{ str_pad($config->id, 5, '0', STR_PAD_LEFT) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-6 border-b border-transparent">
|
||||
<span class="px-2.5 py-1 rounded-lg text-xs font-bold border border-sky-100 dark:border-sky-900/30 bg-sky-50 dark:bg-sky-900/20 text-sky-600 dark:text-sky-400 tracking-wide">
|
||||
{{ $config->company->name ?? __('None') }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-6">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs font-bold text-slate-600 dark:text-slate-300">{{ $config->updated_at->format('Y/m/d H:i') }}</span>
|
||||
<span class="text-[10px] font-medium text-slate-400 dark:text-slate-500 mt-0.5">{{ $config->updated_at->diffForHumans() }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-6 text-right space-x-2">
|
||||
<a href="{{ route('admin.basic-settings.payment-configs.edit', $config) }}"
|
||||
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 border border-transparent hover:border-cyan-500/20 transition-all inline-flex" title="{{ __('Edit') }}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><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>
|
||||
</a>
|
||||
<form action="{{ route('admin.basic-settings.payment-configs.destroy', $config) }}" method="POST" class="inline-block" onsubmit="return confirm('{{ __('Are you sure you want to delete this configuration?') }}')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit"
|
||||
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-rose-500 hover:bg-rose-500/5 border border-transparent hover:border-rose-500/20 transition-all"
|
||||
title="{{ __('Delete') }}">
|
||||
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 border-t border-slate-100/50 dark:border-slate-800/50 pt-6">
|
||||
{{ $paymentConfigs->links('vendor.pagination.luxury') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -62,16 +62,16 @@
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 300ms">
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between mb-8 gap-6">
|
||||
<form action="{{ route('admin.permission.companies.index') }}" method="GET" class="relative group">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
|
||||
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
</span>
|
||||
<input type="text" name="search" value="{{ request('search') }}"
|
||||
class="py-3 pl-12 pr-6 block w-64 border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 rounded-2xl text-sm font-bold text-slate-700 dark:text-slate-200 placeholder-slate-400 dark:placeholder-slate-500 focus:ring-4 focus:ring-cyan-500/10 focus:border-cyan-500 transition-all outline-none"
|
||||
class="py-2.5 pl-12 pr-6 block w-64 luxury-input"
|
||||
placeholder="{{ __('Search customers...') }}">
|
||||
<input type="hidden" name="per_page" value="{{ request('per_page', 10) }}">
|
||||
</form>
|
||||
@@ -254,12 +254,10 @@
|
||||
</div>
|
||||
|
||||
<form
|
||||
:action="editing ? '{{ route('admin.permission.companies.index') }}/' + currentCompany.id : '{{ route('admin.permission.companies.store') }}'"
|
||||
:action="editing ? '{{ url('admin/permission/companies') }}/' + currentCompany.id : '{{ route('admin.permission.companies.store') }}'"
|
||||
method="POST" class="space-y-6">
|
||||
@csrf
|
||||
<template x-if="editing">
|
||||
@method('PUT')
|
||||
</template>
|
||||
<input type="hidden" name="_method" :value="editing ? 'PUT' : 'POST'">
|
||||
|
||||
<!-- Profile Section -->
|
||||
<div class="space-y-6">
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@php
|
||||
$routeName = request()->route()->getName();
|
||||
$baseRoute = str_contains($routeName, 'sub-accounts') ? 'admin.data-config.sub-accounts' : 'admin.permission.accounts';
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
<div class="space-y-6" x-data="{
|
||||
showModal: false,
|
||||
@@ -31,8 +36,10 @@
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white font-display tracking-tight">{{ __('Account Management') }}</h1>
|
||||
<p class="text-xs font-bold text-slate-400 dark:text-slate-500 mt-1 uppercase tracking-[0.2em]">{{ __('Manage administrative and tenant accounts') }}</p>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white font-display tracking-tight">{{ $title }}</h1>
|
||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">
|
||||
{{ __('Manage administrative and tenant accounts') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<button @click="openCreateModal()" class="btn-luxury-primary">
|
||||
@@ -45,11 +52,11 @@
|
||||
<!-- Accounts Content (Integrated Card) -->
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||
<!-- Filters & Search -->
|
||||
<form action="{{ route('admin.permission.accounts') }}" method="GET" class="mb-10">
|
||||
<form action="{{ route($baseRoute) }}" method="GET" class="mb-10">
|
||||
<div class="flex flex-col md:flex-row items-start md:items-center gap-4 w-full md:w-auto">
|
||||
<div class="relative group w-full md:w-80">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none">
|
||||
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
|
||||
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
</svg>
|
||||
@@ -73,13 +80,13 @@
|
||||
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||
<thead>
|
||||
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('User Info') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('User Info') }}</th>
|
||||
@if(auth()->user()->isSystemAdmin())
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Belongs To') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Belongs To') }}</th>
|
||||
@endif
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Role') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Status') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Actions') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Role') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Status') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
||||
@@ -96,50 +103,69 @@
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">{{ $user->name }}</span>
|
||||
<span class="text-[11px] font-bold text-slate-400 dark:text-slate-500 mt-0.5 tracking-tight">{{ $user->username }} @if($user->email) • {{ $user->email }} @endif</span>
|
||||
<span class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-0.5 tracking-tight">{{ $user->username }} @if($user->email) • {{ $user->email }} @endif</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@if(auth()->user()->isSystemAdmin())
|
||||
<td class="px-6 py-6">
|
||||
@if($user->company)
|
||||
<span class="text-xs font-bold text-slate-600 dark:text-slate-300 tracking-tight">{{ $user->company->name }}</span>
|
||||
<span class="text-xs font-bold text-slate-700 dark:text-slate-200 tracking-tight">{{ $user->company->name }}</span>
|
||||
@else
|
||||
<span class="px-2.5 py-1 rounded-lg text-[10px] font-black bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 uppercase tracking-widest">{{ __('SYSTEM') }}</span>
|
||||
<span class="px-2.5 py-1 rounded-lg text-xs font-bold bg-cyan-500/10 text-cyan-600 dark:text-cyan-400 uppercase tracking-widest">{{ __('SYSTEM') }}</span>
|
||||
@endif
|
||||
</td>
|
||||
@endif
|
||||
<td class="px-6 py-6 text-center">
|
||||
@foreach($user->roles as $role)
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-[10px] font-black bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 border border-slate-200 dark:border-slate-700 uppercase tracking-widest">
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 border border-slate-200 dark:border-slate-700 uppercase tracking-wider">
|
||||
{{ $role->name }}
|
||||
</span>
|
||||
@endforeach
|
||||
</td>
|
||||
<td class="px-6 py-6 text-center">
|
||||
@if($user->status)
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-[10px] font-black bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 tracking-wider uppercase">
|
||||
<span class="size-1.5 rounded-full bg-emerald-500 mr-2 animate-pulse"></span>
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-bold bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border border-emerald-500/20 tracking-wider uppercase">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 mr-2 animate-pulse"></span>
|
||||
{{ __('Active') }}
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-[10px] font-black bg-slate-100 dark:bg-slate-800 text-slate-400 dark:text-slate-500 border border-slate-200 dark:border-slate-700 tracking-wider uppercase">
|
||||
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-bold bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 border border-slate-200 dark:border-slate-700 tracking-wider uppercase">
|
||||
{{ __('Disabled') }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-6 text-right">
|
||||
<div class="flex justify-end items-center gap-2">
|
||||
<button @click='openEditModal(@json($user))' class="p-2 rounded-xl bg-slate-50/50 dark:bg-slate-900/30 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/10 transition-all border border-transparent hover:border-cyan-500/20 shadow-sm">
|
||||
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><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>
|
||||
@if(!$user->hasRole('super-admin'))
|
||||
<button @click="openEditModal(@js($user))"
|
||||
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') }}">
|
||||
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
stroke-width="2.5">
|
||||
<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>
|
||||
<form action="{{ route($baseRoute . '.destroy', $user->id) }}"
|
||||
method="POST"
|
||||
onsubmit="return confirm('{{ __('Are you sure you want to delete this account?') }}')"
|
||||
class="inline-block">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit"
|
||||
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-rose-500 hover:bg-rose-500/5 transition-all border border-transparent hover:border-rose-500/20"
|
||||
title="{{ __('Delete') }}">
|
||||
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
stroke-width="2.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round"
|
||||
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
||||
</svg>
|
||||
</button>
|
||||
<form action="{{ route('admin.permission.accounts.destroy', $user->id) }}" method="POST" onsubmit="return confirm('{{ __('Are you sure you want to delete this account?') }}')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="p-2 rounded-xl bg-slate-50/50 dark:bg-slate-900/30 text-slate-400 hover:text-rose-500 hover:bg-rose-500/10 transition-all border border-transparent hover:border-rose-500/20 shadow-sm">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="size-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
</form>
|
||||
@else
|
||||
<span class="text-[10px] font-black text-slate-300 dark:text-slate-600 uppercase tracking-widest px-2">{{ __('Protected') }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -179,7 +205,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form :action="editing ? '{{ url('admin/permission/accounts') }}/' + currentUser.id : '{{ route('admin.permission.accounts.store') }}'" method="POST" class="space-y-6">
|
||||
<form :action="editing ? '{{ route($baseRoute) }}/' + currentUser.id : '{{ route($baseRoute . '.store') }}'" method="POST" class="space-y-6">
|
||||
@csrf
|
||||
<template x-if="editing">
|
||||
<input type="hidden" name="_method" value="PUT">
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@php
|
||||
$routeName = request()->route()->getName();
|
||||
$baseRoute = str_contains($routeName, 'sub-account-roles') ? 'admin.data-config.sub-account-roles' : 'admin.permission.roles';
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
<div class="space-y-6" x-data="{
|
||||
showModal: false,
|
||||
@@ -7,12 +12,14 @@
|
||||
roleId: '',
|
||||
roleName: '',
|
||||
rolePermissions: [],
|
||||
isSystem: false,
|
||||
modalTitle: '{{ __('Create Role') }}',
|
||||
openModal(edit = false, id = '', name = '', permissions = []) {
|
||||
openModal(edit = false, id = '', name = '', permissions = [], isSys = false) {
|
||||
this.isEdit = edit;
|
||||
this.roleId = id;
|
||||
this.roleName = name;
|
||||
this.rolePermissions = Array.isArray(permissions) ? permissions : (typeof permissions === 'string' ? JSON.parse(permissions) : []);
|
||||
this.isSystem = isSys;
|
||||
this.modalTitle = edit ? '{{ __('Edit Role') }}' : '{{ __('Create Role') }}';
|
||||
this.showModal = true;
|
||||
}
|
||||
@@ -20,7 +27,7 @@
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
|
||||
<div>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Roles') }}</h1>
|
||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white font-display tracking-tight">{{ $title }}</h1>
|
||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Define and manage security roles and permissions.') }}</p>
|
||||
</div>
|
||||
<button @click="openModal()" class="btn-luxury-primary text-sm">
|
||||
@@ -33,8 +40,8 @@
|
||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||
<!-- Toolbar -->
|
||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-10">
|
||||
<form action="{{ route('admin.permission.roles') }}" method="GET" class="relative group">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none">
|
||||
<form action="{{ route($baseRoute) }}" method="GET" class="relative group">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
|
||||
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="11" cy="11" r="8"></circle>
|
||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||
@@ -49,11 +56,11 @@
|
||||
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||
<thead>
|
||||
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Role Name') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Type') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Permissions') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Users') }}</th>
|
||||
<th class="px-6 py-4 text-[11px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Actions') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Role Name') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Type') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Permissions') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Users') }}</th>
|
||||
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Actions') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
||||
@@ -61,7 +68,7 @@
|
||||
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
||||
<td class="px-6 py-6">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500/10 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-all duration-500">
|
||||
<div class="w-10 h-10 rounded-2xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
|
||||
</div>
|
||||
<span class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors">{{ $role->name }}</span>
|
||||
@@ -74,40 +81,40 @@
|
||||
</td>
|
||||
<td class="px-6 py-6">
|
||||
@if($role->is_system)
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-[10px] font-black bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 border border-slate-200 dark:border-slate-700 uppercase tracking-widest">
|
||||
{{ __('System') }}
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 border border-slate-200 dark:border-slate-800 uppercase tracking-wider">
|
||||
{{ __('System Level') }}
|
||||
</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-[10px] font-black bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 tracking-wider uppercase">
|
||||
{{ __('Custom') }}
|
||||
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border border-emerald-500/20 tracking-wider uppercase">
|
||||
{{ __('Company Level') }}
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-6">
|
||||
<div class="flex flex-wrap gap-1 max-w-xs">
|
||||
@forelse($role->permissions->take(6) as $permission)
|
||||
<span class="px-2 py-0.5 text-[10px] bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 rounded border border-slate-200 dark:border-slate-700 uppercase font-bold tracking-tight">{{ __(str_replace('menu.', '', $permission->name)) }}</span>
|
||||
<span class="px-2 py-0.5 text-xs bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 rounded border border-slate-200 dark:border-slate-700 uppercase font-bold tracking-tight">{{ __(str_replace('menu.', '', $permission->name)) }}</span>
|
||||
@empty
|
||||
<span class="text-[11px] font-bold text-slate-400 italic tracking-tight">{{ __('No permissions') }}</span>
|
||||
<span class="text-xs font-bold text-slate-500 dark:text-slate-400 italic tracking-tight">{{ __('No permissions') }}</span>
|
||||
@endforelse
|
||||
@if($role->permissions->count() > 6)
|
||||
<span class="px-2 py-0.5 text-[10px] bg-slate-100 dark:bg-slate-800 text-slate-400 rounded border border-slate-200 dark:border-slate-700 uppercase font-bold tracking-tight">+{{ $role->permissions->count() - 6 }}</span>
|
||||
<span class="px-2 py-0.5 text-xs bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 rounded border border-slate-200 dark:border-slate-700 uppercase font-bold tracking-tight">+{{ $role->permissions->count() - 6 }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-6 text-center">
|
||||
<span class="text-sm font-black text-slate-600 dark:text-slate-400">{{ $role->users()->count() }}</span>
|
||||
<span class="text-sm font-extrabold text-slate-700 dark:text-slate-300">{{ $role->users()->count() }}</span>
|
||||
</td>
|
||||
<td class="px-6 py-6 text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button @click="openModal(true, '{{ $role->id }}', '{{ $role->name }}', {{ $role->permissions->pluck('name') }})" class="p-2 rounded-xl bg-slate-50/50 dark:bg-slate-900/30 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/10 transition-all border border-transparent hover:border-cyan-500/20 shadow-sm tooltip" title="{{ __('Edit') }}">
|
||||
<button @click="openModal(true, @js($role->id), @js($role->name), @js($role->permissions->pluck('name')), {{ $role->is_system ? 'true' : 'false' }})" 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 tooltip" title="{{ __('Edit') }}">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><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>
|
||||
@if(!$role->is_system)
|
||||
<form action="{{ route('admin.permission.roles.destroy', $role->id) }}" method="POST" @submit.prevent="if(confirm('{{ __('Are you sure you want to delete this role?') }}')) $el.submit()" class="inline text-slate-400">
|
||||
@if($role->name !== 'super-admin' && (auth()->user()->isSystemAdmin() || !$role->is_system))
|
||||
<form action="{{ route($baseRoute . '.destroy', $role->id) }}" method="POST" @submit.prevent="if(confirm('{{ __('Are you sure you want to delete this role?') }}')) $el.submit()" class="inline text-slate-400">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="p-2 rounded-xl bg-slate-50/50 dark:bg-slate-900/30 text-slate-400 hover:text-rose-500 hover:bg-rose-500/10 transition-all border border-transparent hover:border-rose-500/20 shadow-sm tooltip" title="{{ __('Delete') }}">
|
||||
<button type="submit" class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-rose-500 hover:bg-rose-500/5 transition-all border border-transparent hover:border-rose-500/20 tooltip" title="{{ __('Delete') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
@@ -150,7 +157,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<form :action="isEdit ? '{{ route('admin.permission.roles') }}/' + roleId : '{{ route('admin.permission.roles.store') }}'" method="POST">
|
||||
<form :action="isEdit ? '{{ route($baseRoute) }}/' + roleId : '{{ route($baseRoute . '.store') }}'" method="POST">
|
||||
@csrf
|
||||
<template x-if="isEdit"><input type="hidden" name="_method" value="PUT"></template>
|
||||
|
||||
@@ -160,14 +167,30 @@
|
||||
<div class="space-y-6">
|
||||
<div class="space-y-2">
|
||||
<label class="text-xs font-black text-slate-400 uppercase tracking-widest pl-1">{{ __('Role Name') }}</label>
|
||||
<input type="text" name="name" x-model="roleName" required class="luxury-input w-full" placeholder="{{ __('Enter role name') }}" :disabled="isEdit && {{ json_encode($roles->pluck('is_system', 'id')) }}[roleId]">
|
||||
<template x-if="isEdit && {{ json_encode($roles->pluck('is_system', 'id')) }}[roleId]">
|
||||
<input type="text" name="name" x-model="roleName" required class="luxury-input w-full" placeholder="{{ __('Enter role name') }}" :disabled="isEdit && roleName === 'super-admin'">
|
||||
<template x-if="isEdit && roleName === 'super-admin'">
|
||||
<p class="text-[10px] text-amber-500 font-bold mt-1 px-1 flex items-center gap-1.5">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /></svg>
|
||||
{{ __('System role name cannot be modified.') }}
|
||||
{{ __('The Super Admin role name cannot be modified.') }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
@if(auth()->user()->isSystemAdmin())
|
||||
<div class="space-y-4">
|
||||
<label class="text-xs font-black text-slate-400 uppercase tracking-widest pl-1">{{ __('Role Type') }}</label>
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="flex items-center gap-3 p-3 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-800/50 cursor-pointer transition-all border border-slate-200 dark:border-slate-800 group has-[:checked]:border-cyan-500/50 has-[:checked]:bg-cyan-500/5">
|
||||
<input type="radio" name="is_system" value="1" x-model="isSystem" class="w-4 h-4 text-cyan-500 bg-transparent border-slate-300 focus:ring-cyan-500" :disabled="isEdit && roleName === 'super-admin'">
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-200 group-hover:text-cyan-600 dark:group-hover:text-cyan-400">{{ __('System Level') }}</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-3 p-3 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-800/50 cursor-pointer transition-all border border-slate-200 dark:border-slate-800 group has-[:checked]:border-cyan-500/50 has-[:checked]:bg-cyan-500/5">
|
||||
<input type="radio" name="is_system" value="0" x-model="isSystem" class="w-4 h-4 text-cyan-500 bg-transparent border-slate-300 focus:ring-cyan-500" :disabled="isEdit && roleName === 'super-admin'">
|
||||
<span class="text-sm font-bold text-slate-700 dark:text-slate-200 group-hover:text-cyan-600 dark:group-hover:text-cyan-400">{{ __('Company Level') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Right: Permissions -->
|
||||
@@ -188,16 +211,17 @@
|
||||
'line' => 'M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z',
|
||||
'reservation' => 'M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z',
|
||||
'special-permission' => 'M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z',
|
||||
'companies' => 'M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4',
|
||||
'accounts' => 'M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z',
|
||||
'roles' => 'M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z',
|
||||
'basic-settings' => 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z',
|
||||
'permissions' => 'M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z',
|
||||
];
|
||||
@endphp
|
||||
@foreach($all_permissions->get('menu', []) as $permission)
|
||||
@php
|
||||
$pure_name = str_replace('menu.', '', $permission->name);
|
||||
$is_restricted = in_array($permission->name, ['menu.basic-settings', 'menu.permissions']);
|
||||
@endphp
|
||||
<label class="flex items-center gap-3 p-3 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-800/50 cursor-pointer transition-all border border-transparent hover:border-slate-200 dark:hover:border-slate-700 group">
|
||||
<label class="flex items-center gap-3 p-3 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-800/50 cursor-pointer transition-all border border-transparent hover:border-slate-200 dark:hover:border-slate-700 group"
|
||||
@if($is_restricted) x-show="isSystem == '1'" x-transition @endif>
|
||||
<div class="relative flex items-center">
|
||||
<input type="checkbox" name="permissions[]" value="{{ $permission->name }}"
|
||||
x-model="rolePermissions"
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
'admin.reservation' => __('Reservation System'),
|
||||
'admin.special-permission' => __('Special Permission'),
|
||||
'admin.permission' => __('Permission Settings'),
|
||||
'admin.basic-settings' => __('Basic Settings'),
|
||||
];
|
||||
|
||||
// 1. 找出所屬大模組
|
||||
@@ -62,7 +63,8 @@
|
||||
$midLabel = match($midSegment) {
|
||||
'companies' => __('Customer Management'),
|
||||
'members' => __('Member List'),
|
||||
'machines' => __('Machine List'),
|
||||
'machines' => __('Machine Settings'),
|
||||
'payment-configs' => __('Customer Payment Config'),
|
||||
'warehouses' => __('Warehouse List'),
|
||||
'sales' => __('Sales Records'),
|
||||
default => null,
|
||||
@@ -152,7 +154,6 @@
|
||||
if ($pageLabel) {
|
||||
$links[] = [
|
||||
'label' => $pageLabel,
|
||||
'url' => route($routeName),
|
||||
'active' => true
|
||||
];
|
||||
}
|
||||
@@ -189,7 +190,7 @@
|
||||
@endif
|
||||
|
||||
@if(!$loop->last)
|
||||
<svg class="flex-shrink-0 mx-3 overflow-visible h-2.5 w-2.5 text-slate-400 dark:text-slate-600" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg class="flex-shrink-0 mx-3 overflow-visible h-2.5 w-2.5 text-slate-500 dark:text-slate-400" width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 1L10.6869 7.16086C10.8637 7.35239 10.8637 7.64761 10.6869 7.83914L5 14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
@endif
|
||||
|
||||
49
resources/views/components/luxury-time-input.blade.php
Normal file
49
resources/views/components/luxury-time-input.blade.php
Normal file
@@ -0,0 +1,49 @@
|
||||
@props(['name', 'value' => '', 'label' => ''])
|
||||
|
||||
<div x-data="{
|
||||
time: '{{ $value ? \Carbon\Carbon::parse($value)->format('H:i:s') : '' }}',
|
||||
formatInput(e) {
|
||||
let cursor = e.target.selectionStart;
|
||||
let originalLen = e.target.value.length;
|
||||
let val = e.target.value.replace(/\D/g, '');
|
||||
|
||||
if (val.length > 6) val = val.slice(0, 6);
|
||||
|
||||
let formatted = '';
|
||||
if (val.length > 0) {
|
||||
formatted = val.slice(0, 2);
|
||||
if (val.length > 2) {
|
||||
formatted += ':' + val.slice(2, 4);
|
||||
if (val.length > 4) {
|
||||
formatted += ':' + val.slice(4, 6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.time = formatted;
|
||||
|
||||
// Minor delay to fix cursor position if needed,
|
||||
// though for time strings move-to-end is often acceptable.
|
||||
this.$nextTick(() => {
|
||||
let diff = formatted.length - originalLen;
|
||||
let newPos = cursor + diff;
|
||||
// e.target.setSelectionRange(newPos, newPos); // Optional cursor fix
|
||||
});
|
||||
}
|
||||
}" class="relative">
|
||||
<input
|
||||
type="text"
|
||||
name="{{ $name }}"
|
||||
:value="time"
|
||||
@input="formatInput"
|
||||
maxlength="8"
|
||||
placeholder="HH:mm:ss"
|
||||
class="luxury-input w-full pr-12 font-mono tracking-wider"
|
||||
autocomplete="off"
|
||||
>
|
||||
<div class="absolute right-4 top-1/2 -translate-y-1/2 pointer-events-none text-slate-400 dark:text-slate-500">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
@@ -145,9 +145,9 @@
|
||||
class="absolute right-0 top-full mt-2 min-w-[15rem] bg-white shadow-xl rounded-2xl p-2 dark:bg-gray-800 dark:border dark:border-gray-700 z-50 border border-slate-100"
|
||||
x-cloak>
|
||||
<div class="py-3 px-5 -m-2 bg-slate-50 rounded-t-2xl dark:bg-slate-900/50 border-b border-slate-100 dark:border-slate-700">
|
||||
<p class="text-[10px] font-black uppercase tracking-widest text-slate-400 dark:text-slate-500">{{ __('Signed in as') }}</p>
|
||||
<p class="text-[11px] font-bold uppercase tracking-widest text-slate-500 dark:text-slate-400">{{ __('Signed in as') }}</p>
|
||||
<p class="text-sm font-bold text-slate-700 dark:text-slate-200 truncate">{{ Auth::user()->name }}</p>
|
||||
<p class="text-[10px] font-medium text-slate-400 truncate">{{ Auth::user()->email }}</p>
|
||||
<p class="text-xs font-medium text-slate-500 truncate">{{ Auth::user()->email }}</p>
|
||||
</div>
|
||||
<div class="mt-2 py-2">
|
||||
<a class="flex items-center gap-x-3.5 py-2.5 px-3 rounded-xl text-sm font-bold text-slate-700 hover:bg-gray-100 focus:ring-2 focus:ring-blue-500 dark:text-slate-300 dark:hover:bg-gray-700 dark:hover:text-white transition-colors" href="{{ route('profile.edit') }}">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{{-- 1. 儀表板 --}}
|
||||
<li>
|
||||
<a class="luxury-nav-item {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}" href="{{ route('admin.dashboard') }}">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.dashboard') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/></svg>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.dashboard') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/></svg>
|
||||
{{ __('Dashboard') }}
|
||||
</a>
|
||||
</li>
|
||||
@@ -10,14 +10,14 @@
|
||||
<li x-data="{ open: localStorage.getItem('menu_profile') === 'true' || {{ request()->routeIs('profile.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_profile', open)"
|
||||
class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('profile.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('profile.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||||
{{ __('Profile Settings') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li>
|
||||
<a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('profile.edit') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('profile.edit') }}">
|
||||
<a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('profile.edit') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('profile.edit') }}">
|
||||
{{ __('Profile') }}
|
||||
</a>
|
||||
</li>
|
||||
@@ -29,17 +29,17 @@
|
||||
{{-- 3. 會員管理 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_members') === 'true' || {{ request()->routeIs('admin.members.*') || request()->routeIs('admin.membership-tiers.*') || request()->routeIs('admin.deposit-bonus-rules.*') || request()->routeIs('admin.point-rules.*') || request()->routeIs('admin.gift-definitions.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_members', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.members.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.members.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
|
||||
{{ __('Member Management') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.members.index') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.members.index') }}">{{ __('Member List') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.membership-tiers.*') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.membership-tiers.index') }}">{{ __('Membership Tiers') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.deposit-bonus-rules.*') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.deposit-bonus-rules.index') }}">{{ __('Deposit Bonus') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.point-rules.*') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.point-rules.index') }}">{{ __('Point Rules') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.gift-definitions.*') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.gift-definitions.index') }}">{{ __('Gift Definitions') }}</a></li>
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.members.index') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.members.index') }}">{{ __('Member List') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.membership-tiers.*') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.membership-tiers.index') }}">{{ __('Membership Tiers') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.deposit-bonus-rules.*') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.deposit-bonus-rules.index') }}">{{ __('Deposit Bonus') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.point-rules.*') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.point-rules.index') }}">{{ __('Point Rules') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.gift-definitions.*') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.gift-definitions.index') }}">{{ __('Gift Definitions') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@@ -50,18 +50,18 @@
|
||||
{{-- 4. 機台管理 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_machines') === 'true' || {{ request()->routeIs('admin.machines.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_machines', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.machines.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 01-2 2v4a2 2 0 012 2h14a2 2 0 012-2v-4a2 2 0 01-2-2m-2-4h.01M17 16h.01" /></svg>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.machines.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 01-2 2v4a2 2 0 012 2h14a2 2 0 012-2v-4a2 2 0 01-2-2m-2-4h.01M17 16h.01" /></svg>
|
||||
{{ __('Machine Management') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.logs') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.logs') }}">{{ __('Machine Logs') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.index') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.index') }}">{{ __('Machine List') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.permissions') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.permissions') }}">{{ __('Machine Permissions') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.utilization') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.utilization') }}">{{ __('Utilization Rate') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.expiry') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.expiry') }}">{{ __('Expiry Management') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.maintenance') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.maintenance') }}">{{ __('Maintenance Records') }}</a></li>
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.logs') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.logs') }}">{{ __('Machine Logs') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.index') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.index') }}">{{ __('Machine List') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.permissions') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.permissions') }}">{{ __('Machine Permissions') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.utilization') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.utilization') }}">{{ __('Utilization Rate') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.expiry') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.expiry') }}">{{ __('Expiry Management') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.machines.maintenance') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.machines.maintenance') }}">{{ __('Maintenance Records') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@@ -72,17 +72,17 @@
|
||||
{{-- 5. APP管理 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_app') === 'true' || {{ request()->routeIs('admin.app.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_app', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.app.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" /></svg>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.app.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" /></svg>
|
||||
{{ __('APP Management') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.app.ui-elements') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.app.ui-elements') }}">{{ __('UI Elements') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.app.helper') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.app.helper') }}">{{ __('Helper') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.app.questionnaire') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.app.questionnaire') }}">{{ __('Questionnaire') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.app.games') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.app.games') }}">{{ __('Games') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.app.timer') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.app.timer') }}">{{ __('Timer') }}</a></li>
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.app.ui-elements') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.app.ui-elements') }}">{{ __('UI Elements') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.app.helper') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.app.helper') }}">{{ __('Helper') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.app.questionnaire') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.app.questionnaire') }}">{{ __('Questionnaire') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.app.games') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.app.games') }}">{{ __('Games') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.app.timer') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.app.timer') }}">{{ __('Timer') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@@ -93,22 +93,22 @@
|
||||
{{-- 6. 倉庫管理 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_warehouses') === 'true' || {{ request()->routeIs('admin.warehouses.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_warehouses', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.warehouses.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" /></svg>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.warehouses.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" /></svg>
|
||||
{{ __('Warehouse Management') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.index') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.index') }}">{{ __('Warehouse List (All)') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.personal') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.personal') }}">{{ __('Warehouse List (Individual)') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.stock-management') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.stock-management') }}">{{ __('Stock Management') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.transfers') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.transfers') }}">{{ __('Transfers') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.purchases') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.purchases') }}">{{ __('Purchases') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.replenishments') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.replenishments') }}">{{ __('Replenishments') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.replenishment-records') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.replenishment-records') }}">{{ __('Replenishment Records') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.machine-stock') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.machine-stock') }}">{{ __('Machine Stock') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.staff-stock') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.staff-stock') }}">{{ __('Staff Stock') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.returns') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.returns') }}">{{ __('Returns') }}</a></li>
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.index') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.index') }}">{{ __('Warehouse List (All)') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.personal') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.personal') }}">{{ __('Warehouse List (Individual)') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.stock-management') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.stock-management') }}">{{ __('Stock Management') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.transfers') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.transfers') }}">{{ __('Transfers') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.purchases') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.purchases') }}">{{ __('Purchases') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.replenishments') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.replenishments') }}">{{ __('Replenishments') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.replenishment-records') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.replenishment-records') }}">{{ __('Replenishment Records') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.machine-stock') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.machine-stock') }}">{{ __('Machine Stock') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.staff-stock') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.staff-stock') }}">{{ __('Staff Stock') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.warehouses.returns') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.warehouses.returns') }}">{{ __('Returns') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@@ -119,18 +119,18 @@
|
||||
{{-- 7. 銷售管理 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_sales') === 'true' || {{ request()->routeIs('admin.sales.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_sales', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.sales.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.sales.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
{{ __('Sales Management') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.sales.index') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.sales.index') }}">{{ __('Sales Records') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.sales.pickup-codes') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.sales.pickup-codes') }}">{{ __('Pickup Codes') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.sales.orders') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.sales.orders') }}">{{ __('Orders') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.sales.promotions') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.sales.promotions') }}">{{ __('Promotions') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.sales.pass-codes') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.sales.pass-codes') }}">{{ __('Pass Codes') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.sales.store-gifts') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.sales.store-gifts') }}">{{ __('Store Gifts') }}</a></li>
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.sales.index') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.sales.index') }}">{{ __('Sales Records') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.sales.pickup-codes') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.sales.pickup-codes') }}">{{ __('Pickup Codes') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.sales.orders') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.sales.orders') }}">{{ __('Orders') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.sales.promotions') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.sales.promotions') }}">{{ __('Promotions') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.sales.pass-codes') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.sales.pass-codes') }}">{{ __('Pass Codes') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.sales.store-gifts') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.sales.store-gifts') }}">{{ __('Store Gifts') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@@ -141,16 +141,16 @@
|
||||
{{-- 8. 分析管理 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_analysis') === 'true' || {{ request()->routeIs('admin.analysis.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_analysis', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.analysis.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4M3 4h18M4 4h16v12a1 1 0 01-1 1H5a1 1 0 01-1-1V4z" /></svg>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.analysis.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4M3 4h18M4 4h16v12a1 1 0 01-1 1H5a1 1 0 01-1-1V4z" /></svg>
|
||||
{{ __('Analysis Management') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.analysis.change-stock') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.analysis.change-stock') }}">{{ __('Change Stock') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.analysis.machine-reports') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.analysis.machine-reports') }}">{{ __('Machine Reports') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.analysis.product-reports') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.analysis.product-reports') }}">{{ __('Product Reports') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.analysis.survey-analysis') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.analysis.survey-analysis') }}">{{ __('Survey Analysis') }}</a></li>
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.analysis.change-stock') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.analysis.change-stock') }}">{{ __('Change Stock') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.analysis.machine-reports') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.analysis.machine-reports') }}">{{ __('Machine Reports') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.analysis.product-reports') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.analysis.product-reports') }}">{{ __('Product Reports') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.analysis.survey-analysis') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.analysis.survey-analysis') }}">{{ __('Survey Analysis') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@@ -161,15 +161,15 @@
|
||||
{{-- 9. 稽核管理 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_audit') === 'true' || {{ request()->routeIs('admin.audit.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_audit', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.audit.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" 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>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.audit.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" 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>
|
||||
{{ __('Audit Management') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.audit.purchases') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.audit.purchases') }}">{{ __('Purchase Audit') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.audit.transfers') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.audit.transfers') }}">{{ __('Transfer Audit') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.audit.replenishments') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.audit.replenishments') }}">{{ __('Replenishment Audit') }}</a></li>
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.audit.purchases') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.audit.purchases') }}">{{ __('Purchase Audit') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.audit.transfers') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.audit.transfers') }}">{{ __('Transfer Audit') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.audit.replenishments') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.audit.replenishments') }}">{{ __('Replenishment Audit') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@@ -180,19 +180,19 @@
|
||||
{{-- 10. 資料設定 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_data_config') === 'true' || {{ request()->routeIs('admin.data-config.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_data_config', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.data-config.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.data-config.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
|
||||
{{ __('Data Configuration') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.products') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.products') }}">{{ __('Product Management') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.advertisements') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.advertisements') }}">{{ __('Advertisement Management') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.admin-products') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.admin-products') }}">{{ __('Product Status') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.sub-accounts') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.sub-accounts') }}">{{ __('Sub Accounts') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.sub-account-roles') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.sub-account-roles') }}">{{ __('Sub Account Roles') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.points') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.points') }}">{{ __('Point Settings') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.badges') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.badges') }}">{{ __('Badge Settings') }}</a></li>
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.products') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.products') }}">{{ __('Product Management') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.advertisements') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.advertisements') }}">{{ __('Advertisement Management') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.admin-products') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.admin-products') }}">{{ __('Product Status') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.sub-accounts') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.sub-accounts') }}">{{ __('Sub Accounts') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.sub-account-roles') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.sub-account-roles') }}">{{ __('Sub Account Roles') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.points') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.points') }}">{{ __('Point Settings') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.data-config.badges') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.data-config.badges') }}">{{ __('Badge Settings') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@@ -204,19 +204,19 @@
|
||||
{{-- 11. 遠端管理 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_remote') === 'true' || {{ request()->routeIs('admin.remote.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_remote', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.remote.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5.636 18.364a9 9 0 010-12.728m12.728 0a9 9 0 010 12.728m-9.9-2.829a5 5 0 010-7.07m7.072 0a5 5 0 010 7.07M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.remote.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5.636 18.364a9 9 0 010-12.728m12.728 0a9 9 0 010 12.728m-9.9-2.829a5 5 0 010-7.07m7.072 0a5 5 0 010 7.07M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
{{ __('Remote Management') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.stock') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.stock') }}">{{ __('Machine Stock') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.restart') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.restart') }}">{{ __('Machine Restart') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.restart-card-reader') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.restart-card-reader') }}">{{ __('Card Reader Restart') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.checkout') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.checkout') }}">{{ __('Remote Checkout') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.lock') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.lock') }}">{{ __('Remote Lock') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.change') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.change') }}">{{ __('Remote Change') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.dispense') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.dispense') }}">{{ __('Remote Dispense') }}</a></li>
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.stock') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.stock') }}">{{ __('Machine Stock') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.restart') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.restart') }}">{{ __('Machine Restart') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.restart-card-reader') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.restart-card-reader') }}">{{ __('Card Reader Restart') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.checkout') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.checkout') }}">{{ __('Remote Checkout') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.lock') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.lock') }}">{{ __('Remote Lock') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.change') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.change') }}">{{ __('Remote Change') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.remote.dispense') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.remote.dispense') }}">{{ __('Remote Dispense') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@@ -227,18 +227,18 @@
|
||||
{{-- 12. Line管理 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_line') === 'true' || {{ request()->routeIs('admin.line.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_line', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.line.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /></svg>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.line.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" /></svg>
|
||||
{{ __('Line Management') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.line.official-account') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.line.official-account') }}">{{ __('Official Account') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.line.members') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.line.members') }}">{{ __('Line Members') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.line.machines') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.line.machines') }}">{{ __('Line Machines') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.line.products') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.line.products') }}">{{ __('Line Products') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.line.orders') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.line.orders') }}">{{ __('Line Orders') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.line.coupons') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.line.coupons') }}">{{ __('Line Coupons') }}</a></li>
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.line.official-account') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.line.official-account') }}">{{ __('Official Account') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.line.members') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.line.members') }}">{{ __('Line Members') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.line.machines') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.line.machines') }}">{{ __('Line Machines') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.line.products') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.line.products') }}">{{ __('Line Products') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.line.orders') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.line.orders') }}">{{ __('Line Orders') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.line.coupons') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.line.coupons') }}">{{ __('Line Coupons') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@@ -248,19 +248,19 @@
|
||||
{{-- 13. 預約系統 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_reservation') === 'true' || {{ request()->routeIs('admin.reservation.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_reservation', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.reservation.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.reservation.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" /></svg>
|
||||
{{ __('Reservation System') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.reservations') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.reservations') }}">{{ __('Reservation List') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.members') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.members') }}">{{ __('Reservation Members') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.stores') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.stores') }}">{{ __('Stores Management') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.time-slots') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.time-slots') }}">{{ __('Time Slots') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.venues') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.venues') }}">{{ __('Venues Management') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.coupons') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.coupons') }}">{{ __('Reservation Coupons') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.orders') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.orders') }}">{{ __('Reservation Orders') }}</a></li>
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.reservations') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.reservations') }}">{{ __('Reservation List') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.members') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.members') }}">{{ __('Reservation Members') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.stores') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.stores') }}">{{ __('Stores Management') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.time-slots') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.time-slots') }}">{{ __('Time Slots') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.venues') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.venues') }}">{{ __('Venues Management') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.coupons') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.coupons') }}">{{ __('Reservation Coupons') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.reservation.orders') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.reservation.orders') }}">{{ __('Reservation Orders') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@@ -270,50 +270,71 @@
|
||||
{{-- 14. 特殊權限 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_special_permission') === 'true' || {{ request()->routeIs('admin.special-permission.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_special_permission', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.special-permission.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.special-permission.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" /></svg>
|
||||
{{ __('Special Permission') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.special-permission.clear-stock') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.special-permission.clear-stock') }}">{{ __('Clear Stock') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.special-permission.apk-versions') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.special-permission.apk-versions') }}">{{ __('APK Versions') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.special-permission.discord-notifications') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.special-permission.discord-notifications') }}">{{ __('Discord Notifications') }}</a></li>
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.special-permission.clear-stock') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.special-permission.clear-stock') }}">{{ __('Clear Stock') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.special-permission.apk-versions') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.special-permission.apk-versions') }}">{{ __('APK Versions') }}</a></li>
|
||||
<li><a class="flex items-center gap-x-3.5 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.special-permission.discord-notifications') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.special-permission.discord-notifications') }}">{{ __('Discord Notifications') }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@endcan
|
||||
|
||||
@canany(['menu.companies', 'menu.accounts', 'menu.roles'])
|
||||
{{-- 15. 權限設定 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_permissions') === 'true' || {{ request()->routeIs('admin.permission.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_permissions', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.permission.*') ? 'text-cyan-500' : 'text-slate-400 group-hover:text-cyan-500 dark:text-slate-400 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" /></svg>
|
||||
{{ __('Permission Settings') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-400 dark:text-slate-500" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
@if(auth()->user()->isSystemAdmin())
|
||||
@can('menu.basic-settings')
|
||||
{{-- 14.5. 基本設定 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_basic_settings') === 'true' || {{ request()->routeIs('admin.basic-settings.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_basic_settings', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.basic-settings.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
|
||||
{{ __('Basic Settings') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu">
|
||||
@can('menu.companies')
|
||||
<li><a class="flex items-center gap-x-3 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.permission.companies.*') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.permission.companies.index') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 shrink-0 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" /></svg>
|
||||
{{ __('Customer Management') }}
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.basic-settings.machines.*') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.basic-settings.machines.index') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 shrink-0 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" /></svg>
|
||||
{{ __('Machine Settings') }}
|
||||
</a></li>
|
||||
@endcan
|
||||
@can('menu.accounts')
|
||||
<li><a class="flex items-center gap-x-3 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.permission.accounts') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.permission.accounts') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 shrink-0 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
{{ __('Account Management') }}
|
||||
<li><a class="flex items-center gap-x-3 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.basic-settings.payment-configs.*') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.basic-settings.payment-configs.index') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 shrink-0 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" 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>
|
||||
{{ __('Customer Payment Config') }}
|
||||
</a></li>
|
||||
@endcan
|
||||
@can('menu.roles')
|
||||
<li><a class="flex items-center gap-x-3 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.permission.roles') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.permission.roles') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 shrink-0 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
|
||||
{{ __('Role Settings') }}
|
||||
</a></li>
|
||||
@endcan
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@endcanany
|
||||
@endcan
|
||||
@endif
|
||||
|
||||
@if(auth()->user()->isSystemAdmin())
|
||||
@can('menu.permissions')
|
||||
{{-- 15. 權限設定 --}}
|
||||
<li x-data="{ open: localStorage.getItem('menu_permissions') === 'true' || {{ request()->routeIs('admin.permission.*') ? 'true' : 'false' }} }">
|
||||
<button type="button" @click="open = !open; localStorage.setItem('menu_permissions', open)" class="luxury-nav-item w-full text-start group">
|
||||
<svg class="w-4 h-4 shrink-0 transition-colors {{ request()->routeIs('admin.permission.*') ? 'text-cyan-500' : 'text-slate-600 group-hover:text-cyan-500 dark:text-slate-300 dark:group-hover:text-cyan-400' }}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" /></svg>
|
||||
{{ __('Permission Settings') }}
|
||||
<svg class="ms-auto w-4 h-4 transition-transform duration-300 text-slate-600 dark:text-slate-300" :class="{ 'rotate-180': open, 'rotate-0': !open }" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="m6 9 6 6 6-6"/></svg>
|
||||
</button>
|
||||
<div x-show="open" x-collapse>
|
||||
<ul class="luxury-submenu" data-sidebar-sub>
|
||||
<li><a class="flex items-center gap-x-3 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.permission.companies.*') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.permission.companies.index') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 shrink-0 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" /></svg>
|
||||
{{ __('Customer Management') }}
|
||||
</a></li>
|
||||
<li><a class="flex items-center gap-x-3 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.permission.accounts') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.permission.accounts') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 shrink-0 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
{{ __('Account Management') }}
|
||||
</a></li>
|
||||
<li><a class="flex items-center gap-x-3 py-2 px-2.5 text-sm transition-colors rounded-lg {{ request()->routeIs('admin.permission.roles') ? 'text-slate-900 dark:text-white bg-slate-100 dark:bg-white/5' : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white' }}" href="{{ route('admin.permission.roles') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4 shrink-0 transition-colors" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" /></svg>
|
||||
{{ __('Role Permissions') }}
|
||||
</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
@endcan
|
||||
@endif
|
||||
|
||||
|
||||
@@ -108,8 +108,14 @@ Route::middleware(['auth', 'verified', 'tenant.access'])->prefix('admin')->name(
|
||||
Route::get('/products', [App\Http\Controllers\Admin\DataConfigController::class , 'products'])->name('products');
|
||||
Route::get('/advertisements', [App\Http\Controllers\Admin\DataConfigController::class , 'advertisements'])->name('advertisements');
|
||||
Route::get('/admin-products', [App\Http\Controllers\Admin\DataConfigController::class , 'adminProducts'])->name('admin-products');
|
||||
Route::get('/sub-accounts', [App\Http\Controllers\Admin\DataConfigController::class , 'subAccounts'])->name('sub-accounts');
|
||||
Route::get('/sub-account-roles', [App\Http\Controllers\Admin\DataConfigController::class , 'subAccountRoles'])->name('sub-account-roles');
|
||||
Route::get('/sub-accounts', [App\Http\Controllers\Admin\PermissionController::class , 'accounts'])->name('sub-accounts');
|
||||
Route::post('/sub-accounts', [App\Http\Controllers\Admin\PermissionController::class , 'storeAccount'])->name('sub-accounts.store');
|
||||
Route::put('/sub-accounts/{id}', [App\Http\Controllers\Admin\PermissionController::class , 'updateAccount'])->name('sub-accounts.update');
|
||||
Route::delete('/sub-accounts/{id}', [App\Http\Controllers\Admin\PermissionController::class , 'destroyAccount'])->name('sub-accounts.destroy');
|
||||
Route::get('/sub-account-roles', [App\Http\Controllers\Admin\PermissionController::class , 'roles'])->name('sub-account-roles');
|
||||
Route::post('/sub-account-roles', [App\Http\Controllers\Admin\PermissionController::class , 'storeRole'])->name('sub-account-roles.store');
|
||||
Route::put('/sub-account-roles/{id}', [App\Http\Controllers\Admin\PermissionController::class , 'updateRole'])->name('sub-account-roles.update');
|
||||
Route::delete('/sub-account-roles/{id}', [App\Http\Controllers\Admin\PermissionController::class , 'destroyRole'])->name('sub-account-roles.destroy');
|
||||
Route::get('/points', [App\Http\Controllers\Admin\DataConfigController::class , 'points'])->name('points');
|
||||
Route::get('/badges', [App\Http\Controllers\Admin\DataConfigController::class , 'badges'])->name('badges');
|
||||
}
|
||||
@@ -158,7 +164,21 @@ Route::middleware(['auth', 'verified', 'tenant.access'])->prefix('admin')->name(
|
||||
}
|
||||
);
|
||||
|
||||
// 14. 權限設定
|
||||
// 14. 基本設定
|
||||
Route::prefix('basic-settings')->name('basic-settings.')->group(function () {
|
||||
// 機台設定
|
||||
Route::prefix('machines')->name('machines.')->group(function () {
|
||||
Route::get('/', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'index'])->name('index');
|
||||
Route::get('/{machine}/edit', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'edit'])->name('edit');
|
||||
Route::put('/{machine}', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'update'])->name('update');
|
||||
Route::post('/', [App\Http\Controllers\Admin\BasicSettings\MachineSettingController::class, 'store'])->name('store');
|
||||
});
|
||||
|
||||
// 客戶金流設定
|
||||
Route::resource('payment-configs', App\Http\Controllers\Admin\BasicSettings\PaymentConfigController::class)->except(['show']);
|
||||
});
|
||||
|
||||
// 15. 權限設定
|
||||
Route::prefix('permission')->name('permission.')->group(function () {
|
||||
Route::resource('companies', App\Http\Controllers\Admin\CompanyController::class)->except(['show', 'create', 'edit']);
|
||||
Route::get('/accounts', [App\Http\Controllers\Admin\PermissionController::class , 'accounts'])->name('accounts');
|
||||
|
||||
Reference in New Issue
Block a user