[STYLE] 優化商品管理與客戶管理頁面之間距與按鈕樣式
1. 調整商品管理與客戶管理主容器間距為 space-y-2,縮小標題與頁籤之空隙。 2. 統一頁籤 (Tabs) 與內容區域之頂部間距為 mt-6,標準化視覺節奏。 3. 修正商品與分類管理頁面之「新增」按鈕顏色不一致問題,統一使用奢華藍漸層。 4. 在各管理頁面主要按鈕加入 whitespace-nowrap,確保文字在窄屏下不換行。 5. 優化商品管理 RWD 佈局,使手機與平板裝置下之篩選列與標題區域對齊基準模組。
This commit is contained in:
parent
10bbfef2a4
commit
9686f37fbd
@ -106,11 +106,19 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
|||||||
|
|
||||||
```html
|
```html
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="space-y-6">
|
<div class="space-y-4 pb-20">
|
||||||
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
|
<!-- Breadcrumbs -->
|
||||||
|
<x-breadcrumbs class="mb-4 hidden lg:flex" />
|
||||||
|
|
||||||
|
<!-- Top Header -->
|
||||||
|
<div class="flex items-center justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Title') }}</h1>
|
<h1 class="text-2xl sm:text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display transition-all duration-300">
|
||||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Subtitle') }}</p>
|
{{ __('Title') }}
|
||||||
|
</h1>
|
||||||
|
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">
|
||||||
|
{{ __('Subtitle') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<button class="btn-luxury-primary">
|
<button class="btn-luxury-primary">
|
||||||
@ -121,32 +129,7 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
<div class="luxury-card rounded-3xl p-8 animate-luxury-in">
|
||||||
<div class="flex items-center justify-between mb-10">
|
<!-- Content Container -->
|
||||||
<form class="relative group">
|
|
||||||
<input type="text" class="luxury-input pl-12 pr-6 w-64" placeholder="{{ __('Search...') }}">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<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">{{ __('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 text-right">{{ __('Action') }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
|
||||||
<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">Example Name</td>
|
|
||||||
<td class="px-6 py-6 text-right"> </td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-8 border-t border-slate-100/50 dark:border-slate-800/50 pt-6">
|
|
||||||
{{ $items->links('vendor.pagination.luxury') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
@ -156,7 +139,7 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
|||||||
1. **移除重複內距**: 根容器 `div` 應**禁止**使用 `p-6` 或 `p-10`,因為佈局基底已提供基礎間距。
|
1. **移除重複內距**: 根容器 `div` 應**禁止**使用 `p-6` 或 `p-10`,因為佈局基底已提供基礎間距。
|
||||||
2. **區塊間隙**: 建議使用 `space-y-6` 或 `space-y-8` 以獲得最佳空氣感。但在「高密度資料管理」或使用者有特殊緊湊需求的情境下,容許縮減至 **`space-y-2`**。
|
2. **區塊間隙**: 建議使用 `space-y-6` 或 `space-y-8` 以獲得最佳空氣感。但在「高密度資料管理」或使用者有特殊緊湊需求的情境下,容許縮減至 **`space-y-2`**。
|
||||||
3. **主容器樣式**: 強制對齊為 `luxury-card rounded-3xl p-8`。
|
3. **主容器樣式**: 強制對齊為 `luxury-card rounded-3xl p-8`。
|
||||||
3. **標題排版**:
|
4. **標題排版**:
|
||||||
- 主標題需應用 `font-display` (Outfit)。
|
- 主標題需應用 `font-display` (Outfit)。
|
||||||
- 描述文字需應用 `uppercase tracking-widest font-bold` 以呈現高級設計感。
|
- 描述文字需應用 `uppercase tracking-widest font-bold` 以呈現高級設計感。
|
||||||
|
|
||||||
@ -172,13 +155,28 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
|||||||
- 聚焦時帶有青色 (`Cyan`) 發光邊框。
|
- 聚焦時帶有青色 (`Cyan`) 發光邊框。
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<input type="text" class="luxury-input" placeholder="請輸入內容">
|
<input type="text" class="luxury-input py-2.5 px-5 w-72" placeholder="請輸入內容">
|
||||||
|
|
||||||
<select class="luxury-select">
|
<select class="luxury-select py-2.5">
|
||||||
<option value="1">啟用</option>
|
<option value="1">啟用</option>
|
||||||
<option value="0">禁用</option>
|
<option value="0">禁用</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
### 搜尋與重製按鈕組件 (Filter Actions)
|
||||||
|
- **搜尋按鈕 (Search)**: `p-2.5 rounded-xl bg-cyan-500 text-white shadow-lg shadow-cyan-500/25 transition-all active:scale-95`
|
||||||
|
- **重製按鈕 (Reset)**: `p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 hover:bg-slate-200 transition-all active:scale-95`
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button type="submit" class="p-2.5 rounded-xl bg-cyan-500 text-white shadow-lg shadow-cyan-500/25 active:scale-95">
|
||||||
|
<i class="lucide-search size-5"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 active:scale-95">
|
||||||
|
<i class="lucide-rotate-ccw size-5"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
### 搜尋式下拉選單 (Searchable Select) - 【進階推薦】
|
### 搜尋式下拉選單 (Searchable Select) - 【進階推薦】
|
||||||
- **組件**: `<x-searchable-select />`
|
- **組件**: `<x-searchable-select />`
|
||||||
- **適用場景**: 選項大於 10 筆或具備層級關聯的篩選器(如:公司名稱、機台編號)。
|
- **適用場景**: 選項大於 10 筆或具備層級關聯的篩選器(如:公司名稱、機台編號)。
|
||||||
@ -197,7 +195,6 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
|||||||
onchange="this.form.submit()"
|
onchange="this.form.submit()"
|
||||||
/>
|
/>
|
||||||
```
|
```
|
||||||
```
|
|
||||||
|
|
||||||
## 8. 編輯與詳情頁規範 (Detail & Edit Views)
|
## 8. 編輯與詳情頁規範 (Detail & Edit Views)
|
||||||
|
|
||||||
@ -220,16 +217,19 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
|||||||
### 文字大小與權重 (Typography Hierarchy)
|
### 文字大小與權重 (Typography Hierarchy)
|
||||||
- **表頭 (Table Header)**:
|
- **表頭 (Table Header)**:
|
||||||
- 類別: `text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em]`
|
- 類別: `text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em]`
|
||||||
- 作用: 提供清晰的欄位定義而不奪取資料視覺焦點。具備足夠對比度。
|
- 內距: `px-6 py-4`
|
||||||
- **主標題 (Primary Item)**:
|
- **主標題 (Primary Item)**:
|
||||||
- 類別: `text-base font-extrabold text-slate-800 dark:text-slate-100`
|
- 類別: `text-base font-extrabold text-slate-800 dark:text-slate-100 tracking-tight`
|
||||||
- 範例: 公司名稱、機體名稱。
|
- 範例: 機台名稱、產品標題。
|
||||||
- **次要資訊 (Secondary Info)**:
|
- **次要資訊 (Secondary Info)**:
|
||||||
- 類別: `text-xs font-bold text-slate-500 dark:text-slate-400 tracking-wide`
|
- 類別: `text-xs font-bold text-slate-500 dark:text-slate-400 tracking-wide`
|
||||||
- 範例: 使用者帳號、備註、權限名稱。
|
- 範例: 帳號、技術描述。
|
||||||
|
- **技術/序號資料 (Technical/Mono)**:
|
||||||
|
- 類別: `font-mono text-xs font-black text-slate-400 dark:text-slate-500 tracking-widest`
|
||||||
|
- 範例: SN 碼、版本號、日期時間。
|
||||||
- **狀態標籤 (Status Badge)**:
|
- **狀態標籤 (Status Badge)**:
|
||||||
- 範例: 啟用 (`emerald`)、禁用 (`rose`) / 角色名稱 (`sky`/`indigo`)。
|
- 特性: `px-3 py-1.5 rounded-full text-xs font-black tracking-widest uppercase border`
|
||||||
- 特性: `px-2.5 py-1 rounded-lg text-xs font-bold border tracking-wider`
|
- 範例: 在線 (`emerald`)、離線 (`slate`)、錯誤 (`rose`)。
|
||||||
|
|
||||||
### 空間與反應 (Spacing & Interaction)
|
### 空間與反應 (Spacing & Interaction)
|
||||||
- **單元格內距**: 統一使用 `px-6 py-6`。
|
- **單元格內距**: 統一使用 `px-6 py-6`。
|
||||||
@ -256,9 +256,9 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
|
|||||||
|
|
||||||
為了確保在不同版本的開發環境中(如目前專案使用的 Tailwind CSS v3.1)UI 都能正確呈現,並維持全站操作感一致,必須遵守以下額外規範。
|
為了確保在不同版本的開發環境中(如目前專案使用的 Tailwind CSS v3.1)UI 都能正確呈現,並維持全站操作感一致,必須遵守以下額外規範。
|
||||||
|
|
||||||
### Tailwind CSS 版本兼容性 (v3.1)
|
### Tailwind CSS 版本兼容性 (v3.4+)
|
||||||
- **禁止使用 `size-` 屬性**: 舊版不支援 `size-4` 等語法,請一律分拆寫作 `w-4 h-4`。
|
- **推薦使用 `size-` 屬性**: 支援 `size-4` (等同於 `w-4 h-4`) 等語法,推薦用於簡化圖示尺寸定義。
|
||||||
- **避免非標準間距**: 避免使用 `4.5` (`18px`) 等任意值,優先使用標準等級如 `4` (`16px`) 或 `5` (`20px`)。
|
- **標準間距**: 優先使用標準等級如 `4` (`16px`) 或 `5` (`20px`),確保全站視覺節奏統一。
|
||||||
|
|
||||||
## 11. 字體與技術資訊規範 (Typography & Technical Data)
|
## 11. 字體與技術資訊規範 (Typography & Technical Data)
|
||||||
|
|
||||||
@ -434,4 +434,182 @@ async fetchPage(url) {
|
|||||||
|
|
||||||
### 14.4 統計卡片間距 (Stats Grid Spacing)
|
### 14.4 統計卡片間距 (Stats Grid Spacing)
|
||||||
- **網格間距**: 頂部統計卡片網格 (Grid) 建議使用 **`gap-4`** (16px),避免使用過大的 `gap-6` 或 `gap-8` 導致版面鬆散。
|
- **網格間距**: 頂部統計卡片網格 (Grid) 建議使用 **`gap-4`** (16px),避免使用過大的 `gap-6` 或 `gap-8` 導致版面鬆散。
|
||||||
- **卡片內距**: 統計卡片內部統一使用 `p-6`。
|
- **卡片內距**: 統計卡片內部統一使用 `p-6`。
|
||||||
|
|
||||||
|
### 14.5 分頁與清單底部 (Pagination & Footer)
|
||||||
|
- **佈局**: 清單底部應具備 `mt-8` 的間距,並配合 `border-t` 與 `pt-6` 進行視覺隔離。
|
||||||
|
- **內容**: 需正確顯示「顯示總筆數」與「分頁導覽」。
|
||||||
|
|
||||||
|
## 15. 響應式與 RWD 開發規範 (Responsive Guidelines)
|
||||||
|
|
||||||
|
為了確保在行動裝置與平板上具備同樣奢華的操作體驗,必須遵守以下 RWD 準則。
|
||||||
|
|
||||||
|
### 15.1 垂直空間優化 (Vertical Optimization)
|
||||||
|
- **Sticky 區域**: 在手機版中,垂直空間極為珍貴。Sticky Header 應保持極簡,高度建議控制在 60px 以內。
|
||||||
|
- **整合式 Header**: 禁止在手機版使用獨立的 Sidebar Toggle Bar,應將 Toggle 功能整合進頂部導航欄,以釋放至少 50px 的內容高度。
|
||||||
|
|
||||||
|
### 15.2 表格與卡片轉換 (Table Cardification)
|
||||||
|
- **橫向滾動**: 在 `xl` (1280px) 以下,表格應容器化並啟用 `overflow-x-auto`。
|
||||||
|
- **卡片化切換**: 對於核心管理頁面(如機台列表),應實作手機版專用的 `luxury-card` 垂直佈局,並與桌面版 `table` 進行 `hidden / xl:block` 的切換。
|
||||||
|
|
||||||
|
### 15.3 觸控目標與間距 (Touch & Spacing)
|
||||||
|
- **觸控熱區**: 所有按鈕與連結的最小觸控目標應為 **44x44px**。
|
||||||
|
- **間距縮減**: 桌面版的 `p-8` 在手機版應縮減為 `p-4` 或 `p-6`。根容器的 `space-y-4` 在 Sticky 區域可縮減為 `space-y-2`。
|
||||||
|
|
||||||
|
### 15.4 滾動式分頁標籤 (Scrollable Tabs)
|
||||||
|
- **結構**: `flex items-center gap-1 p-1.5 overflow-x-auto whitespace-nowrap custom-scrollbar`
|
||||||
|
- **設計**: 在行動端必須移除 `w-fit` 或改為 `min-w-0 flex-1`,確保在手機螢幕寬度不足時標籤可以橫向捲動而不撐破版面。
|
||||||
|
- **指示器**: 確保捲動區域不會被遮擋,標籤 `px-8` 寬度應適度縮減至 `px-4` 以增加密度。
|
||||||
|
|
||||||
|
### 15.5 搜尋與操作按鈕綁定 (Search & Actions Binding)
|
||||||
|
- **佈局**: `flex flex-wrap items-center gap-3 sm:gap-4`
|
||||||
|
- **規則**:
|
||||||
|
- 搜尋框與重製按鈕應被包裹在同一個 `flex` 容器中。
|
||||||
|
- 行動端搜尋框應設為 `flex-1`。
|
||||||
|
- 搜尋/重製按鈕應設為 `flex-none`,確保它們在同一行顯示,避免操作中斷。
|
||||||
|
|
||||||
|
### 15.6 奢華表格凍結欄位 (Sticky Table Columns)
|
||||||
|
- **場景**: 當資料欄位過多且需要橫向捲動時(如庫存總覽)。
|
||||||
|
- **實作**:
|
||||||
|
- 關鍵資訊(商品名/SN)應使用 `sticky left-0 z-20` 並給予 `bg-white dark:bg-slate-900`。
|
||||||
|
- 當存在多個凍結欄位時(如總計欄位),需精確計算 `left-[Xpx]`。
|
||||||
|
- **視覺區隔**: 凍結欄位的右側應添加微弱陰影 `shadow-[1px_0_0_0_rgba(0,0,0,0.05)]` 或 `border-r`。
|
||||||
|
|
||||||
|
### 15.7 卡片化折疊詳情 (Collapsible Card Detail)
|
||||||
|
- **結構**: 在 `xl:hidden` 的卡片網格中,使用 Alpine.js 的 `x-collapse`。
|
||||||
|
- **模式**:
|
||||||
|
- **Header**: 顯示核心指標(名稱、總量、狀態)。
|
||||||
|
- **Content**: 折疊區域顯示次要資訊(各分店庫存、詳細日期、備註)。
|
||||||
|
- **互動**: 點擊卡片頂部或專屬箭頭切換展開狀態。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. 整合式模組佈局標竿 (Integrated Module Layout Benchmark)
|
||||||
|
|
||||||
|
針對具備「統計、分頁、清單」的複合式功能模組(如:客戶管理、機台總覽),必須嚴格遵守以下層級佈局。
|
||||||
|
|
||||||
|
### 16.1 視覺層級與間距 (Visual Hierarchy & Spacing)
|
||||||
|
|
||||||
|
1. **頁面根容器 (Root)**: 使用 `space-y-2` 或 `space-y-4` (視內容密度)。
|
||||||
|
2. **第一層:頁面標頭 (Page Header)**
|
||||||
|
- **結構**: `flex flex-col md:flex-row md:items-center justify-between gap-4`
|
||||||
|
- **標題 (H1)**: `text-3xl font-black tracking-tight` (Outfit)。
|
||||||
|
- **副標題**: `text-sm font-bold text-slate-500 uppercase tracking-widest mt-1`。
|
||||||
|
- **全域動作**: 放在右側,使用 `.btn-luxury-primary`。
|
||||||
|
3. **第二層:膠囊式頁籤 (Module Tabs)**
|
||||||
|
- **結構**: `flex items-center gap-1 p-1.5 bg-slate-100 dark:bg-slate-900/50 rounded-2xl w-full sm:w-fit border border-slate-200/50 overflow-x-auto whitespace-nowrap`
|
||||||
|
- **按鈕**: `flex-1 sm:flex-none px-8 py-3 rounded-xl text-sm font-black uppercase tracking-widest`。
|
||||||
|
4. **第三層:AJAX 內容容器 (Content Area)**
|
||||||
|
- **容器內距**: 與頁籤之間保持自然間距(使用 `mt-6`)。
|
||||||
|
- **內部結構**: 使用 `luxury-card rounded-3xl p-6 sm:p-8` 作為主要數據承載容器。
|
||||||
|
|
||||||
|
### 16.2 文字大小與排版細節 (Typography Nuances)
|
||||||
|
|
||||||
|
為了在「極簡奢華風」中保持專業感與可讀性,必須遵循以下文字大小規範:
|
||||||
|
|
||||||
|
1. **表格標題 (Table Headers)**:
|
||||||
|
- 大小:`text-xs` (12px)
|
||||||
|
- 字重:`font-bold` 或 `font-black`
|
||||||
|
- 樣式:`uppercase` + `tracking-[0.15em]` (適度放寬字距)
|
||||||
|
- 顏色:`text-slate-500` 或 `text-slate-400` (深色模式)
|
||||||
|
|
||||||
|
2. **主體資料 (Primary Data)**:
|
||||||
|
- 大小:`text-base` (16px) 或 `text-sm` (14px)
|
||||||
|
- 字重:`font-extrabold` 或 `font-black`
|
||||||
|
- 顏色:`text-slate-800` 或 `text-white` (深色模式)
|
||||||
|
- 備註:主要名稱(如商品名、機台名)應使用 `text-base` 以突出重點。
|
||||||
|
|
||||||
|
3. **輔助資料 (Secondary/Metadata)**:
|
||||||
|
- 大小:`text-[10px]` 或 `text-xs`
|
||||||
|
- 字重:`font-black`
|
||||||
|
- 樣式:`uppercase` + `tracking-widest`
|
||||||
|
- 顏色:`text-slate-400` 或 `text-slate-500`
|
||||||
|
- 適用:日期、序號、公司名稱、分類標籤。
|
||||||
|
|
||||||
|
4. **數值資料 (Numerical Data)**:
|
||||||
|
- 價格:`text-emerald-500` + `font-black`
|
||||||
|
- 數量:`text-slate-700` + `font-black`
|
||||||
|
- 比例/百分比:`text-sm` + `font-black`
|
||||||
|
|
||||||
|
5. **日期顯示 (Date Display)**:
|
||||||
|
- 格式:`YYYY/MM/DD` 或 `YYYY-MM-DD HH:mm`
|
||||||
|
- 樣式:`font-mono` (等寬字體有利於對齊)
|
||||||
|
- 大小:`text-xs` 或 `text-[10px]`
|
||||||
|
- 顏色:`text-slate-400`
|
||||||
|
|
||||||
|
### 16.3 RWD 觸控目標 (Touch Targets)
|
||||||
|
|
||||||
|
行動版 (Cards) 的操作區應具備足夠的點擊空間:
|
||||||
|
- **按鈕高度**: 至少 `py-3` 或 `h-11`。
|
||||||
|
- **按鈕寬度**: 至少 `flex-1` (填滿一半寬度)。
|
||||||
|
- **點擊間距**: 按鈕之間保持 `gap-3`。
|
||||||
|
- **陰影反饋**: 點擊時應有 `active:scale-95` 的縮放效果。
|
||||||
|
|
||||||
|
### 16.4 標竿佈局結構示例 (Benchmark Structure)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl font-black text-slate-800 tracking-tight font-display">Module Name</h1>
|
||||||
|
<p class="text-sm font-bold text-slate-500 uppercase tracking-widest mt-1">Sub-headline</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn-luxury-primary shrink-0">Action Button</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabs -->
|
||||||
|
<div class="flex items-center gap-1 p-1.5 bg-slate-100 rounded-2xl w-full sm:w-fit overflow-x-auto whitespace-nowrap">
|
||||||
|
<button class="px-8 py-3 rounded-xl text-sm font-black uppercase tracking-widest bg-white shadow-sm text-cyan-600">Tab A</button>
|
||||||
|
<button class="px-8 py-3 rounded-xl text-sm font-black uppercase tracking-widest text-slate-400">Tab B</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="luxury-card rounded-3xl p-6 sm:p-8 mt-6">
|
||||||
|
<!-- Filter Bar -->
|
||||||
|
<div class="flex flex-wrap items-center gap-4 mb-8">
|
||||||
|
<div class="flex items-center gap-2 flex-1 min-w-[300px]">
|
||||||
|
<div class="relative group flex-1">
|
||||||
|
<span class="absolute inset-y-0 left-0 flex items-center pl-4 text-slate-400 group-focus-within:text-cyan-500"><i class="lucide-search size-4"></i></span>
|
||||||
|
<input type="text" class="luxury-input pl-12 pr-6 w-full" placeholder="Search...">
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 flex-none">
|
||||||
|
<button class="p-2.5 rounded-xl bg-cyan-500 text-white shadow-lg shadow-cyan-500/25">Search</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table (Desktop) -->
|
||||||
|
<div class="hidden xl:block overflow-x-auto">
|
||||||
|
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||||
|
<!-- Table Content -->
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cards (Mobile) -->
|
||||||
|
<div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<!-- Card Content -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div class="mt-8 border-t border-slate-100 pt-6">
|
||||||
|
{{ $items->links('vendor.pagination.luxury') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 16.3 文字大小細節 (Typography Nuances)
|
||||||
|
|
||||||
|
- **主標題 (H1)**: `30px` (`text-3xl`)。
|
||||||
|
- **副標題 (Subtitle)**: `14px` (`text-sm`)。
|
||||||
|
- **統計數值 (Stats Value)**: `30px` (`text-3xl`)。
|
||||||
|
- **表格主內容 (Primary Text)**: `16px` (`text-base`)。
|
||||||
|
- **表格技術資料 (Technical/Dates)**: `12px` (`text-xs`)。
|
||||||
|
- **表格標頭 (Table Header)**: `12px` (`text-xs`)。
|
||||||
|
- **篩選器文字 (Filter Labels)**: `10px` 或 `12px` (`text-xs`)。
|
||||||
|
|
||||||
|
### 16.4 顏色應用建議 (Color Rationale)
|
||||||
|
- **統計卡片標籤**: 統一使用 `slate-500` (輕量感)。
|
||||||
|
- **數值顏色**: 預設 `slate-800`,特殊狀態如「在線」可用 `emerald-500`,「過期/離線」可用 `rose-500`。
|
||||||
|
- **篩選器選中狀態**: 強制使用 `cyan-600` (或 `cyan-500` 帶陰影),建立品牌識別。
|
||||||
@ -1,7 +1,7 @@
|
|||||||
@extends('layouts.admin')
|
@extends('layouts.admin')
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="space-y-2" x-data="{
|
<div class="space-y-2 pb-20" x-data="{
|
||||||
showModal: false,
|
showModal: false,
|
||||||
showHistoryModal: false,
|
showHistoryModal: false,
|
||||||
showSettingsModal: false,
|
showSettingsModal: false,
|
||||||
@ -136,7 +136,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
init() {
|
init() {
|
||||||
this.$watch('activeTab', function(val) {
|
this.$watch('activeTab', (val) => {
|
||||||
history.pushState({}, '', '{{ route('admin.permission.companies.index') }}?tab=' + val);
|
history.pushState({}, '', '{{ route('admin.permission.companies.index') }}?tab=' + val);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -173,38 +173,39 @@
|
|||||||
}
|
}
|
||||||
}">
|
}">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header Area -->
|
||||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
<div class="flex items-center justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display transition-all duration-300">
|
<h1 class="text-2xl sm:text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display transition-all duration-300">
|
||||||
{{ __('Customer Management') }}
|
{{ __('Customer Management') }}
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">
|
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">
|
||||||
{{ __('Manage all tenant accounts and validity') }}
|
{{ __('Manage all tenant accounts and validity') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button @click="openCreateModal()" class="btn-luxury-primary">
|
<button @click="openCreateModal()" class="btn-luxury-primary whitespace-nowrap flex items-center gap-2 transition-all duration-300">
|
||||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14M5 12h14" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 5v14M5 12h14" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>{{ __('Add Customer') }}</span>
|
<span class="text-sm sm:text-base">{{ __('Add Customer') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-1 p-1.5 bg-slate-100 dark:bg-slate-900/50 rounded-2xl w-fit border border-slate-200/50 dark:border-slate-800/50">
|
<!-- Tabs Navigation -->
|
||||||
<button @click="activeTab = 'list'"
|
<div class="flex items-center gap-1 p-1.5 bg-slate-100 dark:bg-slate-900/50 rounded-2xl w-fit border border-slate-200/50 dark:border-slate-800/50 overflow-x-auto whitespace-nowrap custom-scrollbar" aria-label="Tabs">
|
||||||
|
<button type="button" @click="activeTab = 'list'"
|
||||||
:class="activeTab === 'list' ? 'bg-white dark:bg-slate-800 text-cyan-600 dark:text-cyan-400 shadow-sm shadow-cyan-500/10' : 'text-slate-400 hover:text-slate-600 dark:hover:text-slate-200'"
|
:class="activeTab === 'list' ? 'bg-white dark:bg-slate-800 text-cyan-600 dark:text-cyan-400 shadow-sm shadow-cyan-500/10' : 'text-slate-400 hover:text-slate-600 dark:hover:text-slate-200'"
|
||||||
class="px-8 py-3 rounded-xl text-sm font-black uppercase tracking-widest transition-all">
|
class="px-8 py-3 rounded-xl text-sm font-black uppercase tracking-widest transition-all duration-300">
|
||||||
{{ __('Customer List') }}
|
{{ __('Customer List') }}
|
||||||
</button>
|
</button>
|
||||||
<button @click="activeTab = 'settings'"
|
<button type="button" @click="activeTab = 'settings'"
|
||||||
:class="activeTab === 'settings' ? 'bg-white dark:bg-slate-800 text-cyan-600 dark:text-cyan-400 shadow-sm shadow-cyan-500/10' : 'text-slate-400 hover:text-slate-600 dark:hover:text-slate-200'"
|
:class="activeTab === 'settings' ? 'bg-white dark:bg-slate-800 text-cyan-600 dark:text-cyan-400 shadow-sm shadow-cyan-500/10' : 'text-slate-400 hover:text-slate-600 dark:hover:text-slate-200'"
|
||||||
class="px-8 py-3 rounded-xl text-sm font-black uppercase tracking-widest transition-all">
|
class="px-8 py-3 rounded-xl text-sm font-black uppercase tracking-widest transition-all duration-300">
|
||||||
{{ __('System Settings') }}
|
{{ __('System Settings') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative">
|
<div class="relative mt-6">
|
||||||
<!-- Loading Spinner Overlay -->
|
<!-- Loading Spinner Overlay -->
|
||||||
<div x-show="isLoadingTable"
|
<div x-show="isLoadingTable"
|
||||||
x-transition:enter="transition ease-out duration-300"
|
x-transition:enter="transition ease-out duration-300"
|
||||||
@ -255,25 +256,25 @@
|
|||||||
|
|
||||||
<!-- Table Section -->
|
<!-- Table Section -->
|
||||||
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 300ms">
|
<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">
|
<div class="flex flex-wrap items-center gap-4 mb-8">
|
||||||
<form action="{{ route('admin.permission.companies.index') }}" method="GET" class="relative group" @submit.prevent="fetchPage($el.action + '?' + new URLSearchParams(new FormData($el)).toString())">
|
<form action="{{ route('admin.permission.companies.index') }}" method="GET" class="flex flex-wrap items-center gap-4 w-full"
|
||||||
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
|
@submit.prevent="fetchPage($el.action + '?' + new URLSearchParams(new FormData($el)).toString())">
|
||||||
<svg class="w-4 h-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors stroke-[2.5]"
|
|
||||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round"
|
<div class="relative group w-full sm:w-72">
|
||||||
stroke-linejoin="round">
|
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
|
||||||
<circle cx="11" cy="11" r="8"></circle>
|
<svg class="w-4 h-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors stroke-[2.5]"
|
||||||
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round"
|
||||||
</svg>
|
stroke-linejoin="round">
|
||||||
</span>
|
<circle cx="11" cy="11" r="8"></circle>
|
||||||
<input type="text" name="search" value="{{ request('search') }}"
|
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
||||||
class="py-2.5 pl-12 pr-6 block w-64 luxury-input"
|
</svg>
|
||||||
placeholder="{{ __('Search customers...') }}">
|
</span>
|
||||||
<input type="hidden" name="per_page" value="{{ request('per_page', 10) }}">
|
<input type="text" name="search" value="{{ request('search') }}"
|
||||||
</form>
|
class="py-2.5 pl-12 pr-6 block w-full luxury-input text-sm font-bold"
|
||||||
|
placeholder="{{ __('Search customers...') }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center p-1 bg-slate-100/50 dark:bg-slate-900/50 backdrop-blur-md rounded-2xl border border-slate-200/50 dark:border-slate-700/50 w-full sm:w-fit overflow-x-auto whitespace-nowrap">
|
||||||
<div
|
|
||||||
class="flex items-center p-1 bg-slate-100/50 dark:bg-slate-900/50 backdrop-blur-md rounded-2xl border border-slate-200/50 dark:border-slate-700/50">
|
|
||||||
<a href="{{ route('admin.permission.companies.index') }}" @click.prevent="fetchPage($event.currentTarget.href)"
|
<a href="{{ route('admin.permission.companies.index') }}" @click.prevent="fetchPage($event.currentTarget.href)"
|
||||||
class="px-5 py-2 text-xs font-black tracking-widest uppercase transition-all duration-300 rounded-xl {{ !request()->filled('status') ? 'bg-white dark:bg-slate-800 text-cyan-600 dark:text-cyan-400 shadow-lg shadow-cyan-500/10' : 'text-slate-400 dark:text-slate-500 hover:text-slate-600 dark:hover:text-slate-300' }}">
|
class="px-5 py-2 text-xs font-black tracking-widest uppercase transition-all duration-300 rounded-xl {{ !request()->filled('status') ? 'bg-white dark:bg-slate-800 text-cyan-600 dark:text-cyan-400 shadow-lg shadow-cyan-500/10' : 'text-slate-400 dark:text-slate-500 hover:text-slate-600 dark:hover:text-slate-300' }}">
|
||||||
{{ __('All') }}
|
{{ __('All') }}
|
||||||
@ -287,9 +288,34 @@
|
|||||||
{{ __('Disabled') }}
|
{{ __('Disabled') }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="overflow-x-auto">
|
<div class="flex items-center gap-2 ml-auto sm:ml-0">
|
||||||
|
<button type="submit"
|
||||||
|
class="p-2.5 rounded-xl bg-cyan-500 text-white hover:bg-cyan-600 shadow-lg shadow-cyan-500/25 transition-all active:scale-95"
|
||||||
|
title="{{ __('Search') }}">
|
||||||
|
<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="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<a href="{{ route('admin.permission.companies.index') }}" @click.prevent="fetchPage($event.currentTarget.href)"
|
||||||
|
class="p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700 transition-all active:scale-95 border border-slate-200 dark:border-slate-700"
|
||||||
|
title="{{ __('Reset') }}">
|
||||||
|
<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="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="status" value="{{ request('status') }}">
|
||||||
|
<input type="hidden" name="per_page" value="{{ request('per_page', 10) }}">
|
||||||
|
<input type="hidden" name="tab" value="list">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Table View (Desktop) -->
|
||||||
|
<div class="hidden xl:block overflow-x-auto">
|
||||||
<table class="w-full text-left border-separate border-spacing-y-0">
|
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-slate-50/50 dark:bg-slate-900/30">
|
<tr class="bg-slate-50/50 dark:bg-slate-900/30">
|
||||||
@ -448,26 +474,20 @@
|
|||||||
</button>
|
</button>
|
||||||
@endif
|
@endif
|
||||||
<button @click='statusToggleSource = "edit"; openEditModal({{ json_encode($company) }})'
|
<button @click='statusToggleSource = "edit"; openEditModal({{ json_encode($company) }})'
|
||||||
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 transition-all border border-transparent hover:border-cyan-500/20"
|
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-600 hover:bg-cyan-500/5 transition-all border border-transparent hover:border-cyan-500/20"
|
||||||
title="{{ __('Edit') }}">
|
title="{{ __('Edit') }}">
|
||||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button type="button"
|
<button
|
||||||
@click="deleteFormAction = '{{ route('admin.permission.companies.destroy', $company->id) }}'; isDeleteConfirmOpen = true"
|
@click="deleteFormAction = '{{ route('admin.permission.companies.destroy', $company->id) }}'; isDeleteConfirmOpen = true"
|
||||||
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"
|
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 Customer') }}">
|
title="{{ __('Delete') }}">
|
||||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="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" />
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
</svg>
|
d="M14.74 9l-.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 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-3.38a2.25 2.25 0 00-2.25-2.25h-3.51a2.25 2.25 0 00-2.25 2.25v3.38" />
|
||||||
</button>
|
|
||||||
<button type="button" @click='openDetailSidebar({{ json_encode($company) }})'
|
|
||||||
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-indigo-500 hover:bg-indigo-500/5 transition-all border border-transparent hover:border-indigo-500/20"
|
|
||||||
title="{{ __('View Details') }}">
|
|
||||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 010-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178z" />
|
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -475,16 +495,14 @@
|
|||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="px-6 py-24 text-center">
|
<td colspan="6" class="px-6 py-20 text-center">
|
||||||
<div class="flex flex-col items-center">
|
<div class="flex flex-col items-center justify-center">
|
||||||
<div
|
<div class="w-20 h-20 rounded-full bg-slate-50 dark:bg-slate-800 flex items-center justify-center mb-4">
|
||||||
class="w-16 h-16 rounded-2xl bg-slate-50 dark:bg-slate-900 flex items-center justify-center text-slate-300 mb-4">
|
<svg class="w-10 h-10 text-slate-300" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" 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" />
|
||||||
<path
|
|
||||||
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>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-slate-400 font-bold">{{ __('No customers found') }}</p>
|
<p class="text-slate-400 font-bold tracking-widest uppercase text-sm">{{ __('No Customers Found') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -492,6 +510,102 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Mobile View (Cards) -->
|
||||||
|
<div class="xl:hidden space-y-4">
|
||||||
|
@forelse($companies as $company)
|
||||||
|
<div class="luxury-card p-6 rounded-3xl border border-slate-100 dark:border-slate-800/50 hover:shadow-xl transition-all duration-300">
|
||||||
|
<div class="flex justify-between items-start mb-6">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div class="w-12 h-12 rounded-2xl bg-cyan-500/10 text-cyan-500 flex items-center justify-center">
|
||||||
|
<svg class="w-6 h-6 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path 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>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4 class="text-lg font-black text-slate-800 dark:text-white leading-tight">{{ $company->name }}</h4>
|
||||||
|
<p class="text-xs font-mono font-bold text-slate-500 tracking-widest uppercase mt-0.5">{{ $company->code }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if($company->status)
|
||||||
|
<span class="px-3 py-1 rounded-full text-[10px] font-black bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 tracking-widest uppercase">{{ __('Active') }}</span>
|
||||||
|
@else
|
||||||
|
<span class="px-3 py-1 rounded-full text-[10px] font-black bg-rose-500/10 text-rose-500 border border-rose-500/20 tracking-widest uppercase">{{ __('Disabled') }}</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||||
|
<div class="p-3 bg-slate-50/50 dark:bg-slate-900/50 rounded-2xl border border-slate-100 dark:border-slate-800/50">
|
||||||
|
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-1">{{ __('Business') }}</p>
|
||||||
|
<p class="text-xs font-bold {{ $company->current_type === 'buyout' ? 'text-amber-600' : 'text-blue-600' }} uppercase tracking-wider">
|
||||||
|
{{ __($company->current_type === 'buyout' ? 'Buyout' : 'Lease') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="p-3 bg-slate-50/50 dark:bg-slate-900/50 rounded-2xl border border-slate-100 dark:border-slate-800/50">
|
||||||
|
<p class="text-[10px] font-black text-slate-400 uppercase tracking-widest mb-1">{{ __('Scale') }}</p>
|
||||||
|
<p class="text-xs font-bold text-slate-700 dark:text-slate-200 uppercase">
|
||||||
|
{{ $company->users_count }}U / {{ $company->machines_count }}M
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2.5 mb-6">
|
||||||
|
@if($company->current_type === 'lease')
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="px-2 py-0.5 rounded bg-blue-500/10 text-blue-600 text-[10px] font-black border border-blue-500/20 uppercase tracking-wider min-w-[55px] text-center">{{ __('Contract') }}</span>
|
||||||
|
<span class="text-[11px] font-mono font-bold text-slate-600 dark:text-slate-300">
|
||||||
|
{{ $company->start_date ? $company->start_date->format('Y-m-d') : '--' }} ~ {{ $company->end_date ? $company->end_date->format('Y-m-d') : __('Permanent') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($company->current_type === 'buyout')
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="px-2 py-0.5 rounded bg-amber-500/10 text-amber-600 text-[10px] font-black border border-amber-500/20 uppercase tracking-wider min-w-[55px] text-center">{{ __('Warranty') }}</span>
|
||||||
|
<span class="text-[11px] font-mono font-bold text-slate-600 dark:text-slate-300">
|
||||||
|
{{ $company->warranty_start_date ? $company->warranty_start_date->format('Y-m-d') : '--' }} ~ {{ $company->warranty_end_date ? $company->warranty_end_date->format('Y-m-d') : '--' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<span class="px-2 py-0.5 rounded bg-purple-500/10 text-purple-600 text-[10px] font-black border border-purple-500/20 uppercase tracking-wider min-w-[55px] text-center">{{ __('Software') }}</span>
|
||||||
|
<span class="text-[11px] font-mono font-bold text-slate-600 dark:text-slate-300">
|
||||||
|
{{ $company->software_start_date ? $company->software_start_date->format('Y-m-d') : '--' }} ~ {{ $company->software_end_date ? $company->software_end_date->format('Y-m-d') : '--' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-end gap-3 pt-4 border-t border-slate-100 dark:border-slate-800">
|
||||||
|
@if($company->status)
|
||||||
|
<button type="button"
|
||||||
|
@click="toggleFormAction = '{{ route('admin.permission.companies.status.toggle', $company->id) }}'; statusToggleSource = 'list'; isStatusConfirmOpen = true"
|
||||||
|
class="px-4 py-2 rounded-xl bg-amber-500/5 text-amber-500 text-xs font-black uppercase tracking-widest border border-amber-500/20 hover:bg-amber-500 hover:text-white transition-all">
|
||||||
|
{{ __('Disable') }}
|
||||||
|
</button>
|
||||||
|
@else
|
||||||
|
<button type="button"
|
||||||
|
@click="toggleFormAction = '{{ route('admin.permission.companies.status.toggle', $company->id) }}'; $nextTick(() => $refs.statusToggleForm.submit())"
|
||||||
|
class="px-4 py-2 rounded-xl bg-emerald-500/5 text-emerald-500 text-xs font-black uppercase tracking-widest border border-emerald-500/20 hover:bg-emerald-500 hover:text-white transition-all">
|
||||||
|
{{ __('Enable') }}
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
<button @click='statusToggleSource = "edit"; openEditModal({{ json_encode($company) }})'
|
||||||
|
class="px-4 py-2 rounded-xl bg-cyan-500/5 text-cyan-500 text-xs font-black uppercase tracking-widest border border-cyan-500/20 hover:bg-cyan-500 hover:text-white transition-all">
|
||||||
|
{{ __('Edit') }}
|
||||||
|
</button>
|
||||||
|
<button @click="deleteFormAction = '{{ route('admin.permission.companies.destroy', $company->id) }}'; isDeleteConfirmOpen = true"
|
||||||
|
class="p-2 rounded-xl bg-rose-500/5 text-rose-500 border border-rose-500/20 hover:bg-rose-500 hover:text-white transition-all">
|
||||||
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.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 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-3.38a2.25 2.25 0 00-2.25-2.25h-3.51a2.25 2.25 0 00-2.25 2.25v3.38" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<div class="py-20 text-center bg-slate-50 dark:bg-slate-900/50 rounded-3xl border-2 border-dashed border-slate-200 dark:border-slate-800">
|
||||||
|
<p class="text-slate-400 font-bold uppercase tracking-widest text-sm">{{ __('No Customers Found') }}</p>
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 border-t border-slate-100 dark:border-slate-800 pt-6">
|
<div class="mt-8 border-t border-slate-100 dark:border-slate-800 pt-6">
|
||||||
{{ $companies->links('vendor.pagination.luxury') }}
|
{{ $companies->links('vendor.pagination.luxury') }}
|
||||||
|
|||||||
@ -22,37 +22,35 @@ $roleSelectConfig = [
|
|||||||
data-errors="{{ json_encode($errors->any()) }}"
|
data-errors="{{ json_encode($errors->any()) }}"
|
||||||
data-store-url="{{ route($baseRoute . '.store') }}"
|
data-store-url="{{ route($baseRoute . '.store') }}"
|
||||||
data-index-url="{{ route($baseRoute . '.index') }}">
|
data-index-url="{{ route($baseRoute . '.index') }}">
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header Area -->
|
||||||
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
<div class="flex items-center justify-between gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-3xl font-black text-slate-800 dark:text-white font-display tracking-tight">{{ __('Product Management') }}</h1>
|
<h1 class="text-2xl sm:text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Product Management') }}</h1>
|
||||||
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">
|
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Manage your catalog, prices, and multilingual details.') }}</p>
|
||||||
{{ __('Manage your catalog, prices, and multilingual details.') }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<template x-if="activeTab === 'products'">
|
<template x-if="activeTab === 'products'">
|
||||||
<a href="{{ route($baseRoute . '.create') }}" class="btn-luxury-primary transition-all duration-300">
|
<a href="{{ route($baseRoute . '.create') }}" class="btn-luxury-primary whitespace-nowrap flex items-center gap-2 transition-all duration-300">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
<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" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>{{ __('Add Product') }}</span>
|
<span class="text-sm sm:text-base">{{ __('Add Product') }}</span>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="activeTab === 'categories'">
|
<template x-if="activeTab === 'categories'">
|
||||||
<button @click="openCategoryModal()" type="button" class="btn-luxury-primary transition-all duration-300 bg-emerald-600 hover:bg-emerald-700 shadow-emerald-500/20">
|
<button @click="openCategoryModal()" type="button" class="btn-luxury-primary whitespace-nowrap flex items-center gap-2 transition-all duration-300">
|
||||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
<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" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>{{ __('Add Category') }}</span>
|
<span class="text-sm sm:text-base">{{ __('Add Category') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tabs Navigation -->
|
<!-- Tabs Navigation -->
|
||||||
<div class="flex items-center gap-1 p-1.5 bg-slate-100 dark:bg-slate-900/50 rounded-2xl w-fit border border-slate-200/50 dark:border-slate-800/50" aria-label="Tabs">
|
<div class="flex items-center gap-1 p-1.5 bg-slate-100 dark:bg-slate-900/50 rounded-2xl w-fit border border-slate-200/50 dark:border-slate-800/50 overflow-x-auto whitespace-nowrap custom-scrollbar" aria-label="Tabs">
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@click="activeTab = 'products'"
|
@click="activeTab = 'products'"
|
||||||
:class="activeTab === 'products' ? 'bg-white dark:bg-slate-800 text-cyan-600 dark:text-cyan-400 shadow-sm shadow-cyan-500/10' : 'text-slate-400 hover:text-slate-600 dark:hover:text-slate-200'"
|
:class="activeTab === 'products' ? 'bg-white dark:bg-slate-800 text-cyan-600 dark:text-cyan-400 shadow-sm shadow-cyan-500/10' : 'text-slate-400 hover:text-slate-600 dark:hover:text-slate-200'"
|
||||||
|
|||||||
@ -1,52 +1,57 @@
|
|||||||
<div class="relative">
|
<div class="relative">
|
||||||
<form @submit.prevent="handleFilterSubmit('categories')" id="categories-filter-form" class="mb-8 flex flex-wrap items-center gap-4">
|
<!-- Toolbar & Filters -->
|
||||||
<!-- Search Input -->
|
<div class="flex items-center justify-between mb-8">
|
||||||
<div class="relative group">
|
<div class="flex items-center gap-6 w-full">
|
||||||
<div class="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none text-slate-400 group-focus-within:text-cyan-500 transition-colors">
|
<form @submit.prevent="handleFilterSubmit('categories')" id="categories-filter-form" class="flex flex-wrap items-center gap-4 w-full">
|
||||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
|
<!-- Search Group -->
|
||||||
</div>
|
<div class="relative group w-full sm:w-72">
|
||||||
<input type="text" name="category_search" value="{{ request('category_search', request('tab') === 'categories' ? request('category_search') : '') }}"
|
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
|
||||||
class="bg-slate-50 dark:bg-slate-900/50 border-none rounded-xl py-3 pl-10 pr-4 text-sm font-bold text-slate-700 dark:text-slate-200 focus:ring-2 focus:ring-cyan-500/20 w-64 transition-all shadow-sm"
|
<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.5" 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>
|
||||||
placeholder="{{ __('Search categories...') }}">
|
</span>
|
||||||
|
<input type="text" name="search" value="{{ request('search') }}"
|
||||||
|
placeholder="{{ __('Search categories...') }}"
|
||||||
|
class="luxury-input py-2.5 pl-11 pr-4 block w-full text-sm font-bold">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if(auth()->user()->isSystemAdmin())
|
||||||
|
<div class="relative w-full sm:w-72 flex-none">
|
||||||
|
<x-searchable-select name="category_company_id"
|
||||||
|
:options="$companies"
|
||||||
|
:selected="request('category_company_id')"
|
||||||
|
:placeholder="__('All Companies')"
|
||||||
|
@change="handleFilterSubmit('categories')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2 ml-auto sm:ml-0">
|
||||||
|
<button type="submit"
|
||||||
|
class="p-2.5 rounded-xl bg-cyan-500 text-white hover:bg-cyan-600 shadow-lg shadow-cyan-500/25 transition-all active:scale-95"
|
||||||
|
title="{{ __('Search') }}">
|
||||||
|
<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="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /></svg>
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
@click="
|
||||||
|
$el.closest('form').querySelectorAll('input[type=text], input[type=hidden]:not([name=tab])').forEach(i => i.value = '');
|
||||||
|
$el.closest('form').querySelectorAll('select').forEach(s => {
|
||||||
|
s.value = '';
|
||||||
|
const instance = window.HSSelect.getInstance(s);
|
||||||
|
if(instance) instance.setValue('');
|
||||||
|
});
|
||||||
|
handleFilterSubmit('categories');
|
||||||
|
"
|
||||||
|
class="p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700 transition-all active:scale-95 border border-slate-200 dark:border-slate-700"
|
||||||
|
title="{{ __('Reset') }}">
|
||||||
|
<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="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /></svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="tab" value="categories">
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
@if(auth()->user()->isSystemAdmin())
|
<!-- Desktop Table View -->
|
||||||
<div class="relative w-72">
|
<div class="hidden xl:block overflow-x-auto luxury-scrollbar">
|
||||||
<x-searchable-select name="category_company_id"
|
|
||||||
:options="$companies"
|
|
||||||
:selected="request('category_company_id')"
|
|
||||||
:placeholder="__('All Companies')"
|
|
||||||
@change="handleFilterSubmit('categories')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
|
|
||||||
<input type="hidden" name="tab" value="categories">
|
|
||||||
|
|
||||||
<button type="submit" class="p-2.5 rounded-xl bg-cyan-500 text-white hover:bg-cyan-600 shadow-lg shadow-cyan-500/25 group" title="{{ __('Search') }}">
|
|
||||||
<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="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button type="button"
|
|
||||||
@click="
|
|
||||||
$el.closest('form').querySelectorAll('input[type=text], input[type=hidden]:not([name=tab])').forEach(i => i.value = '');
|
|
||||||
$el.closest('form').querySelectorAll('select').forEach(s => {
|
|
||||||
s.value = ' ';
|
|
||||||
const instance = window.HSSelect.getInstance(s);
|
|
||||||
if (instance) instance.setValue(' ');
|
|
||||||
});
|
|
||||||
handleFilterSubmit('categories');
|
|
||||||
"
|
|
||||||
class="p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-700 group" title="{{ __('Reset') }}">
|
|
||||||
<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="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
<div class="overflow-x-auto luxury-scrollbar">
|
|
||||||
<table class="w-full text-left border-separate border-spacing-y-0">
|
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
||||||
@ -100,6 +105,71 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
@forelse($categories as $category)
|
||||||
|
<div class="luxury-card p-6 rounded-[2rem] border border-slate-100 dark:border-slate-800 bg-white/50 dark:bg-slate-900/50 transition-all duration-300 group">
|
||||||
|
<!-- Card Header -->
|
||||||
|
<div class="flex items-start justify-between gap-4 mb-6">
|
||||||
|
<div class="flex items-center gap-4 min-w-0">
|
||||||
|
<div class="w-14 h-14 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 overflow-hidden shadow-sm shrink-0">
|
||||||
|
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9.568 3H5.25A2.25 2.25 0 003 5.25v13.5A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V9.432a2.25 2.25 0 00-.659-1.591l-4.182-4.182A2.25 2.25 0 0014.568 3h-5z" />
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12h-6m6 4h-6m6-8h-6" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0">
|
||||||
|
<h3 class="text-lg font-black text-slate-800 dark:text-slate-100 truncate tracking-tight transition-colors group-hover:text-cyan-600 dark:group-hover:text-cyan-400">
|
||||||
|
{{ $category->localized_name }}
|
||||||
|
</h3>
|
||||||
|
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest truncate mt-1">
|
||||||
|
{{ __('Category ID') }}: #{{ $category->id }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Info Grid -->
|
||||||
|
@if(auth()->user()->isSystemAdmin())
|
||||||
|
<div class="mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">
|
||||||
|
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">
|
||||||
|
{{ __('Company') }}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm font-bold text-slate-700 dark:text-slate-300">
|
||||||
|
{{ $category->company->name ?? __('System Default') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<button type="button"
|
||||||
|
@click="confirmDelete('{{ route('admin.data-config.product-categories.destroy', $category->id) }}')"
|
||||||
|
class="p-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-400 hover:bg-rose-500 hover:text-white transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50"
|
||||||
|
title="{{ __('Delete') }}">
|
||||||
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9l-.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>
|
||||||
|
|
||||||
|
<button type="button"
|
||||||
|
@click="openCategoryModal(@js($category))"
|
||||||
|
class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-500 dark:text-slate-400 font-bold text-xs hover:bg-cyan-500 hover:text-white transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50">
|
||||||
|
<svg class="w-3.5 h-3.5" 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>
|
||||||
|
{{ __('Edit') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<div class="col-span-full py-20 text-center">
|
||||||
|
<p class="text-slate-400 italic font-bold tracking-widest small-caps">{{ __('No categories found.') }}</p>
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
<div class="mt-6 py-6 border-t border-slate-50 dark:border-slate-800/50">
|
<div class="mt-6 py-6 border-t border-slate-50 dark:border-slate-800/50">
|
||||||
{{ $categories->links('vendor.pagination.luxury') }}
|
{{ $categories->links('vendor.pagination.luxury') }}
|
||||||
|
|||||||
@ -1,109 +1,156 @@
|
|||||||
@php
|
@php
|
||||||
$baseRoute = 'admin.data-config.products';
|
$baseRoute = 'admin.data-config.products';
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<!-- Filters & Search -->
|
<!-- Toolbar & Filters -->
|
||||||
<form @submit.prevent="handleFilterSubmit('products')" id="products-filter-form" class="flex flex-col md:flex-row md:items-center gap-4 mb-6">
|
<div class="flex items-center justify-between mb-8">
|
||||||
<div class="relative group">
|
<div class="flex items-center gap-6 w-full">
|
||||||
<button type="submit" class="absolute inset-y-0 left-0 flex items-center pl-4 z-10 text-slate-400 group-focus-within:text-cyan-500 transition-colors">
|
<form @submit.prevent="handleFilterSubmit('products')" id="products-filter-form"
|
||||||
<svg class="w-4 h-4 stroke-[2.5]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
|
class="flex flex-wrap items-center gap-4 w-full">
|
||||||
<circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
||||||
</svg>
|
<!-- Search Group -->
|
||||||
</button>
|
<div class="relative group w-full sm:w-72">
|
||||||
<input type="text" name="product_search" value="{{ request('product_search') }}"
|
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
|
||||||
class="py-2.5 pl-12 pr-6 block w-full md:w-80 luxury-input" placeholder="{{ __('Search products...') }}">
|
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors"
|
||||||
<button type="submit" class="hidden"></button>
|
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"
|
||||||
</div>
|
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 products...') }}"
|
||||||
|
class="luxury-input py-2.5 pl-11 pr-4 block w-full text-sm font-bold">
|
||||||
|
</div>
|
||||||
|
|
||||||
@if(auth()->user()->isSystemAdmin())
|
<div class="relative w-full sm:w-72 flex-none">
|
||||||
<div class="relative w-72">
|
<x-searchable-select name="category_id" :options="$categories" :selected="request('category_id')"
|
||||||
<x-searchable-select name="product_company_id" :options="$companies" :selected="request('product_company_id')" :placeholder="__('All Companies')" @change="handleFilterSubmit('products')" />
|
:placeholder="__('All Categories')" @change="handleFilterSubmit('products')" />
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
|
@if(auth()->user()->isSystemAdmin())
|
||||||
<div class="relative w-72">
|
<div class="relative w-full sm:w-72 flex-none">
|
||||||
<x-searchable-select name="category_id" :options="$categories" :selected="request('category_id')" :placeholder="__('All Categories')" @change="handleFilterSubmit('products')" />
|
<x-searchable-select name="product_company_id" :options="$companies"
|
||||||
</div>
|
:selected="request('product_company_id')" :placeholder="__('All Companies')"
|
||||||
|
@change="handleFilterSubmit('products')" />
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="flex items-center gap-2 ml-auto sm:ml-0">
|
||||||
|
<button type="submit"
|
||||||
|
class="p-2.5 rounded-xl bg-cyan-500 text-white hover:bg-cyan-600 shadow-lg shadow-cyan-500/25 transition-all active:scale-95"
|
||||||
|
title="{{ __('Search') }}">
|
||||||
|
<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="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button type="button" @click="
|
||||||
|
$el.closest('form').querySelectorAll('input[type=text], input[type=hidden]:not([name=tab])').forEach(i => i.value = '');
|
||||||
|
$el.closest('form').querySelectorAll('select').forEach(s => {
|
||||||
|
s.value = '';
|
||||||
|
const instance = window.HSSelect.getInstance(s);
|
||||||
|
if(instance) instance.setValue('');
|
||||||
|
});
|
||||||
|
handleFilterSubmit('products');
|
||||||
|
"
|
||||||
|
class="p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700 transition-all active:scale-95 border border-slate-200 dark:border-slate-700"
|
||||||
|
title="{{ __('Reset') }}">
|
||||||
|
<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="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<input type="hidden" name="tab" value="products">
|
<input type="hidden" name="tab" value="products">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="p-2.5 rounded-xl bg-cyan-500 text-white hover:bg-cyan-600 shadow-lg shadow-cyan-500/25 group" title="{{ __('Search') }}">
|
<!-- Desktop Table View -->
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
<div class="hidden xl:block overflow-x-auto luxury-scrollbar">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button type="button"
|
|
||||||
@click="
|
|
||||||
$el.closest('form').querySelectorAll('input[type=text], input[type=hidden]:not([name=tab])').forEach(i => i.value = '');
|
|
||||||
$el.closest('form').querySelectorAll('select').forEach(s => {
|
|
||||||
s.value = ' ';
|
|
||||||
const instance = window.HSSelect.getInstance(s);
|
|
||||||
if (instance) instance.setValue(' ');
|
|
||||||
});
|
|
||||||
handleFilterSubmit('products');
|
|
||||||
"
|
|
||||||
class="p-2.5 rounded-xl bg-slate-100 dark:bg-slate-800 text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-700 group" title="{{ __('Reset') }}">
|
|
||||||
<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="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Table -->
|
|
||||||
<div class="overflow-x-auto luxury-scrollbar">
|
|
||||||
<table class="w-full text-left border-separate border-spacing-y-0">
|
<table class="w-full text-left border-separate border-spacing-y-0">
|
||||||
<thead>
|
<thead>
|
||||||
<tr class="bg-slate-50/50 dark:bg-slate-900/10">
|
<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">{{ __('Product Info') }}</th>
|
<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">{{ __('Barcode') }}</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">
|
||||||
|
{{ __('Product 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">
|
||||||
|
{{ __('Barcode') }}</th>
|
||||||
@if(auth()->user()->isSystemAdmin())
|
@if(auth()->user()->isSystemAdmin())
|
||||||
<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">{{ __('Company') }}</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">
|
||||||
|
{{ __('Company') }}</th>
|
||||||
@endif
|
@endif
|
||||||
<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">{{ __('Sale Price') }}</th>
|
<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">{{ __('Track Limit (Track/Spring)') }}</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">
|
||||||
<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>
|
{{ __('Sale Price') }}</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>
|
<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">
|
||||||
|
{{ __('Track Limit (Track/Spring)') }}</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>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
<tbody class="divide-y divide-slate-50 dark:divide-slate-800/80">
|
||||||
@forelse($products as $product)
|
@forelse($products as $product)
|
||||||
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
<tr class="group hover:bg-slate-50/80 dark:hover:bg-slate-800/40 transition-all duration-300">
|
||||||
<td class="px-6 py-5 cursor-pointer group/info" @click="viewProductDetail(@js($product))" title="{{ __('View Details') }}">
|
<td class="px-6 py-5 cursor-pointer group/info" @click="viewProductDetail(@js($product))"
|
||||||
|
title="{{ __('View Details') }}">
|
||||||
<div class="flex items-center gap-x-4">
|
<div class="flex items-center gap-x-4">
|
||||||
<div class="w-12 h-12 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/info:bg-cyan-500 group-hover/info:text-white group-hover/info:border-cyan-500 shadow-sm group-hover/info:shadow-cyan-500/50 transition-all duration-300 overflow-hidden relative">
|
<div
|
||||||
|
class="w-12 h-12 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/info:bg-cyan-500 group-hover/info:text-white group-hover/info:border-cyan-500 shadow-sm group-hover/info:shadow-cyan-500/50 transition-all duration-300 overflow-hidden relative">
|
||||||
@if($product->image_url)
|
@if($product->image_url)
|
||||||
<img src="{{ $product->image_url }}" class="w-full h-full object-cover">
|
<img src="{{ $product->image_url }}" class="w-full h-full object-cover">
|
||||||
@else
|
@else
|
||||||
<svg class="w-6 h-6 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" /></svg>
|
<svg class="w-6 h-6 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" />
|
||||||
|
</svg>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<span class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover/info:text-cyan-600 dark:group-hover/info:text-cyan-400 transition-colors leading-tight">{{ $product->localized_name }}</span>
|
<span
|
||||||
|
class="text-base font-extrabold text-slate-800 dark:text-slate-100 group-hover/info:text-cyan-600 dark:group-hover/info:text-cyan-400 transition-colors leading-tight">{{
|
||||||
|
$product->localized_name }}</span>
|
||||||
<div class="flex flex-wrap items-center gap-1.5 mt-1.5">
|
<div class="flex flex-wrap items-center gap-1.5 mt-1.5">
|
||||||
@php
|
@php
|
||||||
$catName = $product->category->localized_name ?? __('Uncategorized');
|
$catName = $product->category->localized_name ?? __('Uncategorized');
|
||||||
@endphp
|
@endphp
|
||||||
<span class="text-[10px] font-black text-slate-500 dark:text-slate-400 uppercase tracking-[0.14em] bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200/60 dark:border-slate-700/60 px-2.5 py-1 rounded-lg backdrop-blur-sm transition-all duration-300 group-hover/info:bg-cyan-500/10 group-hover/info:border-cyan-500/20 group-hover/info:text-cyan-500 group-hover/info:shadow-sm group-hover/info:shadow-cyan-500/10">{{ $catName }}</span>
|
<span
|
||||||
@if(($companySettings['enable_material_code'] ?? false) && isset($product->metadata['material_code']))
|
class="text-[10px] font-black text-slate-500 dark:text-slate-400 uppercase tracking-[0.14em] bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200/60 dark:border-slate-700/60 px-2.5 py-1 rounded-lg backdrop-blur-sm transition-all duration-300 group-hover/info:bg-cyan-500/10 group-hover/info:border-cyan-500/20 group-hover/info:text-cyan-500 group-hover/info:shadow-sm group-hover/info:shadow-cyan-500/10">{{
|
||||||
<span class="text-[10px] font-black text-emerald-500/90 uppercase tracking-widest bg-emerald-500/10 px-2 py-0.5 rounded-lg border border-emerald-500/20 shadow-sm shadow-emerald-500/5">#{{ $product->metadata['material_code'] }}</span>
|
$catName }}</span>
|
||||||
|
@if(($companySettings['enable_material_code'] ?? false) &&
|
||||||
|
isset($product->metadata['material_code']))
|
||||||
|
<span
|
||||||
|
class="text-[10px] font-black text-emerald-500/90 uppercase tracking-widest bg-emerald-500/10 px-2 py-0.5 rounded-lg border border-emerald-500/20 shadow-sm shadow-emerald-500/5">#{{
|
||||||
|
$product->metadata['material_code'] }}</span>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-5 whitespace-nowrap">
|
<td class="px-6 py-5 whitespace-nowrap">
|
||||||
<span class="text-sm font-mono font-bold text-slate-600 dark:text-slate-300 tracking-tight">{{ $product->barcode ?: '-' }}</span>
|
<span class="text-sm font-mono font-bold text-slate-600 dark:text-slate-300 tracking-tight">{{
|
||||||
|
$product->barcode ?: '-' }}</span>
|
||||||
</td>
|
</td>
|
||||||
@if(auth()->user()->isSystemAdmin())
|
@if(auth()->user()->isSystemAdmin())
|
||||||
<td class="px-6 py-5 text-center">
|
<td class="px-6 py-5 text-center">
|
||||||
<span class="text-xs font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest group-hover:text-slate-600 dark:group-hover:text-slate-300 transition-colors">{{ $product->company->name ?? '-' }}</span>
|
<span
|
||||||
|
class="text-xs font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest group-hover:text-slate-600 dark:group-hover:text-slate-300 transition-colors">{{
|
||||||
|
$product->company->name ?? '-' }}</span>
|
||||||
</td>
|
</td>
|
||||||
@endif
|
@endif
|
||||||
<td class="px-6 py-5 text-center whitespace-nowrap">
|
<td class="px-6 py-5 text-center whitespace-nowrap">
|
||||||
<span class="text-sm font-black text-slate-800 dark:text-white leading-none">${{ number_format($product->price, 0) }}</span>
|
<span class="text-sm font-black text-slate-800 dark:text-white leading-none">${{
|
||||||
|
number_format($product->price, 0) }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-5">
|
<td class="px-6 py-5">
|
||||||
<div class="flex items-center justify-center gap-2 font-black">
|
<div class="flex items-center justify-center gap-2 font-black">
|
||||||
@ -118,38 +165,53 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-5 text-center">
|
<td class="px-6 py-5 text-center">
|
||||||
@if($product->is_active)
|
@if($product->is_active)
|
||||||
<span class="inline-flex items-center px-3 py-1.5 rounded-xl text-[10px] font-black bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 tracking-widest uppercase shadow-sm shadow-emerald-500/5">{{ __('Active') }}</span>
|
<span
|
||||||
|
class="inline-flex items-center px-3 py-1.5 rounded-xl text-[10px] font-black bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 tracking-widest uppercase shadow-sm shadow-emerald-500/5">{{
|
||||||
|
__('Active') }}</span>
|
||||||
@else
|
@else
|
||||||
<span class="inline-flex items-center px-3 py-1.5 rounded-xl text-[10px] font-black bg-slate-400/10 text-slate-400 border border-slate-400/20 tracking-widest uppercase">{{ __('Disabled') }}</span>
|
<span
|
||||||
|
class="inline-flex items-center px-3 py-1.5 rounded-xl text-[10px] font-black bg-slate-400/10 text-slate-400 border border-slate-400/20 tracking-widest uppercase">{{
|
||||||
|
__('Disabled') }}</span>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td class="px-6 py-5 text-right">
|
<td class="px-6 py-5 text-right">
|
||||||
<div class="flex justify-end items-center gap-2">
|
<div class="flex justify-end items-center gap-2">
|
||||||
@if($product->is_active)
|
@if($product->is_active)
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@click="toggleStatus('{{ route($baseRoute . '.status.toggle', $product->id) }}')"
|
@click="toggleStatus('{{ route($baseRoute . '.status.toggle', $product->id) }}')"
|
||||||
class="p-2.5 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-amber-500 hover:bg-amber-500/5 transition-all border border-transparent hover:border-amber-500/20"
|
class="p-2.5 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-amber-500 hover:bg-amber-500/5 transition-all border border-transparent hover:border-amber-500/20"
|
||||||
title="{{ __('Disable') }}">
|
title="{{ __('Disable') }}">
|
||||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25v13.5m-7.5-13.5v13.5" /></svg>
|
|
||||||
</button>
|
|
||||||
@else
|
|
||||||
<button type="button"
|
|
||||||
@click="toggleStatus('{{ route($baseRoute . '.status.toggle', $product->id) }}')"
|
|
||||||
class="p-2.5 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-emerald-500 hover:bg-emerald-500/5 transition-all border border-transparent hover:border-emerald-500/20"
|
|
||||||
title="{{ __('Enable') }}">
|
|
||||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347c-.75.412-1.667-.13-1.667-.986V5.653z" /></svg>
|
|
||||||
</button>
|
|
||||||
@endif
|
|
||||||
<a href="{{ route($baseRoute . '.edit', $product->id) }}" class="p-2.5 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="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" /></svg>
|
|
||||||
</a>
|
|
||||||
<button type="button" @click="confirmDelete('{{ route($baseRoute . '.destroy', $product->id) }}')" class="p-2.5 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="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9l-.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>
|
|
||||||
<button type="button" @click="viewProductDetail({{ json_encode($product) }})" class="p-2.5 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-indigo-500 hover:bg-indigo-500/5 transition-all border border-transparent hover:border-indigo-500/20" title="{{ __('View') }}">
|
|
||||||
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.644C3.67 8.5 7.652 6 12 6c4.348 0 8.33 2.5 9.964 5.678.134.263.134.561 0 .824C20.33 15.5 16.348 18 12 18c-4.348 0-8.33-2.5-9.964-5.678Z" />
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
d="M15.75 5.25v13.5m-7.5-13.5v13.5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
@else
|
||||||
|
<button type="button"
|
||||||
|
@click="toggleStatus('{{ route($baseRoute . '.status.toggle', $product->id) }}')"
|
||||||
|
class="p-2.5 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-emerald-500 hover:bg-emerald-500/5 transition-all border border-transparent hover:border-emerald-500/20"
|
||||||
|
title="{{ __('Enable') }}">
|
||||||
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347c-.75.412-1.667-.13-1.667-.986V5.653z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
<a href="{{ route($baseRoute . '.edit', $product->id) }}"
|
||||||
|
class="p-2.5 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="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
<button type="button"
|
||||||
|
@click="confirmDelete('{{ route($baseRoute . '.destroy', $product->id) }}')"
|
||||||
|
class="p-2.5 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="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="m14.74 9l-.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>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -157,15 +219,155 @@
|
|||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="7" class="px-6 py-20 text-center text-slate-400 italic font-bold tracking-widest small-caps">{{ __('No products found matching your criteria.') }}</td>
|
<td colspan="7"
|
||||||
|
class="px-6 py-20 text-center text-slate-400 italic font-bold tracking-widest small-caps">{{
|
||||||
|
__('No products found matching your criteria.') }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@endforelse
|
@endforelse
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Card View -->
|
||||||
|
<div class="xl:hidden grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
@forelse($products as $product)
|
||||||
|
<div
|
||||||
|
class="luxury-card p-6 rounded-[2rem] border border-slate-100 dark:border-slate-800 bg-white/50 dark:bg-slate-900/50 transition-all duration-300 group">
|
||||||
|
<!-- Card Header -->
|
||||||
|
<div class="flex items-start justify-between gap-4 mb-6">
|
||||||
|
<div class="flex items-center gap-4 min-w-0">
|
||||||
|
<div @click="viewProductDetail(@js($product))"
|
||||||
|
class="w-14 h-14 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 overflow-hidden shadow-sm shrink-0 cursor-pointer">
|
||||||
|
@if($product->image_url)
|
||||||
|
<img src="{{ $product->image_url }}" class="w-full h-full object-cover">
|
||||||
|
@else
|
||||||
|
<svg class="w-7 h-7" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="m21 7.5-9-5.25L3 7.5m18 0-9 5.25m9-5.25v9l-9 5.25M3 7.5l9 5.25M3 7.5v9l9 5.25m0-9v9" />
|
||||||
|
</svg>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="min-w-0">
|
||||||
|
<h3 @click="viewProductDetail(@js($product))"
|
||||||
|
class="text-lg font-black text-slate-800 dark:text-slate-100 truncate hover:text-cyan-600 dark:hover:text-cyan-400 transition-colors tracking-tight cursor-pointer">
|
||||||
|
{{ $product->localized_name }}
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
class="text-xs font-mono font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest truncate">
|
||||||
|
{{ $product->barcode ?: '--' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status Label -->
|
||||||
|
<div class="mt-2">
|
||||||
|
@if($product->is_active)
|
||||||
|
<span
|
||||||
|
class="inline-flex items-center px-3 py-1.5 rounded-xl text-[10px] font-black bg-emerald-500/10 text-emerald-500 border border-emerald-500/20 tracking-widest uppercase shadow-sm shadow-emerald-500/5">{{
|
||||||
|
__('Active') }}</span>
|
||||||
|
@else
|
||||||
|
<span
|
||||||
|
class="inline-flex items-center px-3 py-1.5 rounded-xl text-[10px] font-black bg-slate-400/10 text-slate-400 border border-slate-400/20 tracking-widest uppercase shadow-sm shadow-slate-400/5">{{
|
||||||
|
__('Disabled') }}</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Info Grid -->
|
||||||
|
<div class="grid grid-cols-2 gap-y-4 mb-6 border-y border-slate-100 dark:border-slate-800/50 py-4">
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">
|
||||||
|
{{ __('Category') }}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm font-bold text-slate-700 dark:text-slate-300 truncate">
|
||||||
|
{{ $product->category->localized_name ?? __('Uncategorized') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">
|
||||||
|
{{ __('Sale Price') }}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm font-black text-emerald-600 dark:text-emerald-400 font-mono">
|
||||||
|
${{ number_format($product->price, 0) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">
|
||||||
|
{{ __('Track Limit') }}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm font-bold text-indigo-500 dark:text-indigo-400 font-mono">
|
||||||
|
{{ $product->track_limit ?: 0 }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">
|
||||||
|
{{ __('Spring Limit') }}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm font-bold text-amber-500 dark:text-amber-500 font-mono">
|
||||||
|
{{ $product->spring_limit ?: 0 }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@if(auth()->user()->isSystemAdmin())
|
||||||
|
<div class="col-span-2">
|
||||||
|
<p class="text-[10px] font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest mb-1">
|
||||||
|
{{ __('Company') }}
|
||||||
|
</p>
|
||||||
|
<p class="text-sm font-bold text-slate-700 dark:text-slate-300">
|
||||||
|
{{ $product->company->name ?? '-' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Buttons -->
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<button type="button"
|
||||||
|
@click="toggleStatus('{{ route($baseRoute . '.status.toggle', $product->id) }}')"
|
||||||
|
class="p-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-400 hover:bg-emerald-500 hover:text-white transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50"
|
||||||
|
title="{{ __('Status') }}">
|
||||||
|
@if($product->is_active)
|
||||||
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25v13.5m-7.5-13.5v13.5" />
|
||||||
|
</svg>
|
||||||
|
@else
|
||||||
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347c-.75.412-1.667-.13-1.667-.986V5.653z" />
|
||||||
|
</svg>
|
||||||
|
@endif
|
||||||
|
</button>
|
||||||
|
<a href="{{ route($baseRoute . '.edit', $product->id) }}"
|
||||||
|
class="p-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-400 hover:bg-cyan-500 hover:text-white transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50"
|
||||||
|
title="{{ __('Edit') }}">
|
||||||
|
<svg class="w-4 h-4 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" @click="viewProductDetail(@js($product))"
|
||||||
|
class="flex-1 flex items-center justify-center gap-2 py-3 rounded-xl bg-slate-50 dark:bg-slate-800 text-slate-500 dark:text-slate-400 font-bold text-xs hover:bg-cyan-500 hover:text-white transition-all duration-300 border border-slate-200/50 dark:border-slate-700/50">
|
||||||
|
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round"
|
||||||
|
d="M2.036 12.322a1.012 1.012 0 0 1 0-.644C3.67 8.5 7.652 6 12 6c4.348 0 8.332 2.5 9.964 5.678a1.012 1.012 0 0 1 0 .644C20.33 15.5 16.348 18 12 18c-4.348 0-8.332-2.5-9.964-5.678z" />
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0z" />
|
||||||
|
</svg>
|
||||||
|
{{ __('Details') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<div class="col-span-full py-20 text-center">
|
||||||
|
<p class="text-slate-400 italic font-bold tracking-widest small-caps">{{ __('No products found matching your
|
||||||
|
criteria.') }}</p>
|
||||||
|
</div>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Pagination -->
|
<!-- Pagination -->
|
||||||
<div class="mt-6 py-6 border-t border-slate-50 dark:border-slate-800/50">
|
<div class="mt-6 py-6 border-t border-slate-50 dark:border-slate-800/50">
|
||||||
{{ $products->links('vendor.pagination.luxury') }}
|
{{ $products->links('vendor.pagination.luxury') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1,5 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="h-full" x-data="{ darkMode: localStorage.getItem('darkMode') === 'true' }" :class="{ 'dark': darkMode }">
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="h-full"
|
||||||
|
x-data="{ darkMode: localStorage.getItem('darkMode') === 'true' }" :class="{ 'dark': darkMode }">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@ -10,7 +12,9 @@
|
|||||||
<!-- Fonts -->
|
<!-- Fonts -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&family=Plus+Jakarta+Sans:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&family=Plus+Jakarta+Sans:wght@400;500;600;700;800;900&display=swap"
|
||||||
|
rel="stylesheet">
|
||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script>
|
<script>
|
||||||
@ -24,13 +28,14 @@
|
|||||||
</script>
|
</script>
|
||||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-50 dark:bg-[#0f172a] antialiased font-sans h-full selection:bg-indigo-100 dark:selection:bg-indigo-900/40"
|
|
||||||
|
<body
|
||||||
|
class="bg-gray-50 dark:bg-[#0f172a] antialiased font-sans h-full selection:bg-indigo-100 dark:selection:bg-indigo-900/40"
|
||||||
x-data="{
|
x-data="{
|
||||||
sidebarOpen: false,
|
sidebarOpen: false,
|
||||||
sidebarCollapsed: localStorage.getItem('sidebarCollapsed') === 'true',
|
sidebarCollapsed: localStorage.getItem('sidebarCollapsed') === 'true',
|
||||||
userDropdownOpen: false
|
userDropdownOpen: false
|
||||||
}"
|
}" x-init="$watch('sidebarCollapsed', value => localStorage.setItem('sidebarCollapsed', value))">
|
||||||
x-init="$watch('sidebarCollapsed', value => localStorage.setItem('sidebarCollapsed', value))">
|
|
||||||
<!-- Option A: Loading Screen -->
|
<!-- Option A: Loading Screen -->
|
||||||
<x-loading-screen />
|
<x-loading-screen />
|
||||||
|
|
||||||
@ -42,31 +47,29 @@
|
|||||||
window.addEventListener('beforeunload', () => {
|
window.addEventListener('beforeunload', () => {
|
||||||
document.getElementById('top-loading-bar').classList.add('loading');
|
document.getElementById('top-loading-bar').classList.add('loading');
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('pageshow', () => {
|
window.addEventListener('pageshow', () => {
|
||||||
document.getElementById('top-loading-bar').classList.remove('loading');
|
document.getElementById('top-loading-bar').classList.remove('loading');
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Sidebar Overlay (Mobile) -->
|
<!-- Sidebar Overlay (Mobile) -->
|
||||||
<div x-show="sidebarOpen"
|
<div x-show="sidebarOpen" x-transition:enter="transition-opacity ease-linear duration-300"
|
||||||
x-transition:enter="transition-opacity ease-linear duration-300"
|
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
|
||||||
x-transition:enter-start="opacity-0"
|
x-transition:leave="transition-opacity ease-linear duration-300" x-transition:leave-start="opacity-100"
|
||||||
x-transition:enter-end="opacity-100"
|
x-transition:leave-end="opacity-0" @click="sidebarOpen = false"
|
||||||
x-transition:leave="transition-opacity ease-linear duration-300"
|
class="fixed inset-0 z-[55] bg-gray-900/50 lg:hidden" x-cloak></div>
|
||||||
x-transition:leave-start="opacity-100"
|
|
||||||
x-transition:leave-end="opacity-0"
|
|
||||||
@click="sidebarOpen = false"
|
|
||||||
class="fixed inset-0 z-[55] bg-gray-900/50 lg:hidden"
|
|
||||||
x-cloak></div>
|
|
||||||
|
|
||||||
<!-- ========== HEADER ========== -->
|
<!-- ========== HEADER ========== -->
|
||||||
<header class="sticky top-0 inset-x-0 flex flex-wrap sm:justify-start sm:flex-nowrap z-[48] w-full bg-white/80 backdrop-blur-md border-b border-slate-200/50 text-sm py-2.5 sm:py-4 lg:pl-[256px] dark:bg-[#0f172a]/80 dark:border-slate-800/50 shadow-sm transition-all duration-300"
|
<header
|
||||||
:class="sidebarCollapsed ? 'lg:pl-24' : 'lg:pl-[256px]'">
|
class="sticky top-0 inset-x-0 flex flex-wrap sm:justify-start sm:flex-nowrap z-[48] w-full bg-white/80 backdrop-blur-md border-b border-slate-200/50 text-sm py-2.5 sm:py-4 lg:pl-[256px] dark:bg-[#0f172a]/80 dark:border-slate-800/50 shadow-sm transition-all duration-300"
|
||||||
|
:class="sidebarCollapsed ? 'lg:pl-24' : 'lg:pl-[256px]'">
|
||||||
<nav class="flex basis-full items-center w-full mx-auto px-4 sm:px-6 md:px-8" aria-label="Global">
|
<nav class="flex basis-full items-center w-full mx-auto px-4 sm:px-6 md:px-8" aria-label="Global">
|
||||||
<div class="mr-5 lg:mr-0 lg:hidden text-center">
|
<div class="mr-5 lg:mr-0 lg:hidden text-center">
|
||||||
<a class="flex items-center gap-x-2 flex-none text-xl font-bold dark:text-white font-display tracking-tight" href="{{ route('admin.dashboard') }}" aria-label="Brand">
|
<a class="flex items-center gap-x-2 flex-none text-xl font-bold dark:text-white font-display tracking-tight"
|
||||||
<img src="{{ asset('S1_cropped_transparent.png') }}" alt="{{ config('app.name') }} Logo" class="w-7 h-7 object-contain">
|
href="{{ route('admin.dashboard') }}" aria-label="Brand">
|
||||||
|
<img src="{{ asset('S1_cropped_transparent.png') }}" alt="{{ config('app.name') }} Logo"
|
||||||
|
class="w-7 h-7 object-contain">
|
||||||
<span>Star<span class="text-cyan-500">Cloud</span></span>
|
<span>Star<span class="text-cyan-500">Cloud</span></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@ -76,64 +79,87 @@
|
|||||||
<div class="flex flex-row items-center justify-end gap-x-1.5 sm:gap-x-3">
|
<div class="flex flex-row items-center justify-end gap-x-1.5 sm:gap-x-3">
|
||||||
<!-- Language Switcher -->
|
<!-- Language Switcher -->
|
||||||
<div class="relative inline-flex" x-data="{ langOpen: false }">
|
<div class="relative inline-flex" x-data="{ langOpen: false }">
|
||||||
<button type="button" @click="langOpen = !langOpen" @click.away="langOpen = false" class="inline-flex flex-shrink-0 justify-center items-center gap-2 h-[2.375rem] px-3 rounded-full font-medium bg-white text-gray-700 align-middle hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-white transition-all text-xs dark:bg-gray-800 dark:hover:bg-slate-800 dark:text-gray-400 dark:hover:text-white dark:focus:ring-gray-700 dark:focus:ring-offset-gray-800">
|
<button type="button" @click="langOpen = !langOpen" @click.away="langOpen = false"
|
||||||
|
class="inline-flex flex-shrink-0 justify-center items-center gap-2 h-[2.375rem] px-3 rounded-full font-medium bg-white text-gray-700 align-middle hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-white transition-all text-xs dark:bg-gray-800 dark:hover:bg-slate-800 dark:text-gray-400 dark:hover:text-white dark:focus:ring-gray-700 dark:focus:ring-offset-gray-800">
|
||||||
<span class="flex items-center gap-x-2">
|
<span class="flex items-center gap-x-2">
|
||||||
@if(app()->getLocale() == 'zh_TW')
|
@if(app()->getLocale() == 'zh_TW')
|
||||||
<span class="text-base">🇹🇼</span>
|
<span class="text-base">🇹🇼</span>
|
||||||
<span class="hidden md:inline font-bold">繁體中文</span>
|
<span class="hidden md:inline font-bold">繁體中文</span>
|
||||||
@elseif(app()->getLocale() == 'ja')
|
@elseif(app()->getLocale() == 'ja')
|
||||||
<span class="text-base">🇯🇵</span>
|
<span class="text-base">🇯🇵</span>
|
||||||
<span class="hidden md:inline font-bold">日本語</span>
|
<span class="hidden md:inline font-bold">日本語</span>
|
||||||
@else
|
@else
|
||||||
<span class="text-base">🇬🇧</span>
|
<span class="text-base">🇬🇧</span>
|
||||||
<span class="hidden md:inline font-bold">English</span>
|
<span class="hidden md:inline font-bold">English</span>
|
||||||
@endif
|
@endif
|
||||||
</span>
|
</span>
|
||||||
<svg class="size-3 text-gray-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="m6 9 6 6 6-6"/></svg>
|
<svg class="size-3 text-gray-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="m6 9 6 6 6-6" />
|
||||||
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div x-show="langOpen"
|
<div x-show="langOpen" x-transition:enter="transition ease-out duration-100"
|
||||||
x-transition:enter="transition ease-out duration-100"
|
x-transition:enter-start="transform opacity-0 scale-95"
|
||||||
x-transition:enter-start="transform opacity-0 scale-95"
|
x-transition:enter-end="transform opacity-100 scale-100"
|
||||||
x-transition:enter-end="transform opacity-100 scale-100"
|
x-transition:leave="transition ease-in duration-75"
|
||||||
x-transition:leave="transition ease-in duration-75"
|
x-transition:leave-start="transform opacity-100 scale-100"
|
||||||
x-transition:leave-start="transform opacity-100 scale-100"
|
x-transition:leave-end="transform opacity-0 scale-95"
|
||||||
x-transition:leave-end="transform opacity-0 scale-95"
|
class="absolute right-0 top-full mt-2 min-w-[10rem] bg-white shadow-xl rounded-2xl p-2 dark:bg-gray-800 dark:border dark:border-gray-700 z-50 border border-slate-100"
|
||||||
class="absolute right-0 top-full mt-2 min-w-[10rem] 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>
|
||||||
x-cloak>
|
<a href="{{ route('lang.switch', 'zh_TW') }}"
|
||||||
<a href="{{ route('lang.switch', 'zh_TW') }}" class="flex items-center gap-x-3 py-2.5 px-3 rounded-xl text-sm text-gray-800 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300 transition-colors {{ app()->getLocale() == 'zh_TW' ? 'bg-gray-50 dark:bg-gray-900/50' : '' }}">
|
class="flex items-center gap-x-3 py-2.5 px-3 rounded-xl text-sm text-gray-800 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300 transition-colors {{ app()->getLocale() == 'zh_TW' ? 'bg-gray-50 dark:bg-gray-900/50' : '' }}">
|
||||||
<span class="text-lg">🇹🇼</span>
|
<span class="text-lg">🇹🇼</span>
|
||||||
<span class="font-bold">繁體中文</span>
|
<span class="font-bold">繁體中文</span>
|
||||||
@if(app()->getLocale() == 'zh_TW')
|
@if(app()->getLocale() == 'zh_TW')
|
||||||
<svg class="ml-auto size-4 text-cyan-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
<svg class="ml-auto size-4 text-cyan-500" xmlns="http://www.w3.org/2000/svg" width="24"
|
||||||
|
height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polyline points="20 6 9 17 4 12" />
|
||||||
|
</svg>
|
||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('lang.switch', 'en') }}" class="flex items-center gap-x-3 py-2.5 px-3 rounded-xl text-sm text-gray-800 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300 transition-colors {{ app()->getLocale() == 'en' ? 'bg-gray-50 dark:bg-gray-900/50' : '' }}">
|
<a href="{{ route('lang.switch', 'en') }}"
|
||||||
|
class="flex items-center gap-x-3 py-2.5 px-3 rounded-xl text-sm text-gray-800 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300 transition-colors {{ app()->getLocale() == 'en' ? 'bg-gray-50 dark:bg-gray-900/50' : '' }}">
|
||||||
<span class="text-lg">🇬🇧</span>
|
<span class="text-lg">🇬🇧</span>
|
||||||
<span class="font-bold">English</span>
|
<span class="font-bold">English</span>
|
||||||
@if(app()->getLocale() == 'en')
|
@if(app()->getLocale() == 'en')
|
||||||
<svg class="ml-auto size-4 text-cyan-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
<svg class="ml-auto size-4 text-cyan-500" xmlns="http://www.w3.org/2000/svg" width="24"
|
||||||
|
height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polyline points="20 6 9 17 4 12" />
|
||||||
|
</svg>
|
||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
<a href="{{ route('lang.switch', 'ja') }}" class="flex items-center gap-x-3 py-2.5 px-3 rounded-xl text-sm text-gray-800 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300 transition-colors {{ app()->getLocale() == 'ja' ? 'bg-gray-50 dark:bg-gray-900/50' : '' }}">
|
<a href="{{ route('lang.switch', 'ja') }}"
|
||||||
|
class="flex items-center gap-x-3 py-2.5 px-3 rounded-xl text-sm text-gray-800 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300 transition-colors {{ app()->getLocale() == 'ja' ? 'bg-gray-50 dark:bg-gray-900/50' : '' }}">
|
||||||
<span class="text-lg">🇯🇵</span>
|
<span class="text-lg">🇯🇵</span>
|
||||||
<span class="font-bold">日本語</span>
|
<span class="font-bold">日本語</span>
|
||||||
@if(app()->getLocale() == 'ja')
|
@if(app()->getLocale() == 'ja')
|
||||||
<svg class="ml-auto size-4 text-cyan-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
<svg class="ml-auto size-4 text-cyan-500" xmlns="http://www.w3.org/2000/svg" width="24"
|
||||||
|
height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polyline points="20 6 9 17 4 12" />
|
||||||
|
</svg>
|
||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Dark Mode Toggle -->
|
<!-- Dark Mode Toggle -->
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@click="darkMode = !darkMode; localStorage.setItem('darkMode', darkMode); document.documentElement.classList.toggle('dark', darkMode)"
|
@click="darkMode = !darkMode; localStorage.setItem('darkMode', darkMode); document.documentElement.classList.toggle('dark', darkMode)"
|
||||||
class="inline-flex flex-shrink-0 justify-center items-center gap-2 h-[2.375rem] w-[2.375rem] rounded-full font-medium bg-white text-gray-700 align-middle hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-white transition-all text-xs dark:bg-gray-800 dark:hover:bg-slate-800 dark:text-gray-400 dark:hover:text-white dark:focus:ring-gray-700 dark:focus:ring-offset-gray-800">
|
class="inline-flex flex-shrink-0 justify-center items-center gap-2 h-[2.375rem] w-[2.375rem] rounded-full font-medium bg-white text-gray-700 align-middle hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-white transition-all text-xs dark:bg-gray-800 dark:hover:bg-slate-800 dark:text-gray-400 dark:hover:text-white dark:focus:ring-gray-700 dark:focus:ring-offset-gray-800">
|
||||||
<svg x-show="!darkMode" class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<svg x-show="!darkMode" class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="16"
|
||||||
<path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278zM4.858 1.311A7.269 7.269 0 0 0 1.025 7.71c0 4.02 3.279 7.276 7.319 7.276a7.316 7.316 0 0 0 5.205-2.162c-.337.042-.68.063-1.029.063-4.61 0-8.343-3.714-8.343-8.29 0-1.167.242-2.278.681-3.286z"/>
|
height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278zM4.858 1.311A7.269 7.269 0 0 0 1.025 7.71c0 4.02 3.279 7.276 7.319 7.276a7.316 7.316 0 0 0 5.205-2.162c-.337.042-.68.063-1.029.063-4.61 0-8.343-3.714-8.343-8.29 0-1.167.242-2.278.681-3.286z" />
|
||||||
</svg>
|
</svg>
|
||||||
<svg x-show="darkMode" class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<svg x-show="darkMode" class="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="16" height="16"
|
||||||
<path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"/>
|
fill="currentColor" viewBox="0 0 16 16">
|
||||||
|
<path
|
||||||
|
d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@ -142,34 +168,52 @@
|
|||||||
open: false,
|
open: false,
|
||||||
avatarUrl: '{{ Auth::user()->avatar_url }}'
|
avatarUrl: '{{ Auth::user()->avatar_url }}'
|
||||||
}" @avatar-updated.window="avatarUrl = $event.detail.url">
|
}" @avatar-updated.window="avatarUrl = $event.detail.url">
|
||||||
<button type="button" @click="open = !open" @click.away="open = false" class="inline-flex flex-shrink-0 justify-center items-center gap-2 h-[2.375rem] w-[2.375rem] rounded-full font-medium bg-white text-gray-700 align-middle hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-white transition-all text-xs dark:bg-gray-800 dark:hover:bg-slate-800 dark:text-gray-400 dark:hover:text-white dark:focus:ring-gray-700 dark:focus:ring-offset-gray-800">
|
<button type="button" @click="open = !open" @click.away="open = false"
|
||||||
<img class="inline-block h-[2.375rem] w-[2.375rem] rounded-full ring-2 ring-white dark:ring-gray-800 object-cover" :src="avatarUrl" alt="{{ Auth::user()->name }}">
|
class="inline-flex flex-shrink-0 justify-center items-center gap-2 h-[2.375rem] w-[2.375rem] rounded-full font-medium bg-white text-gray-700 align-middle hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-white transition-all text-xs dark:bg-gray-800 dark:hover:bg-slate-800 dark:text-gray-400 dark:hover:text-white dark:focus:ring-gray-700 dark:focus:ring-offset-gray-800">
|
||||||
|
<img class="inline-block h-[2.375rem] w-[2.375rem] rounded-full ring-2 ring-white dark:ring-gray-800 object-cover"
|
||||||
|
:src="avatarUrl" alt="{{ Auth::user()->name }}">
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div x-show="open"
|
<div x-show="open" x-transition:enter="transition ease-out duration-100"
|
||||||
x-transition:enter="transition ease-out duration-100"
|
x-transition:enter-start="transform opacity-0 scale-95"
|
||||||
x-transition:enter-start="transform opacity-0 scale-95"
|
x-transition:enter-end="transform opacity-100 scale-100"
|
||||||
x-transition:enter-end="transform opacity-100 scale-100"
|
x-transition:leave="transition ease-in duration-75"
|
||||||
x-transition:leave="transition ease-in duration-75"
|
x-transition:leave-start="transform opacity-100 scale-100"
|
||||||
x-transition:leave-start="transform opacity-100 scale-100"
|
x-transition:leave-end="transform opacity-0 scale-95"
|
||||||
x-transition:leave-end="transform opacity-0 scale-95"
|
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"
|
||||||
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>
|
||||||
x-cloak>
|
<div
|
||||||
<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">
|
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-[11px] font-bold uppercase tracking-widest text-slate-500 dark:text-slate-400">{{ __('Signed in as') }}</p>
|
<p
|
||||||
<p class="text-sm font-bold text-slate-700 dark:text-slate-200 truncate">{{ Auth::user()->name }}</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-xs font-medium text-slate-500 truncate">{{ Auth::user()->email }}</p>
|
<p class="text-xs font-medium text-slate-500 truncate">{{ Auth::user()->email }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 py-2">
|
<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') }}">
|
<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"
|
||||||
<svg class="flex-shrink-0 size-4 text-cyan-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" 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>
|
href="{{ route('profile.edit') }}">
|
||||||
|
<svg class="flex-shrink-0 size-4 text-cyan-500" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||||
|
stroke-width="2.5" 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>
|
||||||
{{ __('Account Settings') }}
|
{{ __('Account Settings') }}
|
||||||
</a>
|
</a>
|
||||||
<div class="my-2 border-t border-slate-100 dark:border-slate-700"></div>
|
<div class="my-2 border-t border-slate-100 dark:border-slate-700"></div>
|
||||||
<form method="POST" action="{{ route('logout') }}">
|
<form method="POST" action="{{ route('logout') }}">
|
||||||
@csrf
|
@csrf
|
||||||
<button type="submit" class="w-full flex items-center gap-x-3.5 py-2.5 px-3 rounded-xl text-sm font-bold text-rose-500 hover:bg-rose-50 dark:hover:bg-rose-500/10 transition-colors">
|
<button type="submit"
|
||||||
<svg class="flex-shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" x2="9" y1="12" y2="12"/></svg>
|
class="w-full flex items-center gap-x-3.5 py-2.5 px-3 rounded-xl text-sm font-bold text-rose-500 hover:bg-rose-50 dark:hover:bg-rose-500/10 transition-colors">
|
||||||
|
<svg class="flex-shrink-0 size-4" xmlns="http://www.w3.org/2000/svg" width="24"
|
||||||
|
height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||||
|
stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
|
||||||
|
<polyline points="16 17 21 12 16 7" />
|
||||||
|
<line x1="21" x2="9" y1="12" y2="12" />
|
||||||
|
</svg>
|
||||||
{{ __('Logout') }}
|
{{ __('Logout') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
@ -184,13 +228,15 @@
|
|||||||
|
|
||||||
<!-- ========== MAIN CONTENT ========== -->
|
<!-- ========== MAIN CONTENT ========== -->
|
||||||
<!-- Sidebar Toggle (Mobile) -->
|
<!-- Sidebar Toggle (Mobile) -->
|
||||||
<div class="sticky top-[3.75rem] inset-x-0 z-20 bg-white border-y px-4 sm:px-6 md:px-8 lg:hidden dark:bg-gray-800 dark:border-gray-700">
|
<div
|
||||||
|
class="sticky top-[3.75rem] inset-x-0 z-20 bg-white border-y px-4 sm:px-6 md:px-8 lg:hidden dark:bg-gray-800 dark:border-gray-700">
|
||||||
<div class="flex items-center py-4">
|
<div class="flex items-center py-4">
|
||||||
<!-- Navigation Toggle -->
|
<!-- Navigation Toggle -->
|
||||||
<button type="button" class="text-gray-500 hover:text-gray-600" @click="sidebarOpen = !sidebarOpen">
|
<button type="button" class="text-gray-500 hover:text-gray-600" @click="sidebarOpen = !sidebarOpen">
|
||||||
<span class="sr-only">Toggle Navigation</span>
|
<span class="sr-only">Toggle Navigation</span>
|
||||||
<svg class="w-5 h-5" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
<svg class="w-5 h-5" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||||
<path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"/>
|
<path fill-rule="evenodd"
|
||||||
|
d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<!-- End Navigation Toggle -->
|
<!-- End Navigation Toggle -->
|
||||||
@ -203,58 +249,61 @@
|
|||||||
<!-- End Sidebar Toggle -->
|
<!-- End Sidebar Toggle -->
|
||||||
|
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<div id="application-sidebar"
|
<div id="application-sidebar"
|
||||||
class="fixed top-0 left-0 bottom-0 z-[60] bg-white dark:bg-[#0f172a] pt-5 pb-10 border-e border-slate-200 dark:border-none overflow-y-auto overflow-x-hidden transition-all duration-300 transform lg:translate-x-0 shadow-xl dark:shadow-2xl flex flex-col"
|
class="fixed top-0 left-0 bottom-0 z-[60] bg-white dark:bg-[#0f172a] pt-5 pb-10 border-e border-slate-200 dark:border-none overflow-y-auto overflow-x-hidden transition-all duration-300 transform lg:translate-x-0 shadow-xl dark:shadow-2xl flex flex-col"
|
||||||
:class="[
|
:class="[
|
||||||
sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0',
|
sidebarOpen ? 'translate-x-0' : '-translate-x-full lg:translate-x-0',
|
||||||
sidebarCollapsed ? 'w-[60px]' : 'w-56'
|
sidebarCollapsed ? 'w-[60px]' : 'w-56'
|
||||||
]">
|
]">
|
||||||
|
|
||||||
<!-- Close Button (Mobile) -->
|
<!-- Close Button (Mobile) -->
|
||||||
<button type="button" @click="sidebarOpen = false" class="absolute top-4 right-4 text-slate-500 hover:text-slate-800 lg:hidden dark:text-slate-400 dark:hover:text-slate-200">
|
<button type="button" @click="sidebarOpen = false"
|
||||||
|
class="absolute top-4 right-4 text-slate-500 hover:text-slate-800 lg:hidden dark:text-slate-400 dark:hover:text-slate-200">
|
||||||
<span class="sr-only">Close sidebar</span>
|
<span class="sr-only">Close sidebar</span>
|
||||||
<svg class="w-5 h-5" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
<svg class="w-5 h-5" 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="M6 18L18 6M6 6l12 12" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Sidebar Header (Logo & Toggle) -->
|
<!-- Sidebar Header (Logo & Toggle) -->
|
||||||
<div class="mb-8 mt-2 flex items-center h-8 transition-all duration-300 shrink-0 w-full"
|
<div class="mb-8 mt-2 flex items-center h-8 transition-all duration-300 shrink-0 w-full"
|
||||||
:class="sidebarCollapsed ? 'px-[14px]' : 'px-4'">
|
:class="sidebarCollapsed ? 'px-[14px]' : 'px-4'">
|
||||||
|
|
||||||
<!-- Logo (Hidden when collapsed to save space) -->
|
<!-- Logo (Hidden when collapsed to save space) -->
|
||||||
<a class="flex items-center gap-x-2 text-xl font-bold text-slate-900 dark:text-white font-display tracking-tight whitespace-nowrap overflow-hidden transition-all duration-300"
|
<a class="flex items-center gap-x-2 text-xl font-bold text-slate-900 dark:text-white font-display tracking-tight whitespace-nowrap overflow-hidden transition-all duration-300"
|
||||||
href="{{ route('admin.dashboard') }}" aria-label="Brand"
|
href="{{ route('admin.dashboard') }}" aria-label="Brand"
|
||||||
:class="sidebarCollapsed ? 'max-w-0 opacity-0' : 'max-w-[200px] opacity-100'">
|
:class="sidebarCollapsed ? 'max-w-0 opacity-0' : 'max-w-[200px] opacity-100'">
|
||||||
<img src="{{ asset('S1_cropped_transparent.png') }}" alt="{{ config('app.name') }} Logo" class="w-8 h-8 object-contain shrink-0">
|
<img src="{{ asset('S1_cropped_transparent.png') }}" alt="{{ config('app.name') }} Logo"
|
||||||
|
class="w-8 h-8 object-contain shrink-0">
|
||||||
<span>Star<span class="text-cyan-500">Cloud</span></span>
|
<span>Star<span class="text-cyan-500">Cloud</span></span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div class="flex-1 transition-all duration-300"></div>
|
<div class="flex-1 transition-all duration-300"></div>
|
||||||
|
|
||||||
<!-- Unified Toggle Button (Inside Sidebar) -->
|
<!-- Unified Toggle Button (Inside Sidebar) -->
|
||||||
<button type="button" @click="sidebarCollapsed = !sidebarCollapsed"
|
<button type="button" @click="sidebarCollapsed = !sidebarCollapsed" x-cloak
|
||||||
x-cloak
|
class="hidden lg:flex items-center justify-center size-8 rounded-xl bg-slate-50 dark:bg-slate-800/80 text-slate-400 hover:bg-cyan-500 hover:text-white border border-slate-200 dark:border-slate-700 transition-all shrink-0">
|
||||||
class="hidden lg:flex items-center justify-center size-8 rounded-xl bg-slate-50 dark:bg-slate-800/80 text-slate-400 hover:bg-cyan-500 hover:text-white border border-slate-200 dark:border-slate-700 transition-all shrink-0">
|
<svg class="size-4.5 transition-transform duration-500" :class="sidebarCollapsed ? 'rotate-180' : ''"
|
||||||
<svg class="size-4.5 transition-transform duration-500"
|
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"
|
||||||
:class="sidebarCollapsed ? 'rotate-180' : ''"
|
stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||||
xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
<path d="m11 17-5-5 5-5M18 17l-5-5 5-5" />
|
||||||
<path d="m11 17-5-5 5-5M18 17l-5-5 5-5"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="px-4 py-2 w-full flex flex-col flex-wrap transition-all duration-300 flex-1" :class="sidebarCollapsed ? 'px-2' : 'px-5'">
|
<nav class="px-4 py-2 w-full flex flex-col flex-wrap transition-all duration-300 flex-1"
|
||||||
|
:class="sidebarCollapsed ? 'px-2' : 'px-5'">
|
||||||
<ul class="space-y-1.5">
|
<ul class="space-y-1.5">
|
||||||
@include('layouts.partials.sidebar-menu')
|
@include('layouts.partials.sidebar-menu')
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
<!-- End Sidebar -->
|
<!-- End Sidebar -->
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div class="w-full pt-4 lg:pt-5 pb-12 px-4 sm:px-6 md:px-8 transition-all duration-300"
|
<div class="w-full pt-4 lg:pt-5 pb-12 px-4 sm:px-6 md:px-8 transition-all duration-300"
|
||||||
:class="sidebarCollapsed ? 'lg:pl-24' : 'lg:pl-[256px]'">
|
:class="sidebarCollapsed ? 'lg:pl-24' : 'lg:pl-[256px]'">
|
||||||
<x-breadcrumbs class="mb-4 hidden lg:flex" />
|
<x-breadcrumbs class="mb-4 hidden lg:flex" />
|
||||||
<main class="animate-fade-up">
|
<main class="animate-fade-up">
|
||||||
@yield('content')
|
@yield('content')
|
||||||
@ -266,4 +315,5 @@
|
|||||||
|
|
||||||
@yield('scripts')
|
@yield('scripts')
|
||||||
</body>
|
</body>
|
||||||
</html>
|
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue
Block a user