[FEAT] 完善全站多語系支援、角色權限篩選優化及 UI 元件重構
All checks were successful
star-cloud-deploy-demo / deploy-demo (push) Successful in 1m4s

- [DOCS] 補齊 en, ja, zh_TW 語系檔翻譯並完善驗證錯誤訊息 (validation.php)
- [FEAT] 角色權限頁面新增「所屬單位」篩選功能 (僅限系統管理員)
- [STYLE] 優化角色列表顯示,將「類型」變更為具體「所屬單位」名稱
- [STYLE] 修正角色頁面工具列佈局,搜尋框置前並修正下拉箭頭顯示
- [REFACTOR] 統一全站刪除確認視窗,導入新版 <x-delete-confirm-modal /> 組件
- [REFACTOR] 優化 PermissionController 查詢效能 (Eager Loading)
- [FIX] 修正 RoleSeeder 角色命名與資料庫同步邏輯
This commit is contained in:
2026-03-20 13:41:51 +08:00
parent 6588dcd7f7
commit d2cefe3f39
31 changed files with 2431 additions and 1775 deletions

View File

@@ -24,6 +24,11 @@ trigger: always_on
- 建立新資源時,必須在背景強制綁定 `company_id`,禁止由前端傳參決定。 - 建立新資源時,必須在背景強制綁定 `company_id`,禁止由前端傳參決定。
- 範例:`$model->company_id = Auth::user()->company_id;` - 範例:`$model->company_id = Auth::user()->company_id;`
### 1.4 角色清單隔離 (Role List Isolation)
- 租戶管理員 (Tenant Admin) 只能管理隸屬於其公司下的角色。
- **嚴禁使用**包含 `NULL``forCompany` 廣義作用域來展示管理清單。
- 查詢時必須嚴格使用 `where('company_id', auth()->user()->company_id)` 隔離系統 Super Admin 或 角色範本。
--- ---
## 2. 權限開發規範 (spatie/laravel-permission) ## 2. 權限開發規範 (spatie/laravel-permission)
@@ -36,6 +41,12 @@ trigger: always_on
- 權限名稱應遵循 `[module].[action]` 格式(例如 `machine.view`, `machine.edit`)。 - 權限名稱應遵循 `[module].[action]` 格式(例如 `machine.view`, `machine.edit`)。
- 所有租戶共用相同的權限定義。 - 所有租戶共用相同的權限定義。
### 2.3 權限遞迴約束 (Privilege Delegation Constraint)
為防止權限提升 (Privilege Escalation)
- **權限子集驗證**:管理員僅能指派其**自身持有**之權限給其他角色或帳號。
- **Controller 實作**:在 `store``update` 時,必須比對傳入的權限集合是否為操作者 `getPermissionNames()` 的子集。
- **UI 過濾**:權限分配介面應基於當前使用者權限清單進行動態過濾展示。
--- ---
## 3. 介面安全 (UI/Blade) ## 3. 介面安全 (UI/Blade)

View File

@@ -67,8 +67,9 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
## 4. 實作檢查清單 (Checklist) ## 4. 實作檢查清單 (Checklist)
- [ ] **列表佈局**: 是否採用「整合式卡片」結構且內距設為 `p-8` - [x] **列表佈局**: 是否採用「整合式卡片」結構且內距設為 `p-8`
- [ ] **分頁與總數**: 列表底部是否正確召喚 `vendor.pagination.luxury` - [ ] **分頁與總數**: 列表底部是否正確召喚 `vendor.pagination.luxury`
- [ ] **刪除動作**: 是否已全面使用 `<x-delete-confirm-modal />` 封裝執行路徑?
- [ ] **文字色階**: 符合標題 `slate-900/white` 與標籤 `slate-500` 的對比度。 - [ ] **文字色階**: 符合標題 `slate-900/white` 與標籤 `slate-500` 的對比度。
- [ ] **可讀性檢查**: 二級資訊是否達到 `text-xs` (12px) 且權重不超過 `font-bold` - [ ] **可讀性檢查**: 二級資訊是否達到 `text-xs` (12px) 且權重不超過 `font-bold`
@@ -136,7 +137,7 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
</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">
<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-6 font-extrabold text-slate-800 dark:text-slate-100 italic">Example Name</td> <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> <td class="px-6 py-6 text-right"> </td>
</tr> </tr>
</tbody> </tbody>
@@ -152,8 +153,9 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
``` ```
### 佈局核心原則: ### 佈局核心原則:
1. **移除重複內距**: 根容器 `div` 應**禁止**使用 `p-6``p-10`,因為佈局基底已提供基礎間距。僅使用 `space-y-6` (或 `space-y-8`) 控制區塊間隙。 1. **移除重複內距**: 根容器 `div` 應**禁止**使用 `p-6``p-10`,因為佈局基底已提供基礎間距。
2. **主容器樣式**: 強制對齊為 `luxury-card rounded-3xl p-8` 2. **區塊間隙**: 建議使用 `space-y-6``space-y-8` 以獲得最佳空氣感。但在「高密度資料管理」或使用者有特殊緊湊需求的情境下,容許縮減至 **`space-y-2`**
3. **主容器樣式**: 強制對齊為 `luxury-card rounded-3xl p-8`
3. **標題排版**: 3. **標題排版**:
- 主標題需應用 `font-display` (Outfit)。 - 主標題需應用 `font-display` (Outfit)。
- 描述文字需應用 `uppercase tracking-widest font-bold` 以呈現高級設計感。 - 描述文字需應用 `uppercase tracking-widest font-bold` 以呈現高級設計感。
@@ -212,61 +214,24 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
### 空間與反應 (Spacing & Interaction) ### 空間與反應 (Spacing & Interaction)
- **單元格內距**: 統一使用 `px-6 py-6` - **單元格內距**: 統一使用 `px-6 py-6`
- **懸停反應**: 必須在 `tr` 套用 `group` 且子元素套用 `group-hover:bg-slate-50/80` (深色: `dark:group-hover:bg-slate-800/40`) 以提供高級的互動感知。 - **懸停反應**: 必須在 `tr` 套用 `group` 且子<EFBFBD>### 9.4 標竿刪除確認模式 (Luxury Delete Modal Pattern)
- **圖示容器懸停 (Icon Hover Palette)**: 當執行刪除或具備破壞性的操作時,**禁止**使用瀏覽器原生 `confirm()` 或簡易的 `x-modal`。全站統一使用 **`<x-delete-confirm-modal />`** Blade 組件進行二次確認。
- 列表左側的主圖示容器在 `group-hover` 時,應由淡色背景轉為 **實體主題色**
- 類別: `group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300`
- **文字同步變色**:
- 主標題文字在 `group-hover` 時應同步變色,以強化點擊引導。
- 類別: `group-hover:text-cyan-600 dark:group-hover:text-cyan-400 transition-colors`
### 分頁與列表控制項 (Pagination & Controls) 1. **參數配置**:
為了維持操作一致性所有列表的分頁與切換組件必須遵循以下「Luxury Jump」模式 - `title`: (選填) 預設為「確認刪除」。
- **統一高度**: 所有控制項(按鈕、下拉選單)固定為 `h-9` (36px) - `message`: (選填) 定義具體的刪除警告訊息(例如「您確定要永久刪除此帳號嗎?」)
- **筆數切換 (Limit Selector)**: 2. **視覺特徵**:
- 規範: **禁止**在表格上方Header/Toolbar重複放置筆數切換選單。統一收納於底部分頁欄位 - **背景**: `bg-slate-900/60 backdrop-blur-sm`
- **分頁導航 (Luxury Jump)**: - **容器**: `rounded-3xl shadow-2xl animate-luxury-in`
- 模式: 捨棄傳統頁碼按鈕,全端統一使用「跳轉選單」 - **圖示**: 警告圖示使用 `bg-amber-100/10 text-amber-600`
- 寬度: 下拉選單內部 Padding 為 `pl-4 pr-10` - **按鈕**: 刪除按鈕使用 `bg-rose-500` 搭配 `shadow-rose-200` 投影,取消按鈕使用 `bg-slate-100`
- 字體: 使用 `text-xs font-black tracking-widest` 3. **交互規範**:
- **指示文字**: - **禁止斜體 (No Italics)**: 彈窗標題與按鈕文字嚴禁使用 `italic`,保持直挺專業感。
- 行動端隱藏多餘詞彙僅保留「1 - 10 / 50」格式。
- 數字顏色對齊 `text-slate-600` (深色: `text-slate-300`)。
### 底部清單控制項 (Bottom List Controls) ```html
為了確保長列表的操作便利,清單底部應保持乾淨,統一由分頁與總數組件接管操作。 <!-- 使用範例 -->
<x-delete-confirm-modal :message="__('Are you sure you want to delete this account?')" />
### 標準操作按鈕 (Standard Action Icons) ```
表格內的操作欄位(如「編輯」、「刪除」、「詳情」)必須使用以下定義之 **「黃金標準 (Gold Standard)」**
- **共同樣式**:
- 容器: `p-2 rounded-lg bg-slate-50 dark:bg-slate-800`
- 主色: `text-slate-400`
- 邊框: `border border-transparent` (防閃爍處理)
- 過渡: `transition-all` (使用預設速度以確保俐落感)
- 圖示粗細: `stroke-width="2.5"`
- 尺寸: `w-4 h-4`
- **編輯按鈕 (Edit)**:
- 懸停特效: `hover:text-cyan-500 hover:bg-cyan-500/10 hover:border-cyan-500/20`
- SVG 路徑:
```html
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg>
```
- **查看詳情 (View/Detail)**:
- 懸停特效: `hover:text-indigo-500 hover:bg-indigo-500/10 hover:border-indigo-500/20`
- SVG 路徑:
```html
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"/></svg>
```
- **刪除按鈕 (Delete)**:
- 懸停特效: `hover:text-rose-500 hover:bg-rose-500/10 hover:border-rose-500/20`
- SVG 路徑:
```html
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>
```
## 10. 系統兼容性與標準化 (Compatibility & Standardization) ## 10. 系統兼容性與標準化 (Compatibility & Standardization)
@@ -296,6 +261,47 @@ description: 定義 Star Cloud 管理後台的「極簡奢華風」設計規範
- **權重載入 (Font Weights)**: 確保 HTML Header 載入了 `800``900` 權重,避免瀏覽器模擬出的假粗體。 - **權重載入 (Font Weights)**: 確保 HTML Header 載入了 `800``900` 權重,避免瀏覽器模擬出的假粗體。
- **清單資訊密度**: 對於高密度清單中的時間資訊,應優先使用 `font-black``tracking-widest` 來建立明確的「標籤感」,而非僅僅是「微縮文字」。 - **清單資訊密度**: 對於高密度清單中的時間資訊,應優先使用 `font-black``tracking-widest` 來建立明確的「標籤感」,而非僅僅是「微縮文字」。
---
## 12. 提示與告警規範 (Alerts & Notifications)
為了確保全站操作回饋的一致性與專業感,所有系統內部的提示(成功、錯誤、警告)必須遵循以下規範。
### 1. 懸浮式自動消失提示 (Auto-hiding Toasts)
- **視覺樣式**:
- 位置: 固定於畫面上方中央 (`fixed top-8 left-1/2 -translate-x-1/2`)。
- 特效: 毛玻璃背景 (`backdrop-blur-xl`)、圓角 (`rounded-2xl`)、軟陰影。
- 動畫: 滑入 (`translate-y-0`) / 滑出 (`-translate-y-4`),配合 `opacity` 變化。
- **型態定義**:
- **Success (成功)**: 使用 `emerald` 色系。
- **Error (錯誤)**: 使用 `rose` 色系。
- **時長規範**:
- 成功提示: 3 秒後消失。
- 錯誤提示: 5 秒後消失(提供使用者更多閱讀錯誤原因的時間)。
- **組件實作**: 統一調用 `<x-toast />`
### 2. 視窗內操作警告 (Inline Action Warnings)
- **適用場景**: 在 Modal 或編輯頁面中,提示可能導致風險的操作(如編輯自身角色)。
- **視覺樣式**:
- 背景: `bg-amber-500/10` (琥珀色)。
- 邊框: `border-amber-500/20`
- 進場動畫: `animate-luxury-in`
- **實作範例**:
```html
<div class="p-5 bg-amber-500/10 border border-amber-500/20 text-amber-600 rounded-2xl flex items-start gap-4 animate-luxury-in font-bold">
<!-- Icon & Text -->
</div>
```
### 3. 通用豪華確認與告警視窗 (General Luxury Modals)
**統一準則**: 所有的系統確認 (Confirm) 或重要告警 (Alert/Warning) **必須** 捨棄 `x-modal` 組件,改用 Section 9.4 定義的自定義 Div 結構。
- **警告模式 (Warning/Alert)**:
- 僅提供「關閉/確定」一個按鈕。
- 樣式同 9.4,但隱藏刪除 Form 與相關色彩。
- **確認模式 (Confirm)**:
- 提供「取消」與「執行」兩個按鈕。
- 執行按鈕顏色視操作性質而定 (Delete: `rose`, Save/Action: `cyan`)。
--- ---
> [!IMPORTANT] > [!IMPORTANT]
> **開發新功能前,必須確認 `app.css` 中的 `.btn-luxury-*` 系列組件是否滿足需求。** > **開發新功能前,必須確認 `app.css` 中的 `.btn-luxury-*` 系列組件是否滿足需求。**

View File

@@ -17,6 +17,12 @@ class PaymentConfigController extends AdminController
{ {
$per_page = $request->input('per_page', 20); $per_page = $request->input('per_page', 20);
$configs = PaymentConfig::query() $configs = PaymentConfig::query()
->when($request->search, function ($query, $search) {
$query->where('name', 'like', "%{$search}%")
->orWhereHas('company', function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%");
});
})
->with(['company', 'creator']) ->with(['company', 'creator'])
->latest() ->latest()
->paginate($per_page) ->paginate($per_page)

View File

@@ -32,9 +32,9 @@ class CompanyController extends Controller
$per_page = $request->input('per_page', 10); $per_page = $request->input('per_page', 10);
$companies = $query->latest()->paginate($per_page)->withQueryString(); $companies = $query->latest()->paginate($per_page)->withQueryString();
// 取得可供選擇的客戶角色範本 (is_system = 0, company_id = null) // 取得可供選擇的客戶角色範本 (系統層級的角色,排除 super-admin)
$template_roles = \App\Models\System\Role::where('is_system', 0) $template_roles = \App\Models\System\Role::whereNull('company_id')
->whereNull('company_id') ->where('name', '!=', 'super-admin')
->get(); ->get();
return view('admin.companies.index', compact('companies', 'template_roles')); return view('admin.companies.index', compact('companies', 'template_roles'));
@@ -86,23 +86,23 @@ class CompanyController extends Controller
]); ]);
// 角色初始化與克隆邏輯 (優先使用選擇的角色,否則使用預設) // 角色初始化與克隆邏輯 (優先使用選擇的角色,否則使用預設)
$selected_role_name = $validated['admin_role'] ?? '通用客戶角色範本'; $selected_role_name = $validated['admin_role'] ?? '客戶管理員角色模板';
$role_to_assign = '管理員'; $role_to_assign = null;
$template_role = \App\Models\System\Role::where('name', $selected_role_name) $template_role = \App\Models\System\Role::where('name', $selected_role_name)
->whereNull('company_id') ->whereNull('company_id')
->where('is_system', 0) ->where('name', '!=', 'super-admin')
->first(); ->first();
if ($template_role) { if ($template_role) {
// 克隆範本為該公司的「管理員」 // 克隆範本為該公司的「管理員」
$clonedRole = \App\Models\System\Role::query()->create([ $role_to_assign = \App\Models\System\Role::query()->create([
'name' => '管理員', 'name' => '管理員',
'guard_name' => 'web', 'guard_name' => 'web',
'company_id' => $company->id, 'company_id' => $company->id,
'is_system' => false, 'is_system' => false,
]); ]);
$clonedRole->syncPermissions($template_role->permissions); $role_to_assign->syncPermissions($template_role->getPermissionNames());
} else { } else {
// 如果找不到選定的角色範本,退而求其次嘗試指派現有角色 (通常不應發生) // 如果找不到選定的角色範本,退而求其次嘗試指派現有角色 (通常不應發生)
$role_to_assign = $selected_role_name; $role_to_assign = $selected_role_name;

View File

@@ -12,9 +12,9 @@ class PermissionController extends Controller
{ {
$per_page = request()->input('per_page', 10); $per_page = request()->input('per_page', 10);
$user = auth()->user(); $user = auth()->user();
$query = \App\Models\System\Role::query()->with(['permissions', 'users']); $query = \App\Models\System\Role::query()->with(['permissions', 'users', 'company']);
// 租戶隔離:租戶只能看到自己公司的角色 + 系統角色 (company_id is null) // 租戶隔離:租戶只能看到自己公司的角色
if (!$user->isSystemAdmin()) { if (!$user->isSystemAdmin()) {
$query->where('company_id', $user->company_id); $query->where('company_id', $user->company_id);
} }
@@ -24,10 +24,27 @@ class PermissionController extends Controller
$query->where('name', 'like', "%{$search}%"); $query->where('name', 'like', "%{$search}%");
} }
// 篩選:所屬單位 (僅限系統管理員)
if ($user->isSystemAdmin() && request()->filled('company_id')) {
if (request()->company_id === 'system') {
$query->where('is_system', true);
} else {
$query->where('company_id', request()->company_id);
}
}
$roles = $query->latest()->paginate($per_page)->withQueryString(); $roles = $query->latest()->paginate($per_page)->withQueryString();
$all_permissions = \Spatie\Permission\Models\Permission::all() $companies = $user->isSystemAdmin() ? \App\Models\System\Company::all() : collect();
// 權限遞迴約束:租戶管理員只能看到並指派自己擁有的權限
$permissionQuery = \Spatie\Permission\Models\Permission::query();
if (!$user->isSystemAdmin()) {
$permissionQuery->whereIn('name', $user->getAllPermissions()->pluck('name'));
}
$all_permissions = $permissionQuery->get()
->filter(function($perm) { ->filter(function($perm) {
// 排除子項目的權限,只顯示主選單權限 // 排除子項目,只顯示主權限
$excluded = [ $excluded = [
'menu.basic.machines', 'menu.basic.machines',
'menu.basic.payment-configs', 'menu.basic.payment-configs',
@@ -47,7 +64,8 @@ class PermissionController extends Controller
// 根據路由決定標題 // 根據路由決定標題
$title = request()->routeIs('*.sub-account-roles') ? __('Sub Account Roles') : __('Role Settings'); $title = request()->routeIs('*.sub-account-roles') ? __('Sub Account Roles') : __('Role Settings');
return view('admin.permission.roles', compact('roles', 'all_permissions', 'title')); $currentUserRoleIds = $user->roles->pluck('id')->toArray();
return view('admin.permission.roles', compact('roles', 'all_permissions', 'title', 'currentUserRoleIds', 'companies'));
} }
/** /**
@@ -78,6 +96,15 @@ class PermissionController extends Controller
if (!empty($validated['permissions'])) { if (!empty($validated['permissions'])) {
$perms = $validated['permissions']; $perms = $validated['permissions'];
// 權限遞迴約束驗證:確保指派的權限是操作者權限的子集
if (!auth()->user()->isSystemAdmin()) {
$currentUserPerms = auth()->user()->getAllPermissions()->pluck('name');
if (collect($perms)->diff($currentUserPerms)->isNotEmpty()) {
return redirect()->back()->with('error', __('You cannot assign permissions you do not possess.'));
}
}
// 如果不是系統角色,排除主選單的系統權限 // 如果不是系統角色,排除主選單的系統權限
if (!$is_system) { if (!$is_system) {
$perms = array_diff($perms, ['menu.basic-settings', 'menu.permissions']); $perms = array_diff($perms, ['menu.basic-settings', 'menu.permissions']);
@@ -128,6 +155,15 @@ class PermissionController extends Controller
]); ]);
$perms = $validated['permissions'] ?? []; $perms = $validated['permissions'] ?? [];
// 權限遞迴約束驗證
if (!auth()->user()->isSystemAdmin()) {
$currentUserPerms = auth()->user()->getAllPermissions()->pluck('name');
if (collect($perms)->diff($currentUserPerms)->isNotEmpty()) {
return redirect()->back()->with('error', __('You cannot assign permissions you do not possess.'));
}
}
// 如果不是系統角色,排除主選單的系統權限 // 如果不是系統角色,排除主選單的系統權限
if (!$is_system) { if (!$is_system) {
$perms = array_diff($perms, ['menu.basic-settings', 'menu.permissions']); $perms = array_diff($perms, ['menu.basic-settings', 'menu.permissions']);
@@ -188,9 +224,9 @@ class PermissionController extends Controller
$per_page = $request->input('per_page', 10); $per_page = $request->input('per_page', 10);
$users = $query->latest()->paginate($per_page)->withQueryString(); $users = $query->latest()->paginate($per_page)->withQueryString();
$companies = auth()->user()->isSystemAdmin() ? \App\Models\System\Company::all() : collect(); $companies = auth()->user()->isSystemAdmin() ? \App\Models\System\Company::all() : collect();
$roles_query = \App\Models\System\Role::where('name', '!=', 'super-admin'); $roles_query = \App\Models\System\Role::query();
if (!auth()->user()->isSystemAdmin()) { if (!auth()->user()->isSystemAdmin()) {
$roles_query->where('company_id', auth()->user()->company_id); $roles_query->forCompany(auth()->user()->company_id);
} }
$roles = $roles_query->get(); $roles = $roles_query->get();
@@ -231,9 +267,9 @@ class PermissionController extends Controller
// 驗證角色與公司的匹配性 (RBAC Safeguard) // 驗證角色與公司的匹配性 (RBAC Safeguard)
if ($company_id !== null) { if ($company_id !== null) {
// 如果是租戶帳號,不能選各項系統角色 (is_system = 1) // 如果是租戶帳號,不能選超級管理員角色
if ($role->is_system) { if ($role->is_system && $role->name === 'super-admin') {
return redirect()->back()->with('error', __('System roles cannot be assigned to tenant accounts.')); return redirect()->back()->with('error', __('Super-admin role cannot be assigned to tenant accounts.'));
} }
// 如果角色有特定的 company_id必須匹配 // 如果角色有特定的 company_id必須匹配
if ($role->company_id !== null && $role->company_id != $company_id) { if ($role->company_id !== null && $role->company_id != $company_id) {
@@ -247,10 +283,9 @@ class PermissionController extends Controller
} }
// 角色初始化與克隆邏輯 (只有 super-admin 在幫空白公司開帳號時觸發) // 角色初始化與克隆邏輯 (只有 super-admin 在幫空白公司開帳號時觸發)
$role_to_assign = $validated['role'];
$company_id = auth()->user()->isSystemAdmin() ? ($validated['company_id'] ?? null) : auth()->user()->company_id; $company_id = auth()->user()->isSystemAdmin() ? ($validated['company_id'] ?? null) : auth()->user()->company_id;
if ($company_id && $role && !$role->is_system && $role->company_id === null) { if ($company_id && $role && $role->company_id === null && $role->name !== 'super-admin') {
// 檢查該公司是否已有名為「管理員」的角色 // 檢查該公司是否已有名為「管理員」的角色
$existingRole = \App\Models\System\Role::where('company_id', $company_id) $existingRole = \App\Models\System\Role::where('company_id', $company_id)
->where('name', '管理員') ->where('name', '管理員')
@@ -258,17 +293,17 @@ class PermissionController extends Controller
if (!$existingRole) { if (!$existingRole) {
// 克隆範本為該公司的「管理員」 // 克隆範本為該公司的「管理員」
$clonedRole = \App\Models\System\Role::query()->create([ $newRole = \App\Models\System\Role::query()->create([
'name' => '管理員', 'name' => '管理員',
'guard_name' => 'web', 'guard_name' => 'web',
'company_id' => $company_id, 'company_id' => $company_id,
'is_system' => false, 'is_system' => false,
]); ]);
$clonedRole->syncPermissions($role->permissions); $newRole->syncPermissions($role->getPermissionNames());
$role_to_assign = '管理員'; $role = $newRole;
} else { } else {
// 如果已存在名為「管理員」的角色,則直接使用它 // 如果已存在名為「管理員」的角色,則直接使用它
$role_to_assign = '管理員'; $role = $existingRole;
} }
} }
@@ -279,10 +314,10 @@ class PermissionController extends Controller
'password' => \Illuminate\Support\Facades\Hash::make($validated['password']), 'password' => \Illuminate\Support\Facades\Hash::make($validated['password']),
'status' => $validated['status'], 'status' => $validated['status'],
'company_id' => $company_id, 'company_id' => $company_id,
'phone' => $validated['phone'], 'phone' => $validated['phone'] ?? null,
]); ]);
$user->assignRole($role_to_assign); $user->assignRole($role);
return redirect()->back()->with('success', __('Account created successfully.')); return redirect()->back()->with('success', __('Account created successfully.'));
} }
@@ -325,8 +360,8 @@ class PermissionController extends Controller
// 驗證角色與公司的匹配性 (RBAC Safeguard) // 驗證角色與公司的匹配性 (RBAC Safeguard)
if ($user->id !== auth()->id()) { // 排除編輯自己 (super-admin 有特殊邏輯) if ($user->id !== auth()->id()) { // 排除編輯自己 (super-admin 有特殊邏輯)
if ($target_company_id !== null) { if ($target_company_id !== null) {
if ($roleObj->is_system) { if ($roleObj->is_system && $roleObj->name === 'super-admin') {
return redirect()->back()->with('error', __('System roles cannot be assigned to tenant accounts.')); return redirect()->back()->with('error', __('Super-admin role cannot be assigned to tenant accounts.'));
} }
if ($roleObj->company_id !== null && $roleObj->company_id != $target_company_id) { if ($roleObj->company_id !== null && $roleObj->company_id != $target_company_id) {
return redirect()->back()->with('error', __('This role belongs to another company and cannot be assigned.')); return redirect()->back()->with('error', __('This role belongs to another company and cannot be assigned.'));
@@ -343,7 +378,7 @@ class PermissionController extends Controller
'username' => $validated['username'], 'username' => $validated['username'],
'email' => $validated['email'], 'email' => $validated['email'],
'status' => $validated['status'], 'status' => $validated['status'],
'phone' => $validated['phone'], 'phone' => $validated['phone'] ?? null,
]; ];
if (auth()->user()->isSystemAdmin()) { if (auth()->user()->isSystemAdmin()) {
@@ -361,26 +396,25 @@ class PermissionController extends Controller
} }
// 角色初始化與克隆邏輯 // 角色初始化與克隆邏輯
$role_to_assign = $validated['role'];
$target_company_id = auth()->user()->isSystemAdmin() ? ($validated['company_id'] ?? null) : auth()->user()->company_id; $target_company_id = auth()->user()->isSystemAdmin() ? ($validated['company_id'] ?? null) : auth()->user()->company_id;
if ($target_company_id && $roleObj && !$roleObj->is_system && $roleObj->company_id === null) { if ($target_company_id && $roleObj && $roleObj->company_id === null && $roleObj->name !== 'super-admin') {
// 檢查該公司是否已有名為「管理員」的角色 // 檢查該公司是否已有名為「管理員」的角色
$existingRole = \App\Models\System\Role::where('company_id', $target_company_id) $existingRole = \App\Models\System\Role::where('company_id', $target_company_id)
->where('name', '管理員') ->where('name', '管理員')
->first(); ->first();
if (!$existingRole) { if (!$existingRole) {
$clonedRole = \App\Models\System\Role::query()->create([ $newRole = \App\Models\System\Role::query()->create([
'name' => '管理員', 'name' => '管理員',
'guard_name' => 'web', 'guard_name' => 'web',
'company_id' => $target_company_id, 'company_id' => $target_company_id,
'is_system' => false, 'is_system' => false,
]); ]);
$clonedRole->syncPermissions($roleObj->permissions); $newRole->syncPermissions($roleObj->getPermissionNames());
$role_to_assign = '管理員'; $roleObj = $newRole;
} else { } else {
$role_to_assign = '管理員'; $roleObj = $existingRole;
} }
} }
@@ -390,7 +424,7 @@ class PermissionController extends Controller
if ($user->id === auth()->id() && auth()->user()->isSystemAdmin()) { if ($user->id === auth()->id() && auth()->user()->isSystemAdmin()) {
$user->syncRoles(['super-admin']); $user->syncRoles(['super-admin']);
} else { } else {
$user->syncRoles([$role_to_assign]); $user->syncRoles([$roleObj]);
} }
return redirect()->back()->with('success', __('Account updated successfully.')); return redirect()->back()->with('success', __('Account updated successfully.'));

View File

@@ -24,6 +24,6 @@ class PasswordController extends Controller
'password' => Hash::make($validated['password']), 'password' => Hash::make($validated['password']),
]); ]);
return back()->with('status', 'password-updated'); return back()->with('success', __('Password updated successfully.'));
} }
} }

View File

@@ -39,7 +39,7 @@ class ProfileController extends Controller
$user->save(); $user->save();
return Redirect::route('profile.edit')->with('status', 'profile-updated'); return Redirect::route('profile.edit')->with('success', __('Profile updated successfully.'));
} }
/** /**

View File

@@ -13,6 +13,10 @@ class Role extends SpatieRole
'is_system', 'is_system',
]; ];
protected $casts = [
'is_system' => 'boolean',
];
/** /**
* Get the company that owns the role. * Get the company that owns the role.
*/ */

View File

@@ -3,7 +3,7 @@
namespace Database\Seeders; namespace Database\Seeders;
use Illuminate\Database\Seeder; use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role; use App\Models\System\Role;
use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Permission;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
@@ -41,14 +41,14 @@ class RoleSeeder extends Seeder
// 建立角色 // 建立角色
$superAdmin = Role::updateOrCreate( $superAdmin = Role::updateOrCreate(
['name' => 'super-admin'], ['name' => 'super-admin', 'company_id' => null],
['is_system' => true] ['is_system' => true, 'guard_name' => 'web']
); );
$superAdmin->syncPermissions(Permission::all()); $superAdmin->syncPermissions(Permission::all());
$tenantAdmin = Role::updateOrCreate( $tenantAdmin = Role::updateOrCreate(
['name' => '客戶管理員'], ['name' => '客戶管理員角色模板', 'company_id' => null],
['is_system' => false] ['is_system' => true, 'guard_name' => 'web']
); );
$tenantAdmin->syncPermissions([ $tenantAdmin->syncPermissions([
'menu.members', 'menu.members',

View File

@@ -1,381 +1,483 @@
{ {
"Account Settings": "Account Settings", "A new verification link has been sent to your email address.": "A new verification link has been sent to your email address.",
"Manage your profile information, security settings, and login history": "Manage your profile information, security settings, and login history", "Account created successfully.": "Account created successfully.",
"Profile Information": "Profile Information", "Account deleted successfully.": "Account deleted successfully.",
"Update your account's profile information and email address.": "Update your account's profile information and email address.",
"Update Password": "Update Password",
"Ensure your account is using a long, random password to stay secure.": "Ensure your account is using a long, random password to stay secure.",
"Delete Account": "Delete Account",
"Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.": "Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.",
"Are you sure you want to delete your account?": "Are you sure you want to delete your account?",
"Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.": "Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.",
"Login History": "Login History",
"Name": "Name",
"Phone": "Phone",
"Email": "Email",
"Current Password": "Current Password",
"New Password": "New Password",
"Confirm Password": "Confirm Password",
"Save": "Save",
"Saved.": "Saved.",
"Update": "Update",
"Cancel": "Cancel",
"Confirm": "Confirm",
"Danger Zone: Delete Account": "Danger Zone: Delete Account",
"Permanently Delete Account": "Permanently Delete Account",
"Password": "Password",
"Enter your password to confirm": "Enter your password to confirm",
"Dashboard": "Dashboard",
"Connectivity Status": "Connectivity Status",
"Real-time status monitoring": "Real-time status monitoring",
"LIVE": "LIVE",
"Online Machines": "Online Machines",
"Offline Machines": "Offline Machines",
"Alerts Pending": "Alerts Pending",
"Total Connected": "Total Connected",
"Monthly Transactions": "Monthly Transactions",
"Monthly cumulative revenue overview": "Monthly cumulative revenue overview",
"Today's Transactions": "Today's Transactions",
"vs Yesterday": "vs Yesterday",
"Yesterday": "Yesterday",
"Day Before": "Day Before",
"Machine Status List": "Machine Status List",
"Total items": "Total items: :count",
"Real-time monitoring across all machines": "Real-time monitoring across all machines",
"Quick search...": "Quick search...",
"Machine Info": "Machine Info",
"Running Status": "Running Status",
"Today Cumulative Sales": "Today Cumulative Sales",
"Current Stock": "Current Stock",
"Last Signal": "Last Signal",
"Alert Summary": "Alert Summary",
"No alert summary": "No alert summary",
"No data available": "No data available",
"Showing :from to :to of :total items": "Showing :from to :to of :total items",
"Previous": "Previous",
"Next": "Next",
"Profile Settings": "Profile Settings",
"Profile": "Profile",
"Member Management": "Member Management",
"Member List": "Member List",
"Membership Tiers": "Membership Tiers",
"Deposit Bonus": "Deposit Bonus",
"Point Rules": "Point Rules",
"Gift Definitions": "Gift Definitions",
"Machine Management": "Machine Management",
"Machine Logs": "Machine Logs",
"Machine List": "Machine List",
"Machine Permissions": "Machine Permissions",
"Utilization Rate": "Utilization Rate",
"Expiry Management": "Expiry Management",
"Maintenance Records": "Maintenance Records",
"APP Management": "APP Management",
"UI Elements": "UI Elements",
"Helper": "Helper",
"Questionnaire": "Questionnaire",
"Games": "Games",
"Timer": "Timer",
"Warehouse Management": "Warehouse Management",
"Warehouse List (All)": "Warehouse List (All)",
"Warehouse List (Individual)": "Warehouse List (Individual)",
"Stock Management": "Stock Management",
"Transfers": "Transfers",
"Purchases": "Purchases",
"Replenishments": "Replenishments",
"Replenishment Records": "Replenishment Records",
"Machine Stock": "Machine Stock",
"Staff Stock": "Staff Stock",
"Returns": "Returns",
"Sales Management": "Sales Management",
"Sales Records": "Sales Records",
"Pickup Codes": "Pickup Codes",
"Orders": "Orders",
"Promotions": "Promotions",
"Pass Codes": "Pass Codes",
"Store Gifts": "Store Gifts",
"Analysis Management": "Analysis Management",
"Change Stock": "Change Stock",
"Machine Reports": "Machine Reports",
"Product Reports": "Product Reports",
"Survey Analysis": "Survey Analysis",
"Audit Management": "Audit Management",
"Purchase Audit": "Purchase Audit",
"Transfer Audit": "Transfer Audit",
"Replenishment Audit": "Replenishment Audit",
"Data Configuration": "Data Configuration",
"Product Management": "Product Management",
"Advertisement Management": "Advertisement Management",
"Admin Sellable Products": "Admin Sellable Products",
"Account Management": "Account Management", "Account Management": "Account Management",
"Sub Accounts": "Sub Accounts", "Account Settings": "Account Settings",
"Sub Account Roles": "Sub Account Roles",
"Point Settings": "Point Settings",
"Badge Settings": "Badge Settings",
"Remote Management": "Remote Management",
"Machine Restart": "Machine Restart",
"Card Reader Restart": "Card Reader Restart",
"Remote Checkout": "Remote Checkout",
"Remote Lock": "Remote Lock",
"Remote Change": "Remote Change",
"Remote Dispense": "Remote Dispense",
"Line Management": "Line Management",
"Line Members": "Line Members",
"Line Machines": "Line Machines",
"Line Products": "Line Products",
"Line Official Account": "Line Official Account",
"Line Orders": "Line Orders",
"Line Coupons": "Line Coupons",
"Reservation System": "Reservation System",
"Reservation Members": "Reservation Members",
"Store Management": "Store Management",
"Time Slots": "Time Slots",
"Venue Management": "Venue Management",
"Coupons": "Coupons",
"Reservations": "Reservations",
"Order Management": "Order Management",
"Special Permission": "Special Permission",
"Clear Stock": "Clear Stock",
"APK Versions": "APK Versions",
"Discord Notifications": "Discord Notifications",
"Basic Settings": "Basic Settings",
"Machine Settings": "Machine Settings",
"Permission Settings": "Permission Settings",
"APP Features": "APP Features",
"Sales": "Sales",
"Others": "Others",
"AI Prediction": "AI Prediction",
"Roles": "Role Permissions",
"Role Management": "Role Permission Management",
"Define and manage security roles and permissions.": "Define and manage security roles and permissions.",
"Search roles...": "Search roles...",
"No permissions": "No permissions",
"No roles found.": "No roles found.",
"Create Role": "Create Role",
"Edit Role": "Edit Role",
"Update existing role and permissions.": "Update existing role and permissions.",
"Create a new role and assign permissions.": "Create a new role and assign permissions.",
"Enter role name": "Enter role name",
"Add Role": "Add Role",
"Role Name": "Role Name",
"Type": "Type",
"Permissions": "Permissions",
"Users": "Users",
"System role name cannot be modified.": "System role name cannot be modified.",
"The Super Admin role name cannot be modified.": "The Super Admin role name cannot be modified.",
"System Level": "System Level",
"Company Level": "Company Level",
"Global roles accessible by all administrators.": "Global roles accessible by all administrators.",
"Roles scoped to specific customer companies.": "Roles scoped to specific customer companies.",
"members": "Member Management",
"machines": "Machine Management",
"app": "APP Management",
"warehouses": "Warehouse Management",
"sales": "Sales Management",
"analysis": "Analysis Management",
"audit": "Audit Management",
"data-config": "Data Configuration",
"remote": "Remote Management",
"line": "Line Management",
"reservation": "Reservation System",
"special-permission": "Special Permission",
"companies": "Customer Management",
"accounts": "Account Management",
"roles": "Role Permissions",
"Role Permissions": "Role Permissions",
"Role Settings": "Role Permissions",
"No login history yet": "No login history yet",
"Signed in as": "Signed in as",
"Logout": "Logout",
"Joined": "Joined",
"Recent Login": "Recent Login",
"Total Logins": "Total Logins",
"Account Status": "Account Status", "Account Status": "Account Status",
"Active": "Active", "Account updated successfully.": "Account updated successfully.",
"Customer Management": "Customer Management", "accounts": "Account Management",
"Manage all tenant accounts and validity": "Manage all tenant accounts and validity",
"Add Customer": "Add Customer",
"Total Customers": "Total Customers",
"Expired / Disabled": "Expired / Disabled",
"Search customers...": "Search customers...",
"All": "All",
"Disabled": "Disabled",
"Customer Info": "Customer Info",
"Accounts / Machines": "Accounts / Machines", "Accounts / Machines": "Accounts / Machines",
"Valid Until": "Valid Until",
"Actions": "Actions",
"Permanent": "Permanent",
"Are you sure to delete this customer?": "Are you sure to delete this customer?",
"No customers found": "No customers found",
"Edit Customer": "Edit Customer",
"Update Customer": "Update Customer",
"Create": "Create",
"Company Name": "Company Name",
"Company Code": "Company Code",
"Tax ID (Optional)": "Tax ID (Optional)",
"Status": "Status",
"Contact Name": "Contact Name",
"Contact Phone": "Contact Phone",
"Contact Email": "Contact Email",
"Notes": "Notes",
"Customer created successfully.": "Customer created successfully.",
"Customer updated successfully.": "Customer updated successfully.",
"Customer deleted successfully.": "Customer deleted successfully.",
"Cannot delete company with active accounts.": "Cannot delete company with active accounts.",
"Contract Until (Optional)": "Contract Until (Optional)",
"Company Information": "Company Information",
"Initial Admin Account": "Initial Admin Account",
"Optional": "Optional",
"Username": "Username",
"Enter login ID": "Enter login ID",
"Min 8 characters": "Min 8 characters",
"Admin display name": "Admin display name",
"Initial Role": "Initial Role",
"Contact & Details": "Contact & Details",
"e.g. Taiwan Star": "e.g. Taiwan Star",
"e.g. TWSTAR": "e.g. TWSTAR",
"Manage administrative and tenant accounts": "Manage administrative and tenant accounts",
"Add Account": "Add Account",
"All Companies": "All Companies",
"User Info": "User Info",
"Belongs To": "Belongs To",
"Role": "Role",
"SYSTEM": "SYSTEM",
"No users found": "No users found",
"Data Configuration Permissions": "Data Configuration Permissions",
"Sales Permissions": "Sales Permissions",
"Machine Management Permissions": "Machine Management Permissions",
"Warehouse Permissions": "Warehouse Permissions",
"Analysis Permissions": "Analysis Permissions",
"Audit Permissions": "Audit Permissions",
"Remote Permissions": "Remote Permissions",
"Line Permissions": "Line Permissions",
"Company": "Company",
"Save Changes": "Save Changes",
"User": "User",
"Admin": "Admin",
"Super Admin": "Super Admin",
"e.g. John Doe": "e.g. John Doe",
"e.g. johndoe": "e.g. johndoe",
"Search users...": "Search users...",
"Admin Name": "Admin Name",
"New Password (leave blank to keep current)": "New Password (leave blank to keep current)",
"Are you sure you want to delete this account?": "Are you sure you want to delete this account?",
"Show": "Show",
"to": "to",
"of": "of",
"items": "items",
"Showing": "Showing",
"Monitor events and system activity across your vending fleet.": "Monitor events and system activity across your vending fleet.",
"All Machines": "All Machines",
"All Levels": "All Levels",
"Timestamp": "Timestamp",
"Message Content": "Message Content",
"No matching logs found": "No matching logs found",
"Unknown": "Unknown",
"Info": "Info",
"Warning": "Warning",
"basic-settings": "Basic Settings",
"permissions": "Permission Settings",
"Error": "Error",
"Machine Model Settings": "Machine Model Settings",
"Machine Model": "Machine Model",
"Model Name": "Model Name",
"Machine Count": "Machine Count",
"Add Machine Model": "Add Machine Model",
"Edit Machine Model": "Edit Machine Model",
"Machine model created successfully.": "Machine model created successfully.",
"Machine model updated successfully.": "Machine model updated successfully.",
"Machine model deleted successfully.": "Machine model deleted successfully.",
"Cannot delete model that is currently in use by machines.": "Cannot delete model that is currently in use by machines.",
"Machine Details": "Machine Details",
"Create Machine": "Create Machine",
"Edit Machine": "Edit Machine",
"Basic Information": "Basic Information",
"Location": "Location",
"Temperature": "Temperature",
"Firmware Version": "Firmware Version",
"Last Heartbeat": "Last Heartbeat",
"Never Connected": "Never Connected",
"View Logs": "View Logs",
"Real-time Operation Logs (Last 50)": "Real-time Operation Logs (Last 50)",
"All Times System Timezone": "All times are in system timezone",
"Level": "Level",
"Logs": "Logs",
"Time": "Time",
"Message": "Message",
"Online": "Online",
"Offline": "Offline",
"Connecting...": "Connecting...",
"No logs found": "No logs found",
"Management of operational parameters and models": "Management of operational parameters and models",
"ECPay Invoice Settings Description": "ECPay Electronic Invoice Settings",
"E.SUN QR Scan Settings Description": "E.SUN Bank QR Scan Payment Settings",
"LINE Pay Direct Settings Description": "LINE Pay Official Direct Connection Settings",
"TapPay Integration Settings Description": "TapPay Payment Integration Settings",
"Merchant IDs": "Merchant IDs",
"Parameters": "Parameters",
"Hardware & Network": "Hardware & Network",
"Serial & Version": "Serial & Version",
"Heartbeat": "Heartbeat",
"Heating Range": "Heating Range",
"API Token": "API Token",
"No location set": "No location set",
"Close Panel": "Close Panel",
"Operations": "Operations",
"Operational Parameters": "Operational Parameters",
"Hardware & Slots": "Hardware & Slots",
"Slot Mechanism (default: Conveyor, check for Spring)": "Slot Mechanism (default: Conveyor, check for Spring)",
"Payment & Invoice": "Payment & Invoice",
"Payment Config": "Payment Config",
"Invoice Status": "Invoice Status",
"No Invoice": "No Invoice",
"Default Donate": "Default Donate",
"Default Not Donate": "Default Not Donate",
"Member & External": "Member & External",
"Welcome Gift": "Welcome Gift",
"Enabled/Disabled": "Enabled/Disabled",
"Member System": "Member System",
"Machine Images": "Machine Images",
"No images uploaded": "No images uploaded",
"Upload New Images": "Upload New Images",
"Max 3": "Max 3",
"Uploading new images will replace all existing images.": "Uploading new images will replace all existing images.",
"Search machines...": "Search machines...",
"Search models...": "Search models...",
"Card Reader": "Card Reader",
"Owner": "Owner",
"Action": "Action", "Action": "Action",
"Items": "Items", "Actions": "Actions",
"View Details": "View Details", "Active": "Active",
"Edit Settings": "Edit Settings", "Add Account": "Add Account",
"Are you sure?": "Are you sure?", "Add Customer": "Add Customer",
"Serial No": "Serial No", "Add Machine": "Add Machine",
"Select Owner": "Select Owner", "Add Machine Model": "Add Machine Model",
"Select Model": "Select Model", "Add Role": "Add Role",
"Machines": "Machines", "Admin": "Admin",
"Models": "Models", "Admin display name": "Admin display name",
"Edit": "Edit", "Admin Name": "Admin Name",
"Delete": "Delete", "Admin Sellable Products": "Admin Sellable Products",
"None": "None", "Administrator": "Administrator",
"Select Company": "Select Company", "Advertisement Management": "Advertisement Management",
"e.g., Company Standard Pay": "e.g., Company Standard Pay", "Affiliation": "Affiliation",
"AI Prediction": "AI Prediction",
"Alert Summary": "Alert Summary",
"Alerts Pending": "Alerts Pending",
"All": "All",
"All Affiliations": "All Affiliations",
"All Companies": "All Companies",
"All Levels": "All Levels",
"All Machines": "All Machines",
"All Times System Timezone": "All times are in system timezone",
"analysis": "Analysis Management",
"Analysis Management": "Analysis Management",
"Analysis Permissions": "Analysis Permissions",
"API Token": "API Token",
"APK Versions": "APK Versions",
"app": "APP Management",
"APP Features": "APP Features",
"APP Management": "APP Management",
"APP_ID": "APP_ID", "APP_ID": "APP_ID",
"APP_KEY": "APP_KEY", "APP_KEY": "APP_KEY",
"Are you sure to delete this customer?": "Are you sure to delete this customer?",
"Are you sure you want to delete this account?": "Are you sure you want to delete this account?",
"Are you sure you want to delete this account? This action cannot be undone.": "Are you sure you want to delete this account? This action cannot be undone.",
"Are you sure you want to delete this configuration? This action cannot be undone.": "Are you sure you want to delete this configuration? This action cannot be undone.",
"Are you sure you want to delete this item? This action cannot be undone.": "Are you sure you want to delete this item? This action cannot be undone.",
"Are you sure you want to delete this role? This action cannot be undone.": "Are you sure you want to delete this role? This action cannot be undone.",
"Are you sure you want to delete your account?": "Are you sure you want to delete your account?",
"Are you sure?": "Are you sure?",
"audit": "Audit Management",
"Audit Management": "Audit Management",
"Audit Permissions": "Audit Permissions",
"Avatar updated successfully.": "Avatar updated successfully.",
"Badge Settings": "Badge Settings",
"Basic Information": "Basic Information",
"Basic Settings": "Basic Settings",
"basic-settings": "Basic Settings",
"Belongs To": "Belongs To",
"Belongs To Company": "Belongs To Company",
"Cancel": "Cancel",
"Cannot delete company with active accounts.": "Cannot delete company with active accounts.",
"Cannot delete model that is currently in use by machines.": "Cannot delete model that is currently in use by machines.",
"Cannot Delete Role": "Cannot Delete Role",
"Cannot delete role with active users.": "Cannot delete role with active users.",
"Card Reader": "Card Reader",
"Card Reader No": "Card Reader No",
"Card Reader Restart": "Card Reader Restart",
"Card Reader Seconds": "Card Reader Seconds",
"Change": "Change",
"Change Stock": "Change Stock",
"ChannelId": "ChannelId", "ChannelId": "ChannelId",
"ChannelSecret": "ChannelSecret", "ChannelSecret": "ChannelSecret",
"Checkout Time 1": "Checkout Time 1",
"Checkout Time 2": "Checkout Time 2",
"Clear Stock": "Clear Stock",
"Click here to re-send the verification email.": "Click here to re-send the verification email.",
"Click to upload": "Click to upload",
"Close Panel": "Close Panel",
"companies": "Customer Management",
"Company": "Company",
"Company Code": "Company Code",
"Company Information": "Company Information",
"Company Level": "Company Level",
"Company Name": "Company Name",
"Configuration Name": "Configuration Name",
"Confirm": "Confirm",
"Confirm Deletion": "Confirm Deletion",
"Confirm Password": "Confirm Password",
"Connecting...": "Connecting...",
"Connectivity Status": "Connectivity Status",
"Contact & Details": "Contact & Details",
"Contact Email": "Contact Email",
"Contact Name": "Contact Name",
"Contact Phone": "Contact Phone",
"Contract Until (Optional)": "Contract Until (Optional)",
"Coupons": "Coupons",
"Create": "Create",
"Create a new role and assign permissions.": "Create a new role and assign permissions.",
"Create Config": "Create Config",
"Create Machine": "Create Machine",
"Create Payment Config": "Create Payment Config",
"Create Role": "Create Role",
"Current Password": "Current Password",
"Current Stock": "Current Stock",
"Customer created successfully.": "Customer created successfully.",
"Customer deleted successfully.": "Customer deleted successfully.",
"Customer Info": "Customer Info",
"Customer Management": "Customer Management",
"Customer Payment Config": "Customer Payment Config",
"Customer updated successfully.": "Customer updated successfully.",
"Danger Zone: Delete Account": "Danger Zone: Delete Account",
"Dashboard": "Dashboard",
"Data Configuration": "Data Configuration",
"Data Configuration Permissions": "Data Configuration Permissions",
"data-config": "Data Configuration",
"Day Before": "Day Before",
"Default Donate": "Default Donate",
"Default Not Donate": "Default Not Donate",
"Define and manage security roles and permissions.": "Define and manage security roles and permissions.",
"Define new third-party payment parameters": "Define new third-party payment parameters",
"Delete": "Delete",
"Delete Account": "Delete Account",
"Delete Permanently": "Delete Permanently",
"Deposit Bonus": "Deposit Bonus",
"Detail": "Detail",
"Disabled": "Disabled",
"Discord Notifications": "Discord Notifications",
"e.g. John Doe": "e.g. John Doe",
"e.g. johndoe": "e.g. johndoe",
"e.g. Taiwan Star": "e.g. Taiwan Star",
"e.g. TWSTAR": "e.g. TWSTAR",
"e.g., Company Standard Pay": "e.g., Company Standard Pay",
"e.g., Taipei Station": "e.g., Taipei Station",
"E.SUN QR Scan": "E.SUN QR Scan",
"E.SUN QR Scan Settings Description": "E.SUN Bank QR Scan Payment Settings",
"EASY_MERCHANT_ID": "EASY_MERCHANT_ID", "EASY_MERCHANT_ID": "EASY_MERCHANT_ID",
"ECPay Invoice": "ECPay Invoice",
"ECPay Invoice Settings Description": "ECPay Electronic Invoice Settings",
"Edit": "Edit",
"Edit Account": "Edit Account",
"Edit Customer": "Edit Customer",
"Edit Machine": "Edit Machine",
"Edit Machine Model": "Edit Machine Model",
"Edit Payment Config": "Edit Payment Config",
"Edit Role": "Edit Role",
"Edit Settings": "Edit Settings",
"Email": "Email",
"Enabled/Disabled": "Enabled/Disabled",
"Ensure your account is using a long, random password to stay secure.": "Ensure your account is using a long, random password to stay secure.",
"Enter login ID": "Enter login ID",
"Enter machine location": "Enter machine location",
"Enter machine name": "Enter machine name",
"Enter model name": "Enter model name",
"Enter role name": "Enter role name",
"Enter serial number": "Enter serial number",
"Enter your password to confirm": "Enter your password to confirm",
"Error": "Error",
"Expired / Disabled": "Expired / Disabled",
"Expiry Management": "Expiry Management",
"Failed to update machine images: ": "Failed to update machine images: ",
"files selected": "files selected",
"Firmware Version": "Firmware Version",
"Full Name": "Full Name",
"Games": "Games",
"Gift Definitions": "Gift Definitions",
"Global roles accessible by all administrators.": "Global roles accessible by all administrators.",
"Got it": "Got it",
"Hardware & Network": "Hardware & Network",
"Hardware & Slots": "Hardware & Slots",
"HashIV": "HashIV", "HashIV": "HashIV",
"HashKey": "HashKey", "HashKey": "HashKey",
"Heartbeat": "Heartbeat",
"Heating End Time": "Heating End Time",
"Heating Range": "Heating Range",
"Heating Start Time": "Heating Start Time",
"Helper": "Helper",
"Info": "Info",
"Initial Admin Account": "Initial Admin Account",
"Initial Role": "Initial Role",
"Invoice Status": "Invoice Status",
"items": "items",
"Items": "Items",
"JKO_MERCHANT_ID": "JKO_MERCHANT_ID", "JKO_MERCHANT_ID": "JKO_MERCHANT_ID",
"john@example.com": "john@example.com",
"Joined": "Joined",
"Key": "Key", "Key": "Key",
"Key No": "Key No",
"Last Heartbeat": "Last Heartbeat",
"Last Signal": "Last Signal",
"Last Updated": "Last Updated",
"Level": "Level",
"line": "Line Management",
"Line Coupons": "Line Coupons",
"Line Machines": "Line Machines",
"Line Management": "Line Management",
"Line Members": "Line Members",
"Line Official Account": "Line Official Account",
"Line Orders": "Line Orders",
"LINE Pay Direct": "LINE Pay Direct",
"LINE Pay Direct Settings Description": "LINE Pay Official Direct Connection Settings",
"Line Permissions": "Line Permissions",
"Line Products": "Line Products",
"LINE_MERCHANT_ID": "LINE_MERCHANT_ID", "LINE_MERCHANT_ID": "LINE_MERCHANT_ID",
"PARTNER_KEY": "PARTNER_KEY", "LIVE": "LIVE",
"PI_MERCHANT_ID": "PI_MERCHANT_ID", "Location": "Location",
"PS_MERCHANT_ID": "PS_MERCHANT_ID", "Login History": "Login History",
"Save Config": "Save Config", "Logout": "Logout",
"StoreID": "StoreID", "Logs": "Logs",
"TermID": "TermID", "Machine Count": "Machine Count",
"Photo Slot": "Photo Slot", "Machine created successfully.": "Machine created successfully.",
"Machine Details": "Machine Details",
"Machine Images": "Machine Images",
"Machine images updated successfully.": "Machine images updated successfully.",
"Machine Info": "Machine Info",
"Machine List": "Machine List",
"Machine Logs": "Machine Logs",
"Machine Management": "Machine Management",
"Machine Management Permissions": "Machine Management Permissions",
"Machine Model": "Machine Model",
"Machine model created successfully.": "Machine model created successfully.",
"Machine model deleted successfully.": "Machine model deleted successfully.",
"Machine Model Settings": "Machine Model Settings",
"Machine model updated successfully.": "Machine model updated successfully.",
"Machine Name": "Machine Name",
"Machine Permissions": "Machine Permissions",
"Machine Reports": "Machine Reports",
"Machine Restart": "Machine Restart",
"Machine Settings": "Machine Settings",
"Machine settings updated successfully.": "Machine settings updated successfully.",
"Machine Status List": "Machine Status List",
"Machine Stock": "Machine Stock",
"machines": "Machine Management",
"Machines": "Machines",
"Maintenance Records": "Maintenance Records",
"Manage administrative and tenant accounts": "Manage administrative and tenant accounts",
"Manage all tenant accounts and validity": "Manage all tenant accounts and validity",
"Manage your profile information, security settings, and login history": "Manage your profile information, security settings, and login history",
"Management of operational parameters and models": "Management of operational parameters and models",
"Max 3": "Max 3",
"Member & External": "Member & External",
"Member List": "Member List",
"Member Management": "Member Management",
"Member System": "Member System",
"members": "Member Management",
"Membership Tiers": "Membership Tiers",
"Menu Permissions": "Menu Permissions",
"Merchant IDs": "Merchant IDs",
"Merchant payment gateway settings management": "Merchant payment gateway settings management",
"Message": "Message",
"Message Content": "Message Content",
"Min 8 characters": "Min 8 characters",
"Model": "Model",
"Model Name": "Model Name",
"Models": "Models",
"Modifying your own administrative permissions may result in losing access to certain system functions.": "Modifying your own administrative permissions may result in losing access to certain system functions.",
"Monitor events and system activity across your vending fleet.": "Monitor events and system activity across your vending fleet.",
"Monthly cumulative revenue overview": "Monthly cumulative revenue overview",
"Monthly Transactions": "Monthly Transactions",
"Name": "Name",
"Never Connected": "Never Connected",
"New Password": "New Password",
"New Password (leave blank to keep current)": "New Password (leave blank to keep current)",
"Next": "Next",
"No accounts found": "No accounts found",
"No alert summary": "No alert summary",
"No configurations found": "No configurations found",
"No customers found": "No customers found",
"No data available": "No data available",
"No file uploaded.": "No file uploaded.",
"No images uploaded": "No images uploaded",
"No Invoice": "No Invoice",
"No location set": "No location set",
"No login history yet": "No login history yet",
"No logs found": "No logs found",
"No matching logs found": "No matching logs found",
"No permissions": "No permissions",
"No roles found.": "No roles found.",
"No users found": "No users found",
"None": "None",
"Not Used": "Not Used",
"Notes": "Notes",
"of": "of",
"Offline": "Offline",
"Offline Machines": "Offline Machines",
"Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.": "Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.",
"Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.": "Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.",
"Online": "Online",
"Online Machines": "Online Machines",
"Only system roles can be assigned to platform administrative accounts.": "Only system roles can be assigned to platform administrative accounts.",
"Operational Parameters": "Operational Parameters",
"Operations": "Operations",
"Optimized for display. Supported formats: JPG, PNG, WebP.": "Optimized for display. Supported formats: JPG, PNG, WebP.", "Optimized for display. Supported formats: JPG, PNG, WebP.": "Optimized for display. Supported formats: JPG, PNG, WebP.",
"Change": "Change" "Optional": "Optional",
"Order Management": "Order Management",
"Orders": "Orders",
"Others": "Others",
"Owner": "Owner",
"Parameters": "Parameters",
"PARTNER_KEY": "PARTNER_KEY",
"Pass Codes": "Pass Codes",
"Password": "Password",
"Payment & Invoice": "Payment & Invoice",
"Payment Buffer Seconds": "Payment Buffer Seconds",
"Payment Config": "Payment Config",
"Payment Configuration": "Payment Configuration",
"Payment Configuration created successfully.": "Payment Configuration created successfully.",
"Payment Configuration deleted successfully.": "Payment Configuration deleted successfully.",
"Payment Configuration updated successfully.": "Payment Configuration updated successfully.",
"Permanent": "Permanent",
"Permanently Delete Account": "Permanently Delete Account",
"Permission Settings": "Permission Settings",
"Permissions": "Permissions",
"permissions": "Permission Settings",
"Phone": "Phone",
"Photo Slot": "Photo Slot",
"PI_MERCHANT_ID": "PI_MERCHANT_ID",
"Pickup Codes": "Pickup Codes",
"Please check the following errors:": "Please check the following errors:",
"Please check the form for errors.": "Please check the form for errors.",
"Point Rules": "Point Rules",
"Point Settings": "Point Settings",
"Previous": "Previous",
"Product Management": "Product Management",
"Product Reports": "Product Reports",
"Profile": "Profile",
"Profile Information": "Profile Information",
"Profile Settings": "Profile Settings",
"Profile updated successfully.": "Profile updated successfully.",
"Promotions": "Promotions",
"Protected": "Protected",
"PS_MERCHANT_ID": "PS_MERCHANT_ID",
"Purchase Audit": "Purchase Audit",
"Purchases": "Purchases",
"Questionnaire": "Questionnaire",
"Quick search...": "Quick search...",
"Real-time monitoring across all machines": "Real-time monitoring across all machines",
"Real-time Operation Logs (Last 50)": "Real-time Operation Logs (Last 50)",
"Real-time status monitoring": "Real-time status monitoring",
"Recent Login": "Recent Login",
"remote": "Remote Management",
"Remote Change": "Remote Change",
"Remote Checkout": "Remote Checkout",
"Remote Dispense": "Remote Dispense",
"Remote Lock": "Remote Lock",
"Remote Management": "Remote Management",
"Remote Permissions": "Remote Permissions",
"Replenishment Audit": "Replenishment Audit",
"Replenishment Records": "Replenishment Records",
"Replenishments": "Replenishments",
"reservation": "Reservation System",
"Reservation Members": "Reservation Members",
"Reservation System": "Reservation System",
"Reservations": "Reservations",
"Returns": "Returns",
"Role": "Role",
"Role created successfully.": "Role created successfully.",
"Role deleted successfully.": "Role deleted successfully.",
"Role Management": "Role Permission Management",
"Role Name": "Role Name",
"Role name already exists in this company.": "Role name already exists in this company.",
"Role not found.": "Role not found.",
"Role Permissions": "Role Permissions",
"Role Settings": "Role Permissions",
"Role Type": "Role Type",
"Role updated successfully.": "Role updated successfully.",
"Roles": "Role Permissions",
"roles": "Role Permissions",
"Roles scoped to specific customer companies.": "Roles scoped to specific customer companies.",
"Running Status": "Running Status",
"Sales": "Sales",
"sales": "Sales Management",
"Sales Management": "Sales Management",
"Sales Permissions": "Sales Permissions",
"Sales Records": "Sales Records",
"Save": "Save",
"Save Changes": "Save Changes",
"Save Config": "Save Config",
"Saved.": "Saved.",
"Search configurations...": "Search configurations...",
"Search customers...": "Search customers...",
"Search machines...": "Search machines...",
"Search models...": "Search models...",
"Search roles...": "Search roles...",
"Search users...": "Search users...",
"Select Company": "Select Company",
"Select Model": "Select Model",
"Select Owner": "Select Owner",
"Serial & Version": "Serial & Version",
"Serial No": "Serial No",
"Serial Number": "Serial Number",
"Show": "Show",
"Showing": "Showing",
"Showing :from to :to of :total items": "Showing :from to :to of :total items",
"Sign in to your account": "Sign in to your account",
"Signed in as": "Signed in as",
"Slot Mechanism (default: Conveyor, check for Spring)": "Slot Mechanism (default: Conveyor, check for Spring)",
"Some fields need attention": "Some fields need attention",
"Special Permission": "Special Permission",
"special-permission": "Special Permission",
"Staff Stock": "Staff Stock",
"Status": "Status",
"Stock Management": "Stock Management",
"Store Gifts": "Store Gifts",
"Store ID": "Store ID",
"Store Management": "Store Management",
"StoreID": "StoreID",
"Sub Account Management": "Sub Account Management",
"Sub Account Roles": "Sub Account Roles",
"Sub Accounts": "Sub Accounts",
"Success": "Success",
"Super Admin": "Super Admin",
"Super-admin role cannot be assigned to tenant accounts.": "Super-admin role cannot be assigned to tenant accounts.",
"Survey Analysis": "Survey Analysis",
"SYSTEM": "SYSTEM",
"System Level": "System Level",
"System Role": "System Role",
"System role name cannot be modified.": "System role name cannot be modified.",
"System roles cannot be deleted by tenant administrators.": "System roles cannot be deleted by tenant administrators.",
"System roles cannot be modified by tenant administrators.": "System roles cannot be modified by tenant administrators.",
"System super admin accounts cannot be deleted.": "System super admin accounts cannot be deleted.",
"System super admin accounts cannot be modified via this interface.": "System super admin accounts cannot be modified via this interface.",
"Systems Initializing": "Systems Initializing",
"TapPay Integration": "TapPay Integration",
"TapPay Integration Settings Description": "TapPay Payment Integration Settings",
"Tax ID (Optional)": "Tax ID (Optional)",
"Temperature": "Temperature",
"TermID": "TermID",
"The image is too large. Please upload an image smaller than 1MB.": "The image is too large. Please upload an image smaller than 1MB.",
"The Super Admin role cannot be deleted.": "The Super Admin role cannot be deleted.",
"The Super Admin role is immutable.": "The Super Admin role is immutable.",
"The Super Admin role name cannot be modified.": "The Super Admin role name cannot be modified.",
"This role belongs to another company and cannot be assigned.": "This role belongs to another company and cannot be assigned.",
"Time": "Time",
"Time Slots": "Time Slots",
"Timer": "Timer",
"Timestamp": "Timestamp",
"to": "to",
"Today Cumulative Sales": "Today Cumulative Sales",
"Today's Transactions": "Today's Transactions",
"Total Connected": "Total Connected",
"Total Customers": "Total Customers",
"Total items": "Total items: :count",
"Total Logins": "Total Logins",
"Transfer Audit": "Transfer Audit",
"Transfers": "Transfers",
"Type": "Type",
"UI Elements": "UI Elements",
"Unknown": "Unknown",
"Update": "Update",
"Update Customer": "Update Customer",
"Update existing role and permissions.": "Update existing role and permissions.",
"Update Password": "Update Password",
"Update your account's profile information and email address.": "Update your account's profile information and email address.",
"Upload New Images": "Upload New Images",
"Uploading new images will replace all existing images.": "Uploading new images will replace all existing images.",
"User": "User",
"User Info": "User Info",
"Username": "Username",
"Users": "Users",
"Utilization Rate": "Utilization Rate",
"Valid Until": "Valid Until",
"Venue Management": "Venue Management",
"View Details": "View Details",
"View Logs": "View Logs",
"vs Yesterday": "vs Yesterday",
"Warehouse List": "Warehouse List",
"Warehouse List (All)": "Warehouse List (All)",
"Warehouse List (Individual)": "Warehouse List (Individual)",
"Warehouse Management": "Warehouse Management",
"Warehouse Permissions": "Warehouse Permissions",
"warehouses": "Warehouse Management",
"Warning": "Warning",
"Warning: You are editing your own role!": "Warning: You are editing your own role!",
"Welcome Gift": "Welcome Gift",
"Yesterday": "Yesterday",
"You cannot assign permissions you do not possess.": "You cannot assign permissions you do not possess.",
"You cannot delete your own account.": "You cannot delete your own account.",
"Your email address is unverified.": "Your email address is unverified.",
"Your recent account activity": "Your recent account activity"
} }

View File

@@ -1,388 +1,480 @@
{ {
"Account Settings": "アカウント設定", "A new verification link has been sent to your email address.": "新しい確認リンクがメールアドレスに送信されました。",
"Manage your profile information, security settings, and login history": "プロフィール情報、セキュリティ設定、ログイン履歴の管理", "Account created successfully.": "アカウントが正常に作成されました。",
"Profile Information": "プロフィール情報", "Account deleted successfully.": "アカウントが正常に削除されました。",
"Update your account's profile information and email address.": "アカウントの氏名、電話番号、メールアドレスを更新します。",
"Update Password": "パスワードの更新",
"Ensure your account is using a long, random password to stay secure.": "セキュリティを維持するため、アカウントには長くランダムなパスワードを使用してください。",
"Delete Account": "アカウントの削除",
"Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.": "アカウントが削除されると、そのすべてのリソース和データが永久に削除されます。アカウントを削除する前に、保持したいデータや情報をダウンロードしてください。",
"Are you sure you want to delete your account?": "真的にアカウントを削除してもよろしいですか?",
"Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.": "アカウントが削除されると、すべての関連データが永久に削除されます。アカウントの永久削除を確定するため、パスワードを入力してください。",
"Login History": "ログイン履歴",
"Name": "氏名",
"Phone": "電話番号",
"Email": "メールアドレス",
"Current Password": "現在のパスワード",
"New Password": "新しいパスワード",
"Confirm Password": "新しいパスワード(確認)",
"Save": "変更を保存",
"Saved.": "保存されました",
"Update": "更新",
"Cancel": "キャンセル",
"Confirm": "確認",
"Danger Zone: Delete Account": "危険区域:アカウントの削除",
"Permanently Delete Account": "アカウントを永久に削除",
"Password": "パスワード",
"Enter your password to confirm": "確認のためパスワードを入力してください",
"Dashboard": "ダッシュボード",
"Connectivity Status": "接続ステータス概況",
"Real-time status monitoring": "リアルタイムステータス監視",
"LIVE": "ライブ",
"Online Machines": "オンライン機台",
"Offline Machines": "オフライン機台",
"Alerts Pending": "アラート待機中",
"Total Connected": "接続数合計",
"Monthly Transactions": "今月の取引統計",
"Monthly cumulative revenue overview": "今月の累計収益概要",
"Today's Transactions": "今日の取引額",
"vs Yesterday": "前日比",
"Yesterday": "昨日",
"Day Before": "一昨日",
"Machine Status List": "機台稼働状況リスト",
"Total items": "合計 :count 件",
"Real-time monitoring across all machines": "全機台のリアルタイム監視",
"Quick search...": "クイック検索...",
"Machine Info": "機台情報",
"Running Status": "稼働状況",
"Today Cumulative Sales": "本日累計販売",
"Current Stock": "現在の在庫",
"Last Signal": "最終信号時間",
"Alert Summary": "アラート概要",
"No alert summary": "アラートなし",
"No data available": "データなし",
"Showing :from to :to of :total items": ":total 件中 :from から :to 件を表示",
"Previous": "前へ",
"Next": "次へ",
"Profile Settings": "個人設定",
"Profile": "プロフィール",
"Member Management": "会員管理",
"Member List": "会員リスト",
"Membership Tiers": "会員ランク",
"Deposit Bonus": "入金ボーナス",
"Point Rules": "ポイントルール",
"Gift Definitions": "ギフト設定",
"Machine Management": "機台管理",
"Machine Logs": "機台ログ",
"Machine List": "機台リスト",
"Machine Permissions": "機台権限",
"Utilization Rate": "稼働率",
"Expiry Management": "有効期限管理",
"Maintenance Records": "メンテナンス記録",
"APP Management": "APP管理",
"UI Elements": "UI要素",
"Helper": "ヘルパー",
"Questionnaire": "アンケート",
"Games": "ゲーム",
"Timer": "タイマー",
"Warehouse Management": "倉庫管理",
"Warehouse List (All)": "倉庫リスト(全)",
"Warehouse List (Individual)": "倉庫リスト(個)",
"Stock Management": "在庫管理",
"Transfers": "転送",
"Purchases": "購入",
"Replenishments": "補充",
"Replenishment Records": "補充記録",
"Machine Stock": "機台在庫",
"Staff Stock": "スタッフ在庫",
"Returns": "返品",
"Sales Management": "販売管理",
"Sales Records": "販売記録",
"Pickup Codes": "受取コード",
"Orders": "注文",
"Promotions": "プロモーション",
"Pass Codes": "パスクード",
"Store Gifts": "来店特典",
"Analysis Management": "分析管理",
"Change Stock": "小銭在庫",
"Machine Reports": "機台レポート",
"Product Reports": "商品レポート",
"Survey Analysis": "アンケート分析",
"Audit Management": "監査管理",
"Purchase Audit": "購入監査",
"Transfer Audit": "転送監査",
"Replenishment Audit": "補充監査",
"Data Configuration": "データ設定",
"Product Management": "商品管理",
"Advertisement Management": "廣告管理",
"Admin Sellable Products": "管理者販売可能商品",
"Account Management": "アカウント管理", "Account Management": "アカウント管理",
"Sub Accounts": "サブアカウント", "Account Settings": "アカウント設定",
"Sub Account Roles": "サブアカウントロール",
"Point Settings": "ポイント設定",
"Badge Settings": "バッジ設定",
"Remote Management": "リモート管理",
"Machine Restart": "機台再起動",
"Card Reader Restart": "カードリーダー再起動",
"Remote Checkout": "リモート決済",
"Remote Lock": "リモートロック",
"Remote Change": "リモートお釣り",
"Remote Dispense": "リモート出庫",
"Line Management": "Line管理",
"Line Members": "Line会員",
"Line Machines": "Line機台",
"Line Products": "Line商品",
"Line Official Account": "Line公式アカウント",
"Line Orders": "Line注文",
"Line Coupons": "Lineクーポン",
"Reservation System": "予約システム",
"Reservation Members": "予約会員",
"Store Management": "店舗管理",
"Time Slots": "タイムスロット",
"Venue Management": "会場管理",
"Coupons": "クーポン",
"Reservations": "予約",
"Order Management": "注文管理",
"Special Permission": "特別権限",
"Clear Stock": "在庫クリア",
"APK Versions": "APKバージョン",
"Discord Notifications": "Discord通知",
"Basic Settings": "基本設定",
"Machine Settings": "機台設定",
"Permission Settings": "権限設定",
"APP Features": "APP機能",
"Others": "その他",
"AI Prediction": "AI予測",
"Roles": "ロール權限",
"Role Management": "ロール權限管理",
"Define and manage security roles and permissions.": "システムのセキュリティロールと権限を定義および管理します。",
"Search roles...": "ロールを検索...",
"No permissions": "権限項目なし",
"No roles found.": "ロールが見つかりませんでした。",
"Create Role": "ロール作成",
"Edit Role": "ロール編集",
"Update existing role and permissions.": "既存のロールと権限を更新します。",
"Create a new role and assign permissions.": "新しいロールを作成し、権限を割り当てます。",
"Enter role name": "ロール名を入力してください",
"Add Role": "ロールを追加",
"Role Name": "ロール名",
"Type": "タイプ",
"Permissions": "権限",
"Users": "ユーザー数",
"System role name cannot be modified.": "システムロール名は変更できません。",
"System Level": "システムレベル",
"Company Level": "顧客レベル",
"Menu Permissions": "メニュー権限",
"Save Changes": "変更を保存",
"members": "会員管理",
"machines": "機台管理",
"app": "APP管理",
"warehouses": "倉庫管理",
"sales": "販売管理",
"analysis": "分析管理",
"audit": "監査管理",
"data-config": "データ設定",
"remote": "リモート管理",
"line": "Line管理",
"reservation": "予約システム",
"special-permission": "特別権限",
"companies": "顧客管理",
"accounts": "アカウント管理",
"roles": "ロール權限",
"Role Permissions": "ロール權限",
"Role Settings": "ロール權限",
"No login history yet": "ログイン履歴はまだありません",
"Signed in as": "ログイン中",
"Logout": "ログアウト",
"Joined": "入会日",
"Recent Login": "最近のログイン",
"Total Logins": "總ログイン數",
"Account Status": "アカウント狀態", "Account Status": "アカウント狀態",
"Active": "アクティブ", "Account updated successfully.": "アカウントが正常に更新されました。",
"Customer Management": "顧客管理", "accounts": "アカウント管理",
"Manage all tenant accounts and validity": "すべてのテナントアカウントと有効期限を管理します",
"Add Customer": "顧客を追加",
"Total Customers": "顧客總數",
"Expired / Disabled": "期限切れ / 停止中",
"Search customers...": "顧客を検索...",
"All": "すべて",
"Disabled": "停止中",
"Customer Info": "顧客情報",
"Accounts / Machines": "アカウント / 機台", "Accounts / Machines": "アカウント / 機台",
"Valid Until": "有効期限",
"Actions": "操作",
"Permanent": "永久認可",
"Are you sure to delete this customer?": "この顧客を削除してもよろしいですか?",
"No customers found": "顧客が見つかりません",
"Edit Customer": "顧客を編集",
"Update Customer": "顧客を更新",
"Create": "作成",
"Company Name": "会社名",
"Company Code": "会社コード",
"Tax ID (Optional)": "納税者番号 (任意)",
"Status": "ステータス",
"Contact Name": "連絡担当者名",
"Contact Phone": "連絡先電話番号",
"Contact Email": "連絡先メールアドレス",
"Notes": "備考",
"Customer created successfully.": "顧客が正常に作成されました。",
"Customer updated successfully.": "顧客が正常に更新されました。",
"Customer deleted successfully.": "顧客が正常に削除されました。",
"Cannot delete company with active accounts.": "アクティブなアカウントを持つ会社は削除できません。",
"Contract Until (Optional)": "契約期限 (任意)",
"Company Information": "会社情報",
"Initial Admin Account": "初期管理者アカウント",
"Optional": "任意",
"Username": "ユーザー名",
"Enter login ID": "ログインIDを入力してください",
"Min 8 characters": "最低8文字",
"Admin display name": "管理者表示名",
"Initial Role": "初期ロール",
"Contact & Details": "連絡先と詳細",
"e.g. Taiwan Star": "例:台湾スター",
"e.g. TWSTAR": "例TWSTAR",
"Manage administrative and tenant accounts": "管理者およびテナントアカウントを管理します",
"Add Account": "アカウントを追加",
"All Companies": "すべての会社",
"User Info": "ユーザー情報",
"Belongs To": "所属",
"Role": "ロール",
"SYSTEM": "システムレベル",
"No users found": "ユーザーが見つかりません",
"Data Configuration Permissions": "データ設定権限",
"Sales Permissions": "販売管理権限",
"Machine Management Permissions": "機台管理權限",
"Warehouse Permissions": "倉庫管理權限",
"Analysis Permissions": "分析管理權限",
"Audit Permissions": "監査管理權限",
"Remote Permissions": "リモート管理權限",
"Line Permissions": "Line管理權限",
"Company": "所属顧客",
"User": "一般ユーザー",
"Admin": "管理者",
"Super Admin": "スーパー管理者",
"e.g. John Doe": "例:山田太郎",
"e.g. johndoe": "例yamadataro",
"Search users...": "ユーザーを検索...",
"Admin Name": "管理者名",
"New Password (leave blank to keep current)": "新しいパスワード (変更しない場合は空欄)",
"Are you sure you want to delete this account?": "このアカウントを削除してもよろしいですか?",
"Show": "表示",
"to": "から",
"of": "件中",
"items": "個の項目",
"Showing": "表示中",
"Monitor events and system activity across your vending fleet.": "自販機フリート全体のイベントとシステムアクティビティを監視します。",
"All Machines": "すべての機体",
"All Levels": "すべてのレベル",
"Timestamp": "タイムスタンプ",
"Message Content": "ログ内容",
"No matching logs found": "一致するログが見つかりません",
"Unknown": "不明",
"Info": "情報",
"Warning": "警告",
"basic-settings": "基本設定",
"permissions": "權限設定",
"Error": "エラー",
"Machine Model Settings": "機台型號設定",
"Machine Model": "機台型號",
"Model Name": "型號名称",
"Machine Count": "機台数量",
"Add Machine Model": "機台型號を追加",
"Edit Machine Model": "機台型號を編輯",
"Machine model created successfully.": "機台型號が正常に作成されました。",
"Machine model updated successfully.": "機台型號が正常に更新されました。",
"Machine model deleted successfully.": "機台型號が正常に削除されました。",
"Cannot delete model that is currently in use by machines.": "機台で使用中の型號は削除できません。",
"Machine Details": "機台詳情",
"Create Machine": "機台新規作成",
"Edit Machine": "機台編集",
"Basic Information": "基本情報",
"Location": "場所",
"Temperature": "温度",
"Firmware Version": "ファームウェアバージョン",
"Last Heartbeat": "最終ハートビート時間",
"Never Connected": "未接続",
"View Logs": "ログを表示",
"Real-time Operation Logs (Last 50)": "リアルタイム操作ログ (直近 50 件)",
"All Times System Timezone": "時間はすべてシステムタイムゾーンです",
"Level": "レベル",
"Logs": "ログ",
"Time": "時間",
"Message": "メッセージ",
"Online": "オンライン",
"Offline": "オフライン",
"Connecting...": "接続中...",
"No logs found": "ログがありません",
"Management of operational parameters and models": "運用パラメータと型番の管理",
"ECPay Invoice Settings Description": "ECPay 電子発票設定",
"E.SUN QR Scan Settings Description": "玉山銀行 QR スキャン決済設定",
"LINE Pay Direct Settings Description": "LINE Pay 公式直結設定",
"TapPay Integration Settings Description": "TapPay 決済連携設定",
"Merchant IDs": "マーチャント ID",
"Parameters": "パラメータ設定",
"Hardware & Network": "ハードウェアとネットワーク",
"Serial & Version": "シリアルとバージョン",
"Heartbeat": "ハートビート状態",
"Heating Range": "加熱時間帯",
"API Token": "API キー",
"No location set": "場所が未設定です",
"Close Panel": "パネルを閉じる",
"Operations": "運用設定",
"Operational Parameters": "運用パラメータ",
"Hardware & Slots": "ハードウェアと貨道",
"Slot Mechanism (default: Conveyor, check for Spring)": "貨道メカニズム (デフォルト:コンベア、チェックでスプリング)",
"Payment & Invoice": "決済と発票",
"Payment Config": "決済設定",
"Invoice Status": "発票発行状態",
"No Invoice": "発票を発行しない",
"Default Donate": "デフォルト寄付",
"Default Not Donate": "デフォルト寄付しない",
"Member & External": "会員と外部システム",
"Welcome Gift": "会員登録特典",
"Enabled/Disabled": "有効/無効",
"Member System": "会員システム",
"Machine Images": "機台写真",
"No images uploaded": "写真がアップロードされていません",
"Upload New Images": "新しい写真をアップロード",
"Max 3": "最大3枚",
"Uploading new images will replace all existing images.": "新しい写真をアップロードすると、既存のすべての写真が置き換えられます。",
"Search machines...": "機台を検索...",
"Search models...": "型番を検索...",
"Card Reader": "カードリーダー",
"Owner": "所属会社",
"Action": "操作", "Action": "操作",
"Items": "個の項目", "Actions": "操作",
"View Details": "詳細表示", "Active": "アクティブ",
"Edit Settings": "設定編集", "Add Account": "アカウントを追加",
"Are you sure?": "よろしいですか?", "Add Customer": "顧客を追加",
"Serial No": "機台シリアル番号", "Add Machine": "機台を追加",
"Select Owner": "所属会社を選択", "Add Machine Model": "機台型號を追加",
"Select Model": "型番を選択", "Add Role": "ロールを追加",
"Machines": "機台リスト", "Admin": "管理者",
"Models": "型番リスト", "Admin display name": "管理者表示名",
"Edit": "編集", "Admin Name": "管理者名",
"Delete": "削除", "Admin Sellable Products": "管理者販売可能商品",
"None": "なし", "Administrator": "管理者",
"Create Payment Config": "決済設定を新規作成", "Advertisement Management": "廣告管理",
"Edit Payment Config": "決済設定を編集", "Affiliation": "所属",
"Define new third-party payment parameters": "新しいサードパーティ決済パラメータを定義", "AI Prediction": "AI予測",
"Configuration Name": "設定名称", "Alert Summary": "アラート概要",
"Belongs To Company": "所属会社", "Alerts Pending": "アラート待機中",
"Select Company": "会社を選択", "All": "すべて",
"ECPay Invoice": "ECPay 電子発票", "All Affiliations": "全ての所属",
"Store ID": "加盟店ID (MerchantID)", "All Companies": "すべての会社",
"HashKey": "HashKey", "All Levels": "すべてのレベル",
"HashIV": "HashIV", "All Machines": "すべての機体",
"E.SUN QR Scan": "玉山銀行 QR スキャン", "All Times System Timezone": "時間はすべてシステムタイムゾーンです",
"StoreID": "加盟店ID (StoreID)", "analysis": "分析管理",
"TermID": "端末ID (TermID)", "Analysis Management": "分析管理",
"Key": "キー (Key)", "Analysis Permissions": "分析管理權限",
"LINE Pay Direct": "LINE Pay 直結決済", "API Token": "API キー",
"ChannelId": "チャンネルID", "APK Versions": "APKバージョン",
"ChannelSecret": "チャンネルシークレット", "app": "APP管理",
"TapPay Integration": "TapPay 統合決済", "APP Features": "APP機能",
"PARTNER_KEY": "パートナーキー", "APP Management": "APP管理",
"APP_ID": "APP_ID", "APP_ID": "APP_ID",
"APP_KEY": "APP_KEY", "APP_KEY": "APP_KEY",
"LINE_MERCHANT_ID": "LINE Pay 加盟店ID", "Are you sure to delete this customer?": "この顧客を削除してもよろしいですか?",
"JKO_MERCHANT_ID": "街口支付 加盟店ID", "Are you sure you want to delete this account?": "このアカウントを削除してもよろしいですか?",
"PI_MERCHANT_ID": "Pi 拍錢包 加盟店ID", "Are you sure you want to delete this account? This action cannot be undone.": "このアカウントを削除してもよろしいですか?この操作は元に戻せません。",
"PS_MERCHANT_ID": "全盈+Pay 加盟店ID", "Are you sure you want to delete this configuration? This action cannot be undone.": "この設定を削除してもよろしいですか?この操作は元に戻せません。",
"EASY_MERCHANT_ID": "悠遊付 加盟店ID", "Are you sure you want to delete this item? This action cannot be undone.": "この項目を削除してもよろしいですか?この操作は元に戻せません。",
"Save Config": "設定を保存", "Are you sure you want to delete this role? This action cannot be undone.": "このロールを削除してもよろしいですか?この操作は元に戻せません。",
"Are you sure you want to delete your account?": "真的にアカウントを削除してもよろしいですか?",
"Are you sure?": "よろしいですか?",
"audit": "監査管理",
"Audit Management": "監査管理",
"Audit Permissions": "監査管理權限",
"Avatar updated successfully.": "アバターが正常に更新されました。",
"Badge Settings": "バッジ設定",
"Basic Information": "基本情報",
"Basic Settings": "基本設定",
"basic-settings": "基本設定",
"Belongs To": "所属",
"Belongs To Company": "所属会社",
"Cancel": "キャンセル",
"Cannot delete company with active accounts.": "アクティブなアカウントを持つ会社は削除できません。",
"Cannot delete model that is currently in use by machines.": "機台で使用中の型號は削除できません。",
"Cannot Delete Role": "ロールを削除できません",
"Cannot delete role with active users.": "アクティブなユーザーがいるロールは削除できません。",
"Card Reader": "カードリーダー",
"Card Reader No": "カードリーダー番号",
"Card Reader Restart": "カードリーダー再起動",
"Card Reader Seconds": "カードリーダー秒数",
"Change": "変更",
"Change Stock": "小銭在庫",
"ChannelId": "チャンネルID",
"ChannelSecret": "チャンネルシークレット",
"Checkout Time 1": "決済時間 1",
"Checkout Time 2": "決済時間 2",
"Clear Stock": "在庫クリア",
"Click here to re-send the verification email.": "確認メールを再送信するにはここをクリックしてください。",
"Click to upload": "クリックしてアップロード",
"Close Panel": "パネルを閉じる",
"companies": "顧客管理",
"Company": "所属顧客",
"Company Code": "会社コード",
"Company Information": "会社情報",
"Company Level": "顧客レベル",
"Company Name": "会社名",
"Configuration Name": "設定名称",
"Confirm": "確認",
"Confirm Deletion": "削除の確認",
"Confirm Password": "新しいパスワード(確認)",
"Connecting...": "接続中...",
"Connectivity Status": "接続ステータス概況",
"Contact & Details": "連絡先と詳細",
"Contact Email": "連絡先メールアドレス",
"Contact Name": "連絡担当者名",
"Contact Phone": "連絡先電話番号",
"Contract Until (Optional)": "契約期限 (任意)",
"Coupons": "クーポン",
"Create": "作成",
"Create a new role and assign permissions.": "新しいロールを作成し、権限を割り当てます。",
"Create Config": "設定を新規作成",
"Create Machine": "機台新規作成",
"Create Payment Config": "決済設定を新規作成",
"Create Role": "ロール作成",
"Current Password": "現在のパスワード",
"Current Stock": "現在の在庫",
"Customer created successfully.": "顧客が正常に作成されました。",
"Customer deleted successfully.": "顧客が正常に削除されました。",
"Customer Info": "顧客情報",
"Customer Management": "顧客管理",
"Customer Payment Config": "決済設定管理",
"Customer updated successfully.": "顧客が正常に更新されました。",
"Danger Zone: Delete Account": "危険区域:アカウントの削除",
"Dashboard": "ダッシュボード",
"Data Configuration": "データ設定",
"Data Configuration Permissions": "データ設定権限",
"data-config": "データ設定",
"Day Before": "一昨日",
"Default Donate": "デフォルト寄付",
"Default Not Donate": "デフォルト寄付しない",
"Define and manage security roles and permissions.": "システムのセキュリティロールと権限を定義および管理します。",
"Define new third-party payment parameters": "新しいサードパーティ決済パラメータを定義",
"Delete": "削除",
"Delete Account": "アカウントの削除",
"Delete Permanently": "完全に削除",
"Deposit Bonus": "入金ボーナス",
"Detail": "詳細",
"Disabled": "停止中",
"Discord Notifications": "Discord通知",
"e.g. John Doe": "例:山田太郎",
"e.g. johndoe": "例yamadataro",
"e.g. Taiwan Star": "例:台湾スター",
"e.g. TWSTAR": "例TWSTAR",
"e.g., Company Standard Pay": "例:標準決済組合せ", "e.g., Company Standard Pay": "例:標準決済組合せ",
"Photo Slot": "写真スロット", "e.g., Taipei Station": "例:台北駅",
"E.SUN QR Scan": "玉山銀行 QR スキャン",
"E.SUN QR Scan Settings Description": "玉山銀行 QR スキャン決済設定",
"EASY_MERCHANT_ID": "悠遊付 加盟店ID",
"ECPay Invoice": "ECPay 電子発票",
"ECPay Invoice Settings Description": "ECPay 電子発票設定",
"Edit": "編集",
"Edit Account": "アカウントを編集",
"Edit Customer": "顧客を編集",
"Edit Machine": "機台編集",
"Edit Machine Model": "機台型號を編輯",
"Edit Payment Config": "決済設定を編集",
"Edit Role": "ロール編集",
"Edit Settings": "設定編集",
"Email": "メールアドレス",
"Enabled/Disabled": "有効/無効",
"Ensure your account is using a long, random password to stay secure.": "セキュリティを維持するため、アカウントには長くランダムなパスワードを使用してください。",
"Enter login ID": "ログインIDを入力してください",
"Enter machine location": "機台の場所を入力してください",
"Enter machine name": "機台名を入力してください",
"Enter model name": "型番名を入力してください",
"Enter role name": "ロール名を入力してください",
"Enter serial number": "シリアル番号を入力してください",
"Enter your password to confirm": "確認のためパスワードを入力してください",
"Error": "エラー",
"Expired / Disabled": "期限切れ / 停止中",
"Expiry Management": "有効期限管理",
"Failed to update machine images: ": "機台画像の更新に失敗しました:",
"files selected": "ファイルを選択済み",
"Firmware Version": "ファームウェアバージョン",
"Full Name": "氏名",
"Games": "ゲーム",
"Gift Definitions": "ギフト設定",
"Got it": "了解",
"Hardware & Network": "ハードウェアとネットワーク",
"Hardware & Slots": "ハードウェアと貨道",
"HashIV": "HashIV",
"HashKey": "HashKey",
"Heartbeat": "ハートビート状態",
"Heating End Time": "加熱終了時間",
"Heating Range": "加熱時間帯",
"Heating Start Time": "加熱開始時間",
"Helper": "ヘルパー",
"Info": "情報",
"Initial Admin Account": "初期管理者アカウント",
"Initial Role": "初期ロール",
"Invoice Status": "発票発行状態",
"items": "個の項目",
"Items": "個の項目",
"JKO_MERCHANT_ID": "街口支付 加盟店ID",
"john@example.com": "john@example.com",
"Joined": "入会日",
"Key": "キー (Key)",
"Key No": "キー番号",
"Last Heartbeat": "最終ハートビート時間",
"Last Signal": "最終信号時間",
"Last Updated": "最終更新",
"Level": "レベル",
"line": "Line管理",
"Line Coupons": "Lineクーポン",
"Line Machines": "Line機台",
"Line Management": "Line管理",
"Line Members": "Line会員",
"Line Official Account": "Line公式アカウント",
"Line Orders": "Line注文",
"LINE Pay Direct": "LINE Pay 直結決済",
"LINE Pay Direct Settings Description": "LINE Pay 公式直結設定",
"Line Permissions": "Line管理權限",
"Line Products": "Line商品",
"LINE_MERCHANT_ID": "LINE Pay 加盟店ID",
"LIVE": "ライブ",
"Location": "場所",
"Login History": "ログイン履歴",
"Logout": "ログアウト",
"Logs": "ログ",
"Machine Count": "機台数量",
"Machine created successfully.": "機台が正常に作成されました。",
"Machine Details": "機台詳情",
"Machine Images": "機台写真",
"Machine images updated successfully.": "機台画像が正常に更新されました。",
"Machine Info": "機台情報",
"Machine List": "機台リスト",
"Machine Logs": "機台ログ",
"Machine Management": "機台管理",
"Machine Management Permissions": "機台管理權限",
"Machine Model": "機台型號",
"Machine model created successfully.": "機台型號が正常に作成されました。",
"Machine model deleted successfully.": "機台型號が正常に削除されました。",
"Machine Model Settings": "機台型號設定",
"Machine model updated successfully.": "機台型號が正常に更新されました。",
"Machine Name": "機台名",
"Machine Permissions": "機台権限",
"Machine Reports": "機台レポート",
"Machine Restart": "機台再起動",
"Machine Settings": "機台設定",
"Machine settings updated successfully.": "機台設定が正常に更新されました。",
"Machine Status List": "機台稼働状況リスト",
"Machine Stock": "機台在庫",
"machines": "機台管理",
"Machines": "機台リスト",
"Maintenance Records": "メンテナンス記録",
"Manage administrative and tenant accounts": "管理者およびテナントアカウントを管理します",
"Manage all tenant accounts and validity": "すべてのテナントアカウントと有効期限を管理します",
"Manage your profile information, security settings, and login history": "プロフィール情報、セキュリティ設定、ログイン履歴の管理",
"Management of operational parameters and models": "運用パラメータと型番の管理",
"Max 3": "最大3枚",
"Member & External": "会員と外部システム",
"Member List": "会員リスト",
"Member Management": "会員管理",
"Member System": "会員システム",
"members": "会員管理",
"Membership Tiers": "会員ランク",
"Menu Permissions": "メニュー権限",
"Merchant IDs": "マーチャント ID",
"Merchant payment gateway settings management": "マーチャント決済ゲートウェイ設定管理",
"Message": "メッセージ",
"Message Content": "ログ内容",
"Min 8 characters": "最低8文字",
"Model": "型番",
"Model Name": "型號名称",
"Models": "型番リスト",
"Modifying your own administrative permissions may result in losing access to certain system functions.": "自身の管理權限を変更すると、一部のシステム機能へのアクセス權を失う可能性があります。",
"Monitor events and system activity across your vending fleet.": "自販機フリート全体のイベントとシステムアクティビティを監視します。",
"Monthly cumulative revenue overview": "今月の累計収益概要",
"Monthly Transactions": "今月の取引統計",
"Name": "氏名",
"Never Connected": "未接続",
"New Password": "新しいパスワード",
"New Password (leave blank to keep current)": "新しいパスワード (変更しない場合は空欄)",
"Next": "次へ",
"No accounts found": "アカウントが見つかりません",
"No alert summary": "アラートなし",
"No configurations found": "設定が見つかりません",
"No customers found": "顧客が見つかりません",
"No data available": "データなし",
"No file uploaded.": "ファイルがアップロードされていません。",
"No images uploaded": "写真がアップロードされていません",
"No Invoice": "発票を発行しない",
"No location set": "場所が未設定です",
"No login history yet": "ログイン履歴はまだありません",
"No logs found": "ログがありません",
"No matching logs found": "一致するログが見つかりません",
"No permissions": "権限項目なし",
"No roles found.": "ロールが見つかりませんでした。",
"No users found": "ユーザーが見つかりません",
"None": "なし",
"Not Used": "未使用",
"Notes": "備考",
"of": "件中",
"Offline": "オフライン",
"Offline Machines": "オフライン機台",
"Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.": "アカウントが削除されると、そのすべてのリソース和データが永久に削除されます。アカウントを削除する前に、保持したいデータや情報をダウンロードしてください。",
"Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.": "アカウントが削除されると、すべての関連データが永久に削除されます。アカウントの永久削除を確定するため、パスワードを入力してください。",
"Online": "オンライン",
"Online Machines": "オンライン機台",
"Only system roles can be assigned to platform administrative accounts.": "プラットフォーム管理アカウントにはシステムロールのみ割り当て可能です。",
"Operational Parameters": "運用パラメータ",
"Operations": "運用設定",
"Optimized for display. Supported formats: JPG, PNG, WebP.": "表示用に最適化されています。対応形式JPG, PNG, WebP。", "Optimized for display. Supported formats: JPG, PNG, WebP.": "表示用に最適化されています。対応形式JPG, PNG, WebP。",
"Change": "変更" "Optional": "任意",
"Order Management": "注文管理",
"Orders": "注文",
"Others": "その他",
"Owner": "所属会社",
"Parameters": "パラメータ設定",
"PARTNER_KEY": "パートナーキー",
"Pass Codes": "パスクード",
"Password": "パスワード",
"Payment & Invoice": "決済と発票",
"Payment Buffer Seconds": "決済バッファ秒数",
"Payment Config": "決済設定",
"Payment Configuration": "決済設定",
"Payment Configuration created successfully.": "決済設定が正常に作成されました。",
"Payment Configuration deleted successfully.": "決済設定が正常に削除されました。",
"Payment Configuration updated successfully.": "決済設定が正常に更新されました。",
"Permanent": "永久認可",
"Permanently Delete Account": "アカウントを永久に削除",
"Permission Settings": "権限設定",
"Permissions": "権限",
"permissions": "權限設定",
"Phone": "電話番号",
"Photo Slot": "写真スロット",
"PI_MERCHANT_ID": "Pi 拍錢包 加盟店ID",
"Pickup Codes": "受取コード",
"Please check the following errors:": "以下のエラーを確認してください:",
"Please check the form for errors.": "フォームにエラーがないか確認してください。",
"Point Rules": "ポイントルール",
"Point Settings": "ポイント設定",
"Previous": "前へ",
"Product Management": "商品管理",
"Product Reports": "商品レポート",
"Profile": "プロフィール",
"Profile Information": "プロフィール情報",
"Profile Settings": "個人設定",
"Profile updated successfully.": "プロフィールが正常に更新されました。",
"Promotions": "プロモーション",
"Protected": "保護されています",
"PS_MERCHANT_ID": "全盈+Pay 加盟店ID",
"Purchase Audit": "購入監査",
"Purchases": "購入",
"Questionnaire": "アンケート",
"Quick search...": "クイック検索...",
"Real-time monitoring across all machines": "全機台のリアルタイム監視",
"Real-time Operation Logs (Last 50)": "リアルタイム操作ログ (直近 50 件)",
"Real-time status monitoring": "リアルタイムステータス監視",
"Recent Login": "最近のログイン",
"remote": "リモート管理",
"Remote Change": "リモートお釣り",
"Remote Checkout": "リモート決済",
"Remote Dispense": "リモート出庫",
"Remote Lock": "リモートロック",
"Remote Management": "リモート管理",
"Remote Permissions": "リモート管理權限",
"Replenishment Audit": "補充監査",
"Replenishment Records": "補充記録",
"Replenishments": "補充",
"reservation": "予約システム",
"Reservation Members": "予約会員",
"Reservation System": "予約システム",
"Reservations": "予約",
"Returns": "返品",
"Role": "ロール",
"Role created successfully.": "ロールが正常に作成されました。",
"Role deleted successfully.": "ロールが正常に削除されました。",
"Role Management": "ロール權限管理",
"Role Name": "ロール名",
"Role name already exists in this company.": "この会社には同じ名前のロールが既に存在します。",
"Role not found.": "ロールが見つかりませんでした。",
"Role Permissions": "ロール權限",
"Role Settings": "ロール權限",
"Role Type": "ロールタイプ",
"Role updated successfully.": "ロールが正常に更新されました。",
"Roles": "ロール權限",
"roles": "ロール權限",
"Running Status": "稼働状況",
"sales": "販売管理",
"Sales Management": "販売管理",
"Sales Permissions": "販売管理権限",
"Sales Records": "販売記録",
"Save": "変更を保存",
"Save Changes": "変更を保存",
"Save Config": "設定を保存",
"Saved.": "保存されました",
"Search configurations...": "設定を検索...",
"Search customers...": "顧客を検索...",
"Search machines...": "機台を検索...",
"Search models...": "型番を検索...",
"Search roles...": "ロールを検索...",
"Search users...": "ユーザーを検索...",
"Select Company": "会社を選択",
"Select Model": "型番を選択",
"Select Owner": "所属会社を選択",
"Serial & Version": "シリアルとバージョン",
"Serial No": "機台シリアル番号",
"Serial Number": "シリアル番号",
"Show": "表示",
"Showing": "表示中",
"Showing :from to :to of :total items": ":total 件中 :from から :to 件を表示",
"Sign in to your account": "アカウントにサインイン",
"Signed in as": "ログイン中",
"Slot Mechanism (default: Conveyor, check for Spring)": "貨道メカニズム (デフォルト:コンベア、チェックでスプリング)",
"Some fields need attention": "一部のフィールドに注意が必要です",
"Special Permission": "特別権限",
"special-permission": "特別権限",
"Staff Stock": "スタッフ在庫",
"Status": "ステータス",
"Stock Management": "在庫管理",
"Store Gifts": "来店特典",
"Store ID": "加盟店ID (MerchantID)",
"Store Management": "店舗管理",
"StoreID": "加盟店ID (StoreID)",
"Sub Account Management": "サブアカウント管理",
"Sub Account Roles": "サブアカウントロール",
"Sub Accounts": "サブアカウント",
"Success": "成功",
"Super Admin": "スーパー管理者",
"Super-admin role cannot be assigned to tenant accounts.": "スーパー管理者ロールはテナントアカウントに割り当てることはできません。",
"Survey Analysis": "アンケート分析",
"SYSTEM": "システムレベル",
"System Level": "システムレベル",
"System Role": "システムロール",
"System role name cannot be modified.": "システムロール名は変更できません。",
"System roles cannot be deleted by tenant administrators.": "テナント管理者はシステムロールを削除できません。",
"System roles cannot be modified by tenant administrators.": "テナント管理者はシステムロールを変更できません。",
"System super admin accounts cannot be deleted.": "システムスーパー管理者アカウントは削除できません。",
"System super admin accounts cannot be modified via this interface.": "システムスーパー管理者アカウントはこのインターフェースからは変更できません。",
"Systems Initializing": "システム初期化中",
"TapPay Integration": "TapPay 統合決済",
"TapPay Integration Settings Description": "TapPay 決済連携設定",
"Tax ID (Optional)": "納税者番号 (任意)",
"Temperature": "温度",
"TermID": "端末ID (TermID)",
"The image is too large. Please upload an image smaller than 1MB.": "画像が大きすぎます。1MB未満の画像をアップロードしてください。",
"The Super Admin role cannot be deleted.": "スーパー管理者ロールは削除できません。",
"The Super Admin role is immutable.": "スーパー管理者ロールは変更できません。",
"The Super Admin role name cannot be modified.": "スーパー管理者のロール名は変更できません。",
"This role belongs to another company and cannot be assigned.": "このロールは他の会社に属しており、割り当てることはできません。",
"Time": "時間",
"Time Slots": "タイムスロット",
"Timer": "タイマー",
"Timestamp": "タイムスタンプ",
"to": "から",
"Today Cumulative Sales": "本日累計販売",
"Today's Transactions": "今日の取引額",
"Total Connected": "接続数合計",
"Total Customers": "顧客總數",
"Total items": "合計 :count 件",
"Total Logins": "總ログイン數",
"Transfer Audit": "転送監査",
"Transfers": "転送",
"Type": "タイプ",
"UI Elements": "UI要素",
"Unknown": "不明",
"Update": "更新",
"Update Customer": "顧客を更新",
"Update existing role and permissions.": "既存のロールと権限を更新します。",
"Update Password": "パスワードの更新",
"Update your account's profile information and email address.": "アカウントの氏名、電話番号、メールアドレスを更新します。",
"Upload New Images": "新しい写真をアップロード",
"Uploading new images will replace all existing images.": "新しい写真をアップロードすると、既存のすべての写真が置き換えられます。",
"User": "一般ユーザー",
"User Info": "ユーザー情報",
"Username": "ユーザー名",
"Users": "ユーザー数",
"Utilization Rate": "稼働率",
"Valid Until": "有効期限",
"Venue Management": "会場管理",
"View Details": "詳細表示",
"View Logs": "ログを表示",
"vs Yesterday": "前日比",
"Warehouse List": "倉庫リスト",
"Warehouse List (All)": "倉庫リスト(全)",
"Warehouse List (Individual)": "倉庫リスト(個)",
"Warehouse Management": "倉庫管理",
"Warehouse Permissions": "倉庫管理權限",
"warehouses": "倉庫管理",
"Warning": "警告",
"Warning: You are editing your own role!": "警告:現在使用中のロールを編集しています!",
"Welcome Gift": "会員登録特典",
"Yesterday": "昨日",
"You cannot assign permissions you do not possess.": "ご自身が所有していない権限を割り當てることはできません。",
"You cannot delete your own account.": "ご自身のアカウントは削除できません。",
"Your email address is unverified.": "メールアドレスが未確認です。",
"Your recent account activity": "最近のアカウントアクティビティ"
} }

View File

@@ -1,419 +1,494 @@
{ {
"Account Settings": "帳戶設定", "A new verification link has been sent to your email address.": "已將新的驗證連結發送至您的電子郵件地址。",
"Manage your profile information, security settings, and login history": "管理您的個人資訊、安全設定與登入紀錄", "Account created successfully.": "帳號已成功建立。",
"Profile Information": "個人基本資料", "Account deleted successfully.": "帳號已成功刪除。",
"Update your account's profile information and email address.": "更新您的帳號姓名、手機號碼與電子郵件地址。",
"Update Password": "更改密碼",
"Ensure your account is using a long, random password to stay secure.": "確保您的帳號使用了足夠強度的隨機密碼以維持安全。",
"Delete Account": "刪除帳號",
"Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.": "一旦您的帳號被刪除,其所有資源和數據將被永久刪除。在刪除帳號之前,請下載您希望保留的任何數據或資訊。",
"Are you sure you want to delete your account?": "您確定要刪除您的帳號嗎?",
"Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.": "帳號一旦刪除,所有關連數據將被永久移除。請輸入您的密碼以確認您希望永久刪除此帳號。",
"Login History": "登入歷史",
"Name": "姓名",
"Phone": "手機號碼",
"Email": "電子郵件",
"Current Password": "當前密碼",
"New Password": "新密碼",
"Confirm Password": "確認新密碼",
"Save": "儲存變更",
"Saved.": "已儲存",
"Update": "更新",
"Cancel": "取消",
"Confirm": "確認",
"Danger Zone: Delete Account": "危險區域:刪除帳號",
"Permanently Delete Account": "永久刪除帳號",
"Password": "密碼",
"Enter your password to confirm": "請輸入您的密碼以確認",
"Dashboard": "儀表板",
"Connectivity Status": "連線狀態概況",
"Real-time status monitoring": "即時監控機台連線動態",
"LIVE": "實時",
"Online Machines": "在線機台",
"Offline Machines": "離線機台",
"Alerts Pending": "待處理告警",
"Total Connected": "總計連線數",
"Monthly Transactions": "本月交易統計",
"Monthly cumulative revenue overview": "本月累計營收概況",
"Today's Transactions": "今日交易額",
"vs Yesterday": "較昨日",
"Yesterday": "昨日",
"Day Before": "前日",
"Machine Status List": "機台運行狀態列表",
"Total items": "總計 :count 項",
"Real-time monitoring across all machines": "跨機台即時狀態監控",
"Quick search...": "快速搜尋...",
"Machine Info": "機台資訊",
"Running Status": "運行狀態",
"Today Cumulative Sales": "今日累積銷售",
"Current Stock": "當前庫存",
"Last Signal": "最後訊號時間",
"Alert Summary": "告警摘要",
"No alert summary": "暫無告警記錄",
"No data available": "暫無資料",
"Showing :from to :to of :total items": "顯示第 :from 到 :to 項,共 :total 項",
"Previous": "上一頁",
"Next": "下一頁",
"Profile Settings": "個人設定",
"Profile": "個人檔案",
"Member Management": "會員管理",
"Member List": "會員列表",
"Membership Tiers": "會員等級",
"Deposit Bonus": "儲值回饋",
"Point Rules": "點數規則",
"Gift Definitions": "禮品設定",
"Machine Management": "機台管理",
"Machine Logs": "機台日誌",
"Machine List": "機台列表",
"Machine Permissions": "機台權限",
"Utilization Rate": "機台稼動率",
"Expiry Management": "效期管理",
"Maintenance Records": "維修管理單",
"APP Management": "APP管理",
"UI Elements": "UI元素",
"Helper": "小幫手",
"Questionnaire": "問卷",
"Games": "互動遊戲",
"Timer": "計時器",
"Warehouse Management": "倉庫管理",
"Warehouse List (All)": "倉庫列表(全)",
"Warehouse List (Individual)": "倉庫列表(個)",
"Stock Management": "庫存管理單",
"Transfers": "調撥單",
"Purchases": "採購單",
"Replenishments": "機台補貨單",
"Replenishment Records": "機台補貨紀錄",
"Machine Stock": "機台庫存",
"Staff Stock": "人員庫存",
"Returns": "回庫單",
"Sales Management": "銷售管理",
"Sales Records": "銷售紀錄",
"Pickup Codes": "取貨碼",
"Orders": "購買單",
"Promotions": "促銷時段",
"Pass Codes": "通行碼",
"Store Gifts": "來店禮",
"Analysis Management": "分析管理",
"Change Stock": "零錢庫存",
"Machine Reports": "機台報表",
"Product Reports": "商品報表",
"Survey Analysis": "問卷分析",
"Audit Management": "稽核管理",
"Purchase Audit": "採購單",
"Transfer Audit": "調撥單",
"Replenishment Audit": "補貨單",
"Data Configuration": "資料設定",
"Product Management": "商品管理",
"Advertisement Management": "廣告管理",
"Admin Sellable Products": "管理者可賣",
"Account Management": "帳號管理", "Account Management": "帳號管理",
"Sub Accounts": "子帳號", "Account Settings": "帳戶設定",
"Sub Account Roles": "子帳號角色",
"Point Settings": "點數設定",
"Badge Settings": "識別證",
"Remote Management": "遠端管理",
"Machine Restart": "機台重啟",
"Card Reader Restart": "卡機重啟",
"Remote Checkout": "遠端結帳",
"Remote Lock": "遠端鎖定",
"Remote Change": "遠端找零",
"Remote Dispense": "遠端出貨",
"Line Management": "Line管理",
"Line Members": "Line會員",
"Line Machines": "Line機台",
"Line Products": "Line商品",
"Line Official Account": "Line生活圈",
"Line Orders": "Line訂單",
"Line Coupons": "Line優惠券",
"Reservation System": "預約系統",
"Reservation Members": "預約會員",
"Store Management": "店家管理",
"Time Slots": "時段組合",
"Venue Management": "場地管理",
"Coupons": "優惠券",
"Reservations": "預約管理",
"Order Management": "訂單管理",
"Special Permission": "特殊權限",
"Clear Stock": "庫存清空",
"APK Versions": "APK版本",
"Discord Notifications": "Discord通知",
"Basic Settings": "基本設定",
"Machine Settings": "機台設定",
"Permission Settings": "權限設定",
"APP Features": "APP功能",
"Sales": "銷售管理",
"Others": "其他功能",
"AI Prediction": "AI智能預測",
"Roles": "角色權限",
"Role Management": "角色權限管理",
"Define and manage security roles and permissions.": "定義並管理系統安全角色與權限。",
"Search roles...": "搜尋角色...",
"No permissions": "無權限項目",
"No roles found.": "找不到角色資料。",
"Create Role": "建立角色",
"Edit Role": "編輯角色",
"Update existing role and permissions.": "更新現有角色與權限設定。",
"Create a new role and assign permissions.": "建立新角色並分配對應權限。",
"Enter role name": "請輸入角色名稱",
"Add Role": "新增角色",
"Role Name": "角色名稱",
"Type": "類型",
"Permissions": "權限",
"Users": "帳號數",
"System role name cannot be modified.": "內建系統角色的名稱無法修改。",
"The Super Admin role name cannot be modified.": "超級管理員角色的名稱無法修改。",
"System Level": "系統層級",
"Company Level": "客戶層級",
"Global roles accessible by all administrators.": "適用於所有管理者的全域角色。",
"Roles scoped to specific customer companies.": "適用於各個客戶單位的特定角色。",
"members": "會員管理",
"machines": "機台管理",
"app": "APP 管理",
"warehouses": "倉庫管理",
"sales": "銷售管理",
"analysis": "分析管理",
"audit": "稽核管理",
"data-config": "資料設定",
"remote": "遠端管理",
"line": "Line 管理",
"reservation": "預約系統",
"special-permission": "特殊權限",
"companies": "客戶管理",
"accounts": "帳號管理",
"roles": "角色權限",
"Role Permissions": "角色權限",
"Role Settings": "角色權限",
"No login history yet": "尚無登入紀錄",
"Signed in as": "登入身份",
"Logout": "登出",
"Joined": "加入日期",
"Recent Login": "最近登入",
"Total Logins": "總登入次數",
"Account Status": "帳號狀態", "Account Status": "帳號狀態",
"Active": "使用中", "Account updated successfully.": "帳號已成功更新。",
"Customer Management": "客戶管理", "accounts": "帳號管理",
"Manage all tenant accounts and validity": "管理所有租戶帳號與合約效期",
"Add Customer": "新增客戶",
"Total Customers": "客戶總數",
"Expired / Disabled": "已過期 / 停用",
"Search customers...": "搜尋客戶...",
"All": "全部",
"Disabled": "已停用",
"Customer Info": "客戶資訊",
"Accounts / Machines": "帳號 / 機台", "Accounts / Machines": "帳號 / 機台",
"Valid Until": "合約到期日",
"Actions": "操作",
"Permanent": "永久授權",
"Are you sure to delete this customer?": "您確定要刪除此客戶嗎?",
"No customers found": "找不到客戶資料",
"Edit Customer": "編輯客戶",
"Update Customer": "更新客戶",
"Create": "建立",
"Company Name": "公司名稱",
"Company Code": "公司代碼",
"Tax ID (Optional)": "統一編號 (選填)",
"Status": "狀態",
"Contact Name": "聯絡人姓名",
"Contact Phone": "聯絡人電話",
"Contact Email": "聯絡人信箱",
"Notes": "備註",
"Customer created successfully.": "客戶新增成功",
"Customer updated successfully.": "客戶更新成功",
"Customer deleted successfully.": "客戶刪除成功",
"Cannot delete company with active accounts.": "無法刪除仍有帳號的客戶",
"Contract Until (Optional)": "合約到期日 (選填)",
"Company Information": "公司資訊",
"Initial Admin Account": "初始管理帳號",
"Optional": "選填",
"Username": "使用者帳號",
"Enter login ID": "請輸入登入帳號",
"Min 8 characters": "至少 8 個字元",
"Admin display name": "管理員顯示名稱",
"Initial Role": "初始角色",
"Contact & Details": "聯絡資訊與詳情",
"e.g. Taiwan Star": "例如:台灣之星",
"e.g. TWSTAR": "例如TWSTAR",
"Manage administrative and tenant accounts": "管理系統管理者與租戶帳號",
"Add Account": "新增帳號",
"All Companies": "所有公司",
"User Info": "用戶資訊",
"Company": "所屬客戶",
"Belongs To": "所屬單位",
"Role": "角色",
"SYSTEM": "系統層級",
"No users found": "找不到用戶資料",
"Data Configuration Permissions": "資料設定權限",
"Sales Permissions": "銷售管理權限",
"Machine Management Permissions": "機台管理權限",
"Warehouse Permissions": "倉庫管理權限",
"Analysis Permissions": "分析管理權限",
"Audit Permissions": "稽核管理權限",
"Remote Permissions": "遠端管理權限",
"Line Permissions": "Line 管理權限",
"Save Changes": "儲存變更",
"User": "一般用戶",
"Admin": "管理員",
"Super Admin": "超級管理員",
"e.g. John Doe": "例如:張曉明",
"e.g. johndoe": "例如xiaoming",
"Search users...": "搜尋用戶...",
"Admin Name": "管理員姓名",
"New Password (leave blank to keep current)": "新密碼 (若不修改請留空)",
"Are you sure you want to delete this account?": "您確定要刪除此帳號嗎?",
"Show": "顯示",
"to": "至",
"of": "總計",
"items": "筆項目",
"Showing": "顯示第",
"Full Name": "全名",
"super-admin": "超級管理員",
"admin": "管理員",
"user": "一般用戶",
"Product Status": "商品狀態",
"Monitor events and system activity across your vending fleet.": "跨機台連線動態與系統日誌監控。",
"All Machines": "所有機台",
"All Levels": "所有層級",
"Timestamp": "時間戳記",
"Message Content": "日誌內容",
"No matching logs found": "找不到符合條件的日誌",
"Unknown": "未知",
"Info": "一般",
"Warning": "警告",
"Error": "異常",
"Management of operational parameters": "機台運作參數管理",
"Add Machine": "新增機台",
"Search machines...": "搜尋機台...",
"Items": "個項目",
"Machine Name": "機台名稱",
"Serial No": "機台序號",
"Owner": "所屬公司",
"Model": "機台型號",
"Action": "操作", "Action": "操作",
"No location set": "尚未設定位置", "Actions": "操作",
"Edit Settings": "編輯設定", "Active": "使用中",
"Enter machine name": "請輸入機台名稱", "Add Account": "新增帳號",
"Enter serial number": "請輸入機台序號", "Add Customer": "新增客戶",
"Select Owner": "選擇所屬公司", "Add Machine": "新增機台",
"Select Model": "選擇型號", "Add Machine Model": "新增機台型號",
"Customer Payment Config": "客戶金流設定", "Add Role": "新增角色",
"Not Used": "不使用", "Admin": "管理員",
"Edit Machine Settings": "編輯機台設定", "admin": "管理員",
"Operational Parameters": "運作參數", "Admin display name": "管理員顯示名稱",
"Card Reader Seconds": "刷卡機秒數", "Admin Name": "管理員姓名",
"Payment Buffer Seconds": "金流緩衝時間(s)", "Admin Sellable Products": "管理者可賣",
"Checkout Time 1": "卡機結帳時間1", "Administrator": "管理員",
"Checkout Time 2": "卡機結帳時間2", "Advertisement Management": "廣告管理",
"Heating Start Time": "開啟-加熱時間", "Affiliation": "所屬單位",
"Heating End Time": "關閉-加熱時間", "AI Prediction": "AI智能預測",
"Hardware & Slots": "硬體與貨道", "Alert Summary": "告警摘要",
"Card Reader No": "刷卡機編號", "Alerts Pending": "待處理告警",
"Key No": "鑰匙編號", "All": "全部",
"Slot Mechanism (default: Conveyor, check for Spring)": "貨道機制 (預設履帶,勾選為彈簧)", "All Affiliations": "全部單位",
"Payment & Invoice": "金流與發票", "All Companies": "所有公司",
"Invoice Status": "發票開立狀態", "All Levels": "所有層級",
"No Invoice": "不開立發票", "All Machines": "所有機台",
"Default Donate": "預設捐贈", "All Times System Timezone": "所有時間為系統時區",
"Default Not Donate": "預設不捐贈", "analysis": "分析管理",
"Member & External": "會員與外部系統", "Analysis Management": "分析管理",
"Welcome Gift": "註冊成效禮", "Analysis Permissions": "分析管理權限",
"Enabled/Disabled": "啟用/停用", "API Token": "API 金鑰",
"Member System": "會員系統", "APK Versions": "APK版本",
"Payment Configuration": "客戶金流設定", "app": "APP 管理",
"Merchant payment gateway settings management": "特約商店支付網關參數管理", "APP Features": "APP功能",
"Create Config": "建立配置", "APP Management": "APP管理",
"Config Name": "配置名稱",
"Last Updated": "最後更新日期",
"Are you sure you want to delete this configuration?": "您確定要刪除此金流配置嗎?",
"Create Payment Config": "建立金流配置",
"Define new third-party payment parameters": "定義新的第三方支付介接參數",
"Save Config": "儲存配置",
"Configuration Name": "金流組合名稱",
"Belongs To Company": "所屬客戶公司",
"ECPay Invoice": "綠界發票",
"Store ID": "特約商店代號 (MerchantID)",
"HashKey": "HashKey",
"HashIV": "HashIV",
"E.SUN QR Scan": "玉山掃碼",
"StoreID": "商店代號 (StoreID)",
"TermID": "終端代號 (TermID)",
"Key": "金鑰 (Key)",
"LINE Pay Direct": "Line官方支付",
"ChannelId": "ChannelId",
"ChannelSecret": "ChannelSecret",
"TapPay Integration": "TapPay 整合支付",
"PARTNER_KEY": "PARTNER_KEY",
"APP_ID": "APP_ID", "APP_ID": "APP_ID",
"APP_KEY": "APP_KEY", "APP_KEY": "APP_KEY",
"Merchant IDs": "商店代號群", "Are you sure to delete this customer?": "您確定要刪除此客戶嗎?",
"LINE_MERCHANT_ID": "LINE Pay 商店代號", "Are you sure you want to delete this account?": "您確定要刪除此帳號嗎?",
"JKO_MERCHANT_ID": "街口支付 商店代號", "Are you sure you want to delete this account? This action cannot be undone.": "確定要刪除此帳號嗎?此操作無法復原。",
"PI_MERCHANT_ID": "Pi 拍錢包 商店代號", "Are you sure you want to delete this configuration?": "您確定要刪除此金流配置嗎?",
"PS_MERCHANT_ID": "全盈+Pay 商店代號", "Are you sure you want to delete this configuration? This action cannot be undone.": "您確定要刪除此金流配置嗎?此操作將無法復原。",
"EASY_MERCHANT_ID": "悠遊付 商店代號", "Are you sure you want to delete this item? This action cannot be undone.": "確定要刪除此項目嗎?此操作無法復原。",
"basic-settings": "基本設定", "Are you sure you want to delete this role? This action cannot be undone.": "您確定要刪除此角色嗎?此操作將無法復原。",
"permissions": "權限設定", "Are you sure you want to delete your account?": "您確定要刪除您的帳號嗎?",
"Edit Payment Config": "編輯金流配置", "Are you sure you want to proceed? This action cannot be undone.": "您確定要繼續嗎?此操作將無法復原。",
"Machine Model Settings": "機台型號設定",
"Machine Model": "機台型號",
"Model Name": "型號名稱",
"Machine Count": "機台數量",
"Add Machine Model": "新增機台型號",
"Edit Machine Model": "編輯機台型號",
"Machine model created successfully.": "機台型號已成功建立。",
"Machine model updated successfully.": "機台型號已成功更新。",
"Machine model deleted successfully.": "機台型號已成功刪除。",
"Cannot delete model that is currently in use by machines.": "無法刪除目前正在被機台使用的型號。",
"Machine Details": "機台詳情",
"Create Machine": "新增機台",
"Edit Machine": "編輯機台",
"Basic Information": "基本資訊",
"Location": "位置",
"Temperature": "溫度",
"Firmware Version": "韌體版本",
"Last Heartbeat": "最後心跳時間",
"Never Connected": "從未連線",
"View Logs": "查看日誌",
"Real-time Operation Logs (Last 50)": "即時操作日誌 (最後 50 筆)",
"All Times System Timezone": "所有時間為系統時區",
"Level": "層級",
"Logs": "日誌",
"Time": "時間",
"Message": "訊息",
"Online": "線上",
"Offline": "離線",
"Connecting...": "連線中",
"No logs found": "暫無相關日誌",
"Management of operational parameters and models": "管理運作參數與型號",
"ECPay Invoice Settings Description": "綠界科技電子發票設定",
"E.SUN QR Scan Settings Description": "玉山銀行掃碼支付設定",
"LINE Pay Direct Settings Description": "LINE Pay 官方直連設定",
"TapPay Integration Settings Description": "喬睿科技支付串接設定",
"Parameters": "參數設定",
"Hardware & Network": "硬體與網路",
"Serial & Version": "序號與版本",
"Heartbeat": "心跳狀態",
"Heating Range": "加熱時段",
"API Token": "API 金鑰",
"Close Panel": "關閉面板",
"Operations": "運作設定",
"Payment Config": "金流配置",
"Machine Images": "機台照片",
"No images uploaded": "尚未上傳照片",
"Upload New Images": "上傳新照片",
"Max 3": "最多 3 張",
"Uploading new images will replace all existing images.": "上傳新照片將會取代所有現有照片。",
"Search models...": "搜尋型號...",
"Card Reader": "刷卡機",
"View Details": "查看詳情",
"Are you sure?": "確定要執行此操作嗎?", "Are you sure?": "確定要執行此操作嗎?",
"Machines": "機台列表", "audit": "稽核管理",
"Models": "型號列表", "Audit Management": "稽核管理",
"Edit": "編輯", "Audit Permissions": "稽核管理權限",
"Avatar updated successfully.": "頭像已成功更新。",
"Badge Settings": "識別證",
"Basic Information": "基本資訊",
"Basic Settings": "基本設定",
"basic-settings": "基本設定",
"Belongs To": "所屬公司",
"Belongs To Company": "所屬公司",
"Cancel": "取消",
"Cannot delete company with active accounts.": "無法刪除仍有帳號的客戶",
"Cannot delete model that is currently in use by machines.": "無法刪除目前正在被機台使用的型號。",
"Cannot Delete Role": "無法刪除該角色",
"Cannot delete role with active users.": "無法刪除已有綁定帳號的角色。",
"Card Reader": "刷卡機",
"Card Reader No": "刷卡機編號",
"Card Reader Restart": "卡機重啟",
"Card Reader Seconds": "刷卡機秒數",
"Change": "更換",
"Change Stock": "零錢庫存",
"ChannelId": "ChannelId",
"ChannelSecret": "ChannelSecret",
"Checkout Time 1": "卡機結帳時間1",
"Checkout Time 2": "卡機結帳時間2",
"Clear Stock": "庫存清空",
"Click here to re-send the verification email.": "點擊此處重新發送驗證郵件。",
"Click to upload": "點擊上傳",
"Close Panel": "關閉面板",
"companies": "客戶管理",
"Company": "所屬客戶",
"Company Code": "公司代碼",
"Company Information": "公司資訊",
"Company Level": "客戶層級",
"Company Name": "公司名稱",
"Config Name": "配置名稱",
"Configuration Name": "配置名稱",
"Confirm": "確認",
"Confirm Deletion": "確認刪除資料",
"Confirm Password": "確認新密碼",
"Connecting...": "連線中",
"Connectivity Status": "連線狀態概況",
"Contact & Details": "聯絡資訊與詳情",
"Contact Email": "聯絡人信箱",
"Contact Name": "聯絡人姓名",
"Contact Phone": "聯絡人電話",
"Contract Until (Optional)": "合約到期日 (選填)",
"Coupons": "優惠券",
"Create": "建立",
"Create a new role and assign permissions.": "建立新角色並分配對應權限。",
"Create Config": "建立配置",
"Create Machine": "新增機台",
"Create Payment Config": "新增金流配置",
"Create Role": "建立角色",
"Current Password": "當前密碼",
"Current Stock": "當前庫存",
"Customer created successfully.": "客戶新增成功",
"Customer deleted successfully.": "客戶刪除成功",
"Customer Info": "客戶資訊",
"Customer Management": "客戶管理",
"Customer Payment Config": "客戶金流設定",
"Customer updated successfully.": "客戶更新成功",
"Danger Zone: Delete Account": "危險區域:刪除帳號",
"Dashboard": "儀表板",
"Data Configuration": "資料設定",
"Data Configuration Permissions": "資料設定權限",
"data-config": "資料設定",
"Day Before": "前日",
"Default Donate": "預設捐贈",
"Default Not Donate": "預設不捐贈",
"Define and manage security roles and permissions.": "定義並管理系統安全角色與權限。",
"Define new third-party payment parameters": "定義新的第三方支付參數",
"Delete": "刪除", "Delete": "刪除",
"None": "", "Delete Account": "刪除帳號",
"Select Company": "選擇所屬公司", "Delete Permanently": "確認永久刪除資料",
"Deposit Bonus": "儲值回饋",
"Detail": "詳細",
"Disabled": "已停用",
"Discord Notifications": "Discord通知",
"e.g. John Doe": "例如:張曉明",
"e.g. johndoe": "例如xiaoming",
"e.g. Taiwan Star": "例如:台灣之星",
"e.g. TWSTAR": "例如TWSTAR",
"e.g., Company Standard Pay": "例如:公司標準支付", "e.g., Company Standard Pay": "例如:公司標準支付",
"Photo Slot": "照片欄位", "e.g., Taipei Station": "例如:台北車站",
"E.SUN QR Scan": "玉山銀行標籤支付",
"E.SUN QR Scan Settings Description": "玉山銀行掃碼支付設定",
"EASY_MERCHANT_ID": "悠遊付 商店代號",
"ECPay Invoice": "綠界電子發票",
"ECPay Invoice Settings Description": "綠界科技電子發票設定",
"Edit": "編輯",
"Edit Account": "編輯帳號",
"Edit Customer": "編輯客戶",
"Edit Machine": "編輯機台",
"Edit Machine Model": "編輯機台型號",
"Edit Machine Settings": "編輯機台設定",
"Edit Payment Config": "編輯金流配置",
"Edit Role": "編輯角色",
"Edit Settings": "編輯設定",
"Email": "電子郵件",
"Enabled/Disabled": "啟用/停用",
"Ensure your account is using a long, random password to stay secure.": "確保您的帳號使用了足夠強度的隨機密碼以維持安全。",
"Enter login ID": "請輸入登入帳號",
"Enter machine location": "請輸入機台地點",
"Enter machine name": "請輸入機台名稱",
"Enter model name": "請輸入型號名稱",
"Enter role name": "請輸入角色名稱",
"Enter serial number": "請輸入機台序號",
"Enter your password to confirm": "請輸入您的密碼以確認",
"Error": "異常",
"Expired / Disabled": "已過期 / 停用",
"Expiry Management": "效期管理",
"Failed to update machine images: ": "更新機台圖片失敗:",
"files selected": "個檔案已選擇",
"Firmware Version": "韌體版本",
"Full Name": "全名",
"Games": "互動遊戲",
"Gift Definitions": "禮品設定",
"Global roles accessible by all administrators.": "適用於所有管理者的全域角色。",
"Got it": "知道了",
"Hardware & Network": "硬體與網路",
"Hardware & Slots": "硬體與貨道",
"HashIV": "HashIV",
"HashKey": "HashKey",
"Heartbeat": "心跳狀態",
"Heating End Time": "關閉-加熱時間",
"Heating Range": "加熱時段",
"Heating Start Time": "開啟-加熱時間",
"Helper": "小幫手",
"Info": "一般",
"Initial Admin Account": "初始管理帳號",
"Initial Role": "初始角色",
"Invoice Status": "發票開立狀態",
"items": "筆項目",
"Items": "個項目",
"JKO_MERCHANT_ID": "街口支付 商店代號",
"john@example.com": "john@example.com",
"Joined": "加入日期",
"Key": "金鑰 (Key)",
"Key No": "鑰匙編號",
"Last Heartbeat": "最後心跳時間",
"Last Signal": "最後訊號時間",
"Last Updated": "最後更新日期",
"Level": "層級",
"line": "Line 管理",
"Line Coupons": "Line優惠券",
"Line Machines": "Line機台",
"Line Management": "Line管理",
"Line Members": "Line會員",
"Line Official Account": "Line生活圈",
"Line Orders": "Line訂單",
"LINE Pay Direct": "LINE Pay 官方直連",
"LINE Pay Direct Settings Description": "LINE Pay 官方直連設定",
"Line Permissions": "Line 管理權限",
"Line Products": "Line商品",
"LINE_MERCHANT_ID": "LINE Pay 商店代號",
"LIVE": "實時",
"Location": "位置",
"Login History": "登入歷史",
"Logout": "登出",
"Logs": "日誌",
"Machine Count": "機台數量",
"Machine created successfully.": "機台已成功建立。",
"Machine Details": "機台詳情",
"Machine Images": "機台照片",
"Machine images updated successfully.": "機台圖片已成功更新。",
"Machine Info": "機台資訊",
"Machine List": "機台列表",
"Machine Logs": "機台日誌",
"Machine Management": "機台管理",
"Machine Management Permissions": "機台管理權限",
"Machine Model": "機台型號",
"Machine model created successfully.": "機台型號已成功建立。",
"Machine model deleted successfully.": "機台型號已成功刪除。",
"Machine Model Settings": "機台型號設定",
"Machine model updated successfully.": "機台型號已成功更新。",
"Machine Name": "機台名稱",
"Machine Permissions": "機台權限",
"Machine Reports": "機台報表",
"Machine Restart": "機台重啟",
"Machine Settings": "機台設定",
"Machine settings updated successfully.": "機台設定已成功更新。",
"Machine Status List": "機台運行狀態列表",
"Machine Stock": "機台庫存",
"machines": "機台管理",
"Machines": "機台列表",
"Maintenance Records": "維修管理單",
"Manage administrative and tenant accounts": "管理系統管理者與租戶帳號",
"Manage all tenant accounts and validity": "管理所有租戶帳號與合約效期",
"Manage your profile information, security settings, and login history": "管理您的個人資訊、安全設定與登入紀錄",
"Management of operational parameters": "機台運作參數管理",
"Management of operational parameters and models": "管理運作參數與型號",
"Max 3": "最多 3 張",
"Member & External": "會員與外部系統",
"Member List": "會員列表",
"Member Management": "會員管理",
"Member System": "會員系統",
"members": "會員管理",
"Membership Tiers": "會員等級",
"Menu Permissions": "選單權限",
"Merchant IDs": "特店代號清單",
"Merchant payment gateway settings management": "特約商店支付網關參數管理",
"Message": "訊息",
"Message Content": "日誌內容",
"Min 8 characters": "至少 8 個字元",
"Model": "機台型號",
"Model Name": "型號名稱",
"Models": "型號列表",
"Modifying your own administrative permissions may result in losing access to certain system functions.": "修改自身管理權限可能導致失去對部分系統功能的控制權。",
"Monitor events and system activity across your vending fleet.": "跨機台連線動態與系統日誌監控。",
"Monthly cumulative revenue overview": "本月累計營收概況",
"Monthly Transactions": "本月交易統計",
"Name": "姓名",
"Never Connected": "從未連線",
"New Password": "新密碼",
"New Password (leave blank to keep current)": "新密碼 (若不修改請留空)",
"Next": "下一頁",
"No accounts found": "找不到帳號資料",
"No alert summary": "暫無告警記錄",
"No configurations found": "暫無相關配置",
"No customers found": "找不到客戶資料",
"No data available": "暫無資料",
"No file uploaded.": "未上傳任何檔案。",
"No images uploaded": "尚未上傳照片",
"No Invoice": "不開立發票",
"No location set": "尚未設定位置",
"No login history yet": "尚無登入紀錄",
"No logs found": "暫無相關日誌",
"No matching logs found": "找不到符合條件的日誌",
"No permissions": "無權限項目",
"No roles found.": "找不到角色資料。",
"No users found": "找不到用戶資料",
"None": "無",
"Not Used": "不使用",
"Not Used Description": "不使用第三方支付介接",
"Notes": "備註",
"of": "總計",
"Offline": "離線",
"Offline Machines": "離線機台",
"Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting your account, please download any data or information that you wish to retain.": "一旦您的帳號被刪除,其所有資源和數據將被永久刪除。在刪除帳號之前,請下載您希望保留的任何數據或資訊。",
"Once your account is deleted, all of its resources and data will be permanently deleted. Please enter your password to confirm you would like to permanently delete your account.": "帳號一旦刪除,所有關連數據將被永久移除。請輸入您的密碼以確認您希望永久刪除此帳號。",
"Online": "線上",
"Online Machines": "在線機台",
"Only system roles can be assigned to platform administrative accounts.": "僅系統角色可指派給平台管理帳號。",
"Operational Parameters": "運作參數",
"Operations": "運作設定",
"Optimized for display. Supported formats: JPG, PNG, WebP.": "已針對顯示進行優化。支援格式JPG, PNG, WebP。", "Optimized for display. Supported formats: JPG, PNG, WebP.": "已針對顯示進行優化。支援格式JPG, PNG, WebP。",
"Change": "更換" "Optional": "選填",
"Order Management": "訂單管理",
"Orders": "購買單",
"Others": "其他功能",
"Owner": "所屬公司",
"Parameters": "參數設定",
"PARTNER_KEY": "PARTNER_KEY",
"Pass Codes": "通行碼",
"Password": "密碼",
"Password updated successfully.": "密碼已成功變更。",
"Payment & Invoice": "金流與發票",
"Payment Buffer Seconds": "金流緩衝時間(s)",
"Payment Config": "金流配置",
"Payment Configuration": "客戶金流設定",
"Payment Configuration created successfully.": "金流設定已成功建立。",
"Payment Configuration deleted successfully.": "金流設定已成功刪除。",
"Payment Configuration updated successfully.": "金流設定已成功更新。",
"Permanent": "永久授權",
"Permanently Delete Account": "永久刪除帳號",
"Permission Settings": "權限設定",
"Permissions": "權限",
"permissions": "權限設定",
"Phone": "手機號碼",
"Photo Slot": "照片欄位",
"PI_MERCHANT_ID": "Pi 拍錢包 商店代號",
"Pickup Codes": "取貨碼",
"Please check the following errors:": "請檢查以下錯誤:",
"Please check the form for errors.": "請檢查欄位內容是否正確。",
"Point Rules": "點數規則",
"Point Settings": "點數設定",
"Previous": "上一頁",
"Product Management": "商品管理",
"Product Reports": "商品報表",
"Product Status": "商品狀態",
"Profile": "個人檔案",
"Profile Information": "個人基本資料",
"Profile Settings": "個人設定",
"Profile updated successfully.": "個人資料已成功更新。",
"Promotions": "促銷時段",
"Protected": "受保護",
"PS_MERCHANT_ID": "全盈+Pay 商店代號",
"Purchase Audit": "採購單",
"Purchases": "採購單",
"Questionnaire": "問卷",
"Quick search...": "快速搜尋...",
"Real-time monitoring across all machines": "跨機台即時狀態監控",
"Real-time Operation Logs (Last 50)": "即時操作日誌 (最後 50 筆)",
"Real-time status monitoring": "即時監控機台連線動態",
"Recent Login": "最近登入",
"remote": "遠端管理",
"Remote Change": "遠端找零",
"Remote Checkout": "遠端結帳",
"Remote Dispense": "遠端出貨",
"Remote Lock": "遠端鎖定",
"Remote Management": "遠端管理",
"Remote Permissions": "遠端管理權限",
"Replenishment Audit": "補貨單",
"Replenishment Records": "機台補貨紀錄",
"Replenishments": "機台補貨單",
"reservation": "預約系統",
"Reservation Members": "預約會員",
"Reservation System": "預約系統",
"Reservations": "預約管理",
"Returns": "回庫單",
"Role": "角色",
"Role created successfully.": "角色已成功建立。",
"Role deleted successfully.": "角色已成功刪除。",
"Role Management": "角色權限管理",
"Role Name": "角色名稱",
"Role name already exists in this company.": "該公司已存在相同名稱的角色。",
"Role not found.": "角色不存在。",
"Role Permissions": "角色權限",
"Role Settings": "角色權限",
"Role Type": "角色類型",
"Role updated successfully.": "角色已成功更新。",
"Roles": "角色權限",
"roles": "角色權限",
"Roles scoped to specific customer companies.": "適用於各個客戶單位的特定角色。",
"Running Status": "運行狀態",
"Sales": "銷售管理",
"sales": "銷售管理",
"Sales Management": "銷售管理",
"Sales Permissions": "銷售管理權限",
"Sales Records": "銷售紀錄",
"Save": "儲存變更",
"Save Changes": "儲存變更",
"Save Config": "儲存配置",
"Saved.": "已儲存",
"Search configurations...": "搜尋設定...",
"Search customers...": "搜尋客戶...",
"Search machines...": "搜尋機台...",
"Search models...": "搜尋型號...",
"Search roles...": "搜尋角色...",
"Search users...": "搜尋用戶...",
"Select Company": "選擇所屬公司",
"Select Model": "選擇型號",
"Select Owner": "選擇所屬公司",
"Serial & Version": "序號與版本",
"Serial No": "機台序號",
"Serial Number": "機台序號",
"Show": "顯示",
"Showing": "顯示第",
"Showing :from to :to of :total items": "顯示第 :from 到 :to 項,共 :total 項",
"Sign in to your account": "隨時隨地掌控您的業務。",
"Signed in as": "登入身份",
"Slot Mechanism (default: Conveyor, check for Spring)": "貨道機制 (預設履帶,勾選為彈簧)",
"Some fields need attention": "部分欄位需要注意",
"Special Permission": "特殊權限",
"special-permission": "特殊權限",
"Staff Stock": "人員庫存",
"Status": "狀態",
"Stock Management": "庫存管理單",
"Store Gifts": "來店禮",
"Store ID": "商店代號",
"Store Management": "店家管理",
"StoreID": "商店代號 (StoreID)",
"Sub Account Management": "子帳號管理",
"Sub Account Roles": "子帳號角色",
"Sub Accounts": "子帳號",
"Success": "成功",
"Super Admin": "超級管理員",
"super-admin": "超級管理員",
"Super-admin role cannot be assigned to tenant accounts.": "超級管理員角色無法指派給租戶帳號。",
"Survey Analysis": "問卷分析",
"SYSTEM": "系統層級",
"System Level": "系統層級",
"System Role": "系統角色",
"System role name cannot be modified.": "內建系統角色的名稱無法修改。",
"System roles cannot be deleted by tenant administrators.": "租戶管理員無法刪除系統角色。",
"System roles cannot be modified by tenant administrators.": "租戶管理員無法修改系統角色。",
"System super admin accounts cannot be deleted.": "系統超級管理員帳號無法刪除。",
"System super admin accounts cannot be modified via this interface.": "系統超級管理員帳號無法透過此介面修改。",
"Systems Initializing": "系統初始化中",
"TapPay Integration": "TapPay 支付串接",
"TapPay Integration Settings Description": "喬睿科技支付串接設定",
"Tax ID (Optional)": "統一編號 (選填)",
"Temperature": "溫度",
"TermID": "終端代號 (TermID)",
"The image is too large. Please upload an image smaller than 1MB.": "圖片檔案太大,請上傳小於 1MB 的圖片。",
"The Super Admin role cannot be deleted.": "超級管理員角色不可刪除。",
"The Super Admin role is immutable.": "超級管理員角色不可修改。",
"The Super Admin role name cannot be modified.": "超級管理員角色的名稱無法修改。",
"This role belongs to another company and cannot be assigned.": "此角色屬於其他公司,無法指派。",
"Time": "時間",
"Time Slots": "時段組合",
"Timer": "計時器",
"Timestamp": "時間戳記",
"to": "至",
"Today Cumulative Sales": "今日累積銷售",
"Today's Transactions": "今日交易額",
"Total Connected": "總計連線數",
"Total Customers": "客戶總數",
"Total items": "總計 :count 項",
"Total Logins": "總登入次數",
"Transfer Audit": "調撥單",
"Transfers": "調撥單",
"Type": "類型",
"UI Elements": "UI元素",
"Unknown": "未知",
"Update": "更新",
"Update Customer": "更新客戶",
"Update existing role and permissions.": "更新現有角色與權限設定。",
"Update Password": "更改密碼",
"Update your account's profile information and email address.": "更新您的帳號姓名、手機號碼與電子郵件地址。",
"Upload New Images": "上傳新照片",
"Uploading new images will replace all existing images.": "上傳新照片將會取代所有現有照片。",
"User": "一般用戶",
"user": "一般用戶",
"User Info": "用戶資訊",
"Username": "使用者帳號",
"Users": "帳號數",
"Utilization Rate": "機台稼動率",
"Valid Until": "合約到期日",
"Venue Management": "場地管理",
"View Details": "查看詳情",
"View Logs": "查看日誌",
"vs Yesterday": "較昨日",
"Warehouse List": "倉庫清單",
"Warehouse List (All)": "倉庫列表(全)",
"Warehouse List (Individual)": "倉庫列表(個)",
"Warehouse Management": "倉庫管理",
"Warehouse Permissions": "倉庫管理權限",
"warehouses": "倉庫管理",
"Warning": "警告",
"Warning: You are editing your own role!": "警告:您正在編輯目前使用的角色!",
"Welcome Gift": "註冊成效禮",
"Yesterday": "昨日",
"You cannot assign permissions you do not possess.": "您無法指派您自身不具備的權限。",
"You cannot delete your own account.": "您無法刪除自己的帳號。",
"Your email address is unverified.": "您的電子郵件地址尚未驗證。",
"Your recent account activity": "最近的帳號活動"
} }

View File

@@ -13,159 +13,159 @@ return [
| |
*/ */
'accepted' => 'The :attribute field must be accepted.', 'accepted' => '必須接受 :attribute',
'accepted_if' => 'The :attribute field must be accepted when :other is :value.', 'accepted_if' => '當 :other 為 :value 時,必須接受 :attribute。',
'active_url' => 'The :attribute field must be a valid URL.', 'active_url' => ':attribute 並非有效的 URL',
'after' => 'The :attribute field must be a date after :date.', 'after' => ':attribute 必須在 :date 之後。',
'after_or_equal' => 'The :attribute field must be a date after or equal to :date.', 'after_or_equal' => ':attribute 必須在 :date 之後或相等。',
'alpha' => 'The :attribute field must only contain letters.', 'alpha' => ':attribute 只能包含字母。',
'alpha_dash' => 'The :attribute field must only contain letters, numbers, dashes, and underscores.', 'alpha_dash' => ':attribute 只能包含字母、數字、破折號與底線。',
'alpha_num' => 'The :attribute field must only contain letters and numbers.', 'alpha_num' => ':attribute 只能包含字母與數字。',
'any_of' => 'The :attribute field is invalid.', 'any_of' => ':attribute 無效。',
'array' => 'The :attribute field must be an array.', 'array' => ':attribute 必須是一個陣列。',
'ascii' => 'The :attribute field must only contain single-byte alphanumeric characters and symbols.', 'ascii' => ':attribute 只能包含單字節的字母、數字與符號。',
'before' => 'The :attribute field must be a date before :date.', 'before' => ':attribute 必須在 :date 之前。',
'before_or_equal' => 'The :attribute field must be a date before or equal to :date.', 'before_or_equal' => ':attribute 必須在 :date 之前或相等。',
'between' => [ 'between' => [
'array' => 'The :attribute field must have between :min and :max items.', 'array' => ':attribute 必須包含 :min :max 個項目。',
'file' => 'The :attribute field must be between :min and :max kilobytes.', 'file' => ':attribute 必須介於 :min :max KB 之間。',
'numeric' => 'The :attribute field must be between :min and :max.', 'numeric' => ':attribute 必須介於 :min :max 之間。',
'string' => 'The :attribute field must be between :min and :max characters.', 'string' => ':attribute 必須介於 :min :max 個字元之間。',
], ],
'boolean' => 'The :attribute field must be true or false.', 'boolean' => ':attribute 必須為布林值。',
'can' => 'The :attribute field contains an unauthorized value.', 'can' => ':attribute 包含未授權的值。',
'confirmed' => 'The :attribute field confirmation does not match.', 'confirmed' => ':attribute 確認欄位不符。',
'contains' => 'The :attribute field is missing a required value.', 'contains' => ':attribute 缺少必要的值。',
'current_password' => 'The password is incorrect.', 'current_password' => '目前的密碼不正確。',
'date' => 'The :attribute field must be a valid date.', 'date' => ':attribute 並非有效的日期。',
'date_equals' => 'The :attribute field must be a date equal to :date.', 'date_equals' => ':attribute 必須等於 :date',
'date_format' => 'The :attribute field must match the format :format.', 'date_format' => ':attribute 不符合格式 :format',
'decimal' => 'The :attribute field must have :decimal decimal places.', 'decimal' => ':attribute 必須有 :decimal 位小數。',
'declined' => 'The :attribute field must be declined.', 'declined' => ':attribute 必須拒絕。',
'declined_if' => 'The :attribute field must be declined when :other is :value.', 'declined_if' => ' :other :value 時,:attribute 必須拒絕。',
'different' => 'The :attribute field and :other must be different.', 'different' => ':attribute 與 :other 必須不同。',
'digits' => 'The :attribute field must be :digits digits.', 'digits' => ':attribute 必須是 :digits 位數。',
'digits_between' => 'The :attribute field must be between :min and :max digits.', 'digits_between' => ':attribute 必須介於 :min :max 位數之間。',
'dimensions' => 'The :attribute field has invalid image dimensions.', 'dimensions' => ':attribute 圖片尺寸無效。',
'distinct' => 'The :attribute field has a duplicate value.', 'distinct' => ':attribute 欄位含有重複的值。',
'doesnt_contain' => 'The :attribute field must not contain any of the following: :values.', 'doesnt_contain' => ':attribute 不得包含以下任何值::values',
'doesnt_end_with' => 'The :attribute field must not end with one of the following: :values.', 'doesnt_end_with' => ':attribute 不得以以下任何值結尾::values',
'doesnt_start_with' => 'The :attribute field must not start with one of the following: :values.', 'doesnt_start_with' => ':attribute 不得以以下任何值開頭::values',
'email' => 'The :attribute field must be a valid email address.', 'email' => ':attribute 必須是有效的電子郵件地址。',
'encoding' => 'The :attribute field must be encoded in :encoding.', 'encoding' => ':attribute 必須以 :encoding 編碼。',
'ends_with' => 'The :attribute field must end with one of the following: :values.', 'ends_with' => ':attribute 必須以以下任一值結尾::values',
'enum' => 'The selected :attribute is invalid.', 'enum' => '所選的 :attribute 無效。',
'exists' => 'The selected :attribute is invalid.', 'exists' => '所選的 :attribute 無效。',
'extensions' => 'The :attribute field must have one of the following extensions: :values.', 'extensions' => ':attribute 必須是以下副檔名之一::values',
'file' => 'The :attribute field must be a file.', 'file' => ':attribute 必須是一個檔案。',
'filled' => 'The :attribute field must have a value.', 'filled' => ':attribute 不能為空。',
'gt' => [ 'gt' => [
'array' => 'The :attribute field must have more than :value items.', 'array' => ':attribute 必須包含超過 :value 個項目。',
'file' => 'The :attribute field must be greater than :value kilobytes.', 'file' => ':attribute 必須大於 :value KB。',
'numeric' => 'The :attribute field must be greater than :value.', 'numeric' => ':attribute 必須大於 :value',
'string' => 'The :attribute field must be greater than :value characters.', 'string' => ':attribute 必須超過 :value 個字元。',
], ],
'gte' => [ 'gte' => [
'array' => 'The :attribute field must have :value items or more.', 'array' => ':attribute 必須包含 :value 個以上項目。',
'file' => 'The :attribute field must be greater than or equal to :value kilobytes.', 'file' => ':attribute 必須大於或等於 :value KB。',
'numeric' => 'The :attribute field must be greater than or equal to :value.', 'numeric' => ':attribute 必須大於或等於 :value',
'string' => 'The :attribute field must be greater than or equal to :value characters.', 'string' => ':attribute 必須大於或等於 :value 個字元。',
], ],
'hex_color' => 'The :attribute field must be a valid hexadecimal color.', 'hex_color' => ':attribute 必須是有效的十六進位色碼。',
'image' => 'The :attribute field must be an image.', 'image' => ':attribute 必須是一張圖片。',
'in' => 'The selected :attribute is invalid.', 'in' => '所選的 :attribute 無效。',
'in_array' => 'The :attribute field must exist in :other.', 'in_array' => ':attribute 必須存在於 :other 之中。',
'in_array_keys' => 'The :attribute field must contain at least one of the following keys: :values.', 'in_array_keys' => ':attribute 必須包含以下至少一個鍵::values',
'integer' => 'The :attribute field must be an integer.', 'integer' => ':attribute 必須是整數。',
'ip' => 'The :attribute field must be a valid IP address.', 'ip' => ':attribute 必須是有效的 IP 位址。',
'ipv4' => 'The :attribute field must be a valid IPv4 address.', 'ipv4' => ':attribute 必須是有效的 IPv4 位址。',
'ipv6' => 'The :attribute field must be a valid IPv6 address.', 'ipv6' => ':attribute 必須是有效的 IPv6 位址。',
'json' => 'The :attribute field must be a valid JSON string.', 'json' => ':attribute 必須是有效的 JSON 字串。',
'list' => 'The :attribute field must be a list.', 'list' => ':attribute 必須是一個列表。',
'lowercase' => 'The :attribute field must be lowercase.', 'lowercase' => ':attribute 必須是小寫。',
'lt' => [ 'lt' => [
'array' => 'The :attribute field must have less than :value items.', 'array' => ':attribute 必須包含少於 :value 個項目。',
'file' => 'The :attribute field must be less than :value kilobytes.', 'file' => ':attribute 必須小於 :value KB。',
'numeric' => 'The :attribute field must be less than :value.', 'numeric' => ':attribute 必須小於 :value',
'string' => 'The :attribute field must be less than :value characters.', 'string' => ':attribute 必須少於 :value 個字元。',
], ],
'lte' => [ 'lte' => [
'array' => 'The :attribute field must not have more than :value items.', 'array' => ':attribute 不得包含超過 :value 個項目。',
'file' => 'The :attribute field must be less than or equal to :value kilobytes.', 'file' => ':attribute 必須小於或等於 :value KB。',
'numeric' => 'The :attribute field must be less than or equal to :value.', 'numeric' => ':attribute 必須小於或等於 :value',
'string' => 'The :attribute field must be less than or equal to :value characters.', 'string' => ':attribute 必須小於或等於 :value 個字元。',
], ],
'mac_address' => 'The :attribute field must be a valid MAC address.', 'mac_address' => ':attribute 必須是有效的 MAC 位址。',
'max' => [ 'max' => [
'array' => 'The :attribute field must not have more than :max items.', 'array' => ':attribute 不得超過 :max 個項目。',
'file' => 'The :attribute field must not be greater than :max kilobytes.', 'file' => ':attribute 不得大於 :max KB。',
'numeric' => 'The :attribute field must not be greater than :max.', 'numeric' => ':attribute 不得大於 :max',
'string' => 'The :attribute field must not be greater than :max characters.', 'string' => ':attribute 不得超過 :max 個字元。',
], ],
'max_digits' => 'The :attribute field must not have more than :max digits.', 'max_digits' => ':attribute 不得超過 :max 位數。',
'mimes' => 'The :attribute field must be a file of type: :values.', 'mimes' => ':attribute 必須是以下檔案類型::values',
'mimetypes' => 'The :attribute field must be a file of type: :values.', 'mimetypes' => ':attribute 必須是以下檔案類型::values',
'min' => [ 'min' => [
'array' => 'The :attribute field must have at least :min items.', 'array' => ':attribute 至少需要 :min 個項目。',
'file' => 'The :attribute field must be at least :min kilobytes.', 'file' => ':attribute 至少需要 :min KB。',
'numeric' => 'The :attribute field must be at least :min.', 'numeric' => ':attribute 不得小於 :min',
'string' => 'The :attribute field must be at least :min characters.', 'string' => ':attribute 至少需要 :min 個字元。',
], ],
'min_digits' => 'The :attribute field must have at least :min digits.', 'min_digits' => ':attribute 至少需要 :min 位數。',
'missing' => 'The :attribute field must be missing.', 'missing' => ':attribute 必須不存在。',
'missing_if' => 'The :attribute field must be missing when :other is :value.', 'missing_if' => ' :other :value 時,:attribute 必須不存在。',
'missing_unless' => 'The :attribute field must be missing unless :other is :value.', 'missing_unless' => '除非 :other :value,否則 :attribute 必須不存在。',
'missing_with' => 'The :attribute field must be missing when :values is present.', 'missing_with' => '當 :values 存在時,:attribute 必須不存在。',
'missing_with_all' => 'The :attribute field must be missing when :values are present.', 'missing_with_all' => '當 :values 都存在時,:attribute 必須不存在。',
'multiple_of' => 'The :attribute field must be a multiple of :value.', 'multiple_of' => ':attribute 必須是 :value 的倍數。',
'not_in' => 'The selected :attribute is invalid.', 'not_in' => '所選的 :attribute 無效。',
'not_regex' => 'The :attribute field format is invalid.', 'not_regex' => ':attribute 格式無效。',
'numeric' => 'The :attribute field must be a number.', 'numeric' => ':attribute 必須是數字。',
'password' => [ 'password' => [
'letters' => 'The :attribute field must contain at least one letter.', 'letters' => ':attribute 必須包含至少一個字母。',
'mixed' => 'The :attribute field must contain at least one uppercase and one lowercase letter.', 'mixed' => ':attribute 必須包含至少一個大寫與一個小寫字母。',
'numbers' => 'The :attribute field must contain at least one number.', 'numbers' => ':attribute 必須包含至少一個數字。',
'symbols' => 'The :attribute field must contain at least one symbol.', 'symbols' => ':attribute 必須包含至少一個符號。',
'uncompromised' => 'The given :attribute has appeared in a data leak. Please choose a different :attribute.', 'uncompromised' => ':attribute 已出現在外洩資料中,請選擇其他 :attribute',
], ],
'present' => 'The :attribute field must be present.', 'present' => ':attribute 必須存在。',
'present_if' => 'The :attribute field must be present when :other is :value.', 'present_if' => '當 :other 為 :value 時,:attribute 必須存在。',
'present_unless' => 'The :attribute field must be present unless :other is :value.', 'present_unless' => '除非 :other 為 :value否則 :attribute 必須存在。',
'present_with' => 'The :attribute field must be present when :values is present.', 'present_with' => '當 :values 存在時,:attribute 必須存在。',
'present_with_all' => 'The :attribute field must be present when :values are present.', 'present_with_all' => '當 :values 都存在時,:attribute 必須存在。',
'prohibited' => 'The :attribute field is prohibited.', 'prohibited' => ':attribute 被禁止使用。',
'prohibited_if' => 'The :attribute field is prohibited when :other is :value.', 'prohibited_if' => ' :other :value 時,:attribute 被禁止使用。',
'prohibited_if_accepted' => 'The :attribute field is prohibited when :other is accepted.', 'prohibited_if_accepted' => '當 :other 被接受時,:attribute 被禁止使用。',
'prohibited_if_declined' => 'The :attribute field is prohibited when :other is declined.', 'prohibited_if_declined' => '當 :other 被拒絕時,:attribute 被禁止使用。',
'prohibited_unless' => 'The :attribute field is prohibited unless :other is in :values.', 'prohibited_unless' => '除非 :other :values 之中,否則 :attribute 被禁止使用。',
'prohibits' => 'The :attribute field prohibits :other from being present.', 'prohibits' => ':attribute 禁止 :other 存在。',
'regex' => 'The :attribute field format is invalid.', 'regex' => ':attribute 格式無效。',
'required' => 'The :attribute field is required.', 'required' => ':attribute 為必填欄位。',
'required_array_keys' => 'The :attribute field must contain entries for: :values.', 'required_array_keys' => ':attribute 必須包含以下項目::values',
'required_if' => 'The :attribute field is required when :other is :value.', 'required_if' => ' :other :value 時,:attribute 為必填。',
'required_if_accepted' => 'The :attribute field is required when :other is accepted.', 'required_if_accepted' => '當 :other 被接受時,:attribute 為必填。',
'required_if_declined' => 'The :attribute field is required when :other is declined.', 'required_if_declined' => '當 :other 被拒絕時,:attribute 為必填。',
'required_unless' => 'The :attribute field is required unless :other is in :values.', 'required_unless' => '除非 :other :values 之中,否則 :attribute 為必填。',
'required_with' => 'The :attribute field is required when :values is present.', 'required_with' => '當 :values 存在時,:attribute 為必填。',
'required_with_all' => 'The :attribute field is required when :values are present.', 'required_with_all' => '當 :values 都存在時,:attribute 為必填。',
'required_without' => 'The :attribute field is required when :values is not present.', 'required_without' => '當 :values 不存在時,:attribute 為必填。',
'required_without_all' => 'The :attribute field is required when none of :values are present.', 'required_without_all' => '當 :values 都不存在時,:attribute 為必填。',
'same' => 'The :attribute field must match :other.', 'same' => ':attribute 必須與 :other 相符。',
'size' => [ 'size' => [
'array' => 'The :attribute field must contain :size items.', 'array' => ':attribute 必須包含 :size 個項目。',
'file' => 'The :attribute field must be :size kilobytes.', 'file' => ':attribute 必須是 :size KB。',
'numeric' => 'The :attribute field must be :size.', 'numeric' => ':attribute 必須是 :size',
'string' => 'The :attribute field must be :size characters.', 'string' => ':attribute 必須是 :size 個字元。',
], ],
'starts_with' => 'The :attribute field must start with one of the following: :values.', 'starts_with' => ':attribute 必須以以下任一值開頭::values',
'string' => 'The :attribute field must be a string.', 'string' => ':attribute 必須是字串。',
'timezone' => 'The :attribute field must be a valid timezone.', 'timezone' => ':attribute 必須是有效的時區。',
'unique' => 'The :attribute has already been taken.', 'unique' => ':attribute 已被使用。',
'uploaded' => 'The :attribute failed to upload.', 'uploaded' => ':attribute 上傳失敗。',
'uppercase' => 'The :attribute field must be uppercase.', 'uppercase' => ':attribute 必須是大寫。',
'url' => 'The :attribute field must be a valid URL.', 'url' => ':attribute 必須是有效的 URL',
'ulid' => 'The :attribute field must be a valid ULID.', 'ulid' => ':attribute 必須是有效的 ULID',
'uuid' => 'The :attribute field must be a valid UUID.', 'uuid' => ':attribute 必須是有效的 UUID',
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
@@ -195,6 +195,13 @@ return [
| |
*/ */
'attributes' => [], 'attributes' => [
'name' => '姓名',
'email' => '電子郵件',
'password' => '密碼',
'current_password' => '目前密碼',
'password_confirmation' => '確認密碼',
'phone' => '電話',
],
]; ];

View File

@@ -1,72 +1,92 @@
@extends('layouts.admin') @extends('layouts.admin')
@section('content') @section('content')
<div class="space-y-8 pb-20"> <div class="space-y-2 pb-20">
<!-- Header -->
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
<!-- Header Section -->
<div class="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<a href="{{ route('admin.basic-settings.machines.index') }}" class="p-2.5 rounded-xl bg-white dark:bg-slate-900 border border-slate-100 dark:border-slate-800 text-slate-500 hover:text-cyan-500 transition-all shadow-sm"> <a href="{{ route('admin.basic-settings.machines.index') }}"
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18"/></svg> class="inline-flex items-center justify-center w-10 h-10 rounded-xl bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 text-slate-500 hover:text-cyan-500 hover:border-cyan-500/20 transition-all shadow-sm group">
<svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"/>
</svg>
</a> </a>
<div> <div>
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Edit Machine Settings') }}</h1> <h1 class="text-2xl font-black text-slate-800 dark:text-white tracking-tight items-center flex gap-3 outfit-font">
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ $machine->name }} / {{ $machine->serial_no }}</p> {{ __('Edit Machine') }}
</h1>
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1.5 flex items-center gap-2 uppercase tracking-widest leading-none">
<span class="inline-flex items-center px-2 py-1 rounded-lg bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 border border-slate-200 dark:border-slate-800 font-mono tracking-tighter">
{{ $machine->name }} / {{ $machine->serial_no }}
</span>
</p>
</div> </div>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button type="submit" form="edit-form" class="btn-luxury-primary px-8 flex items-center gap-2"> <button type="submit" form="edit-form" class="btn-luxury-primary flex items-center gap-2">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5"/></svg> <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="m4.5 12.75 6 6 9-13.5" />
</svg>
<span>{{ __('Save Changes') }}</span> <span>{{ __('Save Changes') }}</span>
</button> </button>
</div> </div>
</div> </div>
<form id="edit-form" action="{{ route('admin.basic-settings.machines.update', $machine) }}" method="POST" enctype="multipart/form-data" class="space-y-8"> <form id="edit-form" action="{{ route('admin.basic-settings.machines.update', $machine) }}" method="POST" enctype="multipart/form-data" class="space-y-6">
@csrf @csrf
@method('PUT') @method('PUT')
<!-- Validation Errors --> <!-- Validation Errors -->
@if ($errors->any()) @if ($errors->any())
<div class="luxury-card p-6 bg-rose-50/50 dark:bg-rose-500/5 border-rose-100 dark:border-rose-500/20 mb-8 animate-luxury-in"> <div class="luxury-card p-6 bg-rose-500/5 border-rose-500/10 mb-8 animate-luxury-in">
<div class="flex items-center gap-3 mb-4 text-rose-600 dark:text-rose-400"> <div class="flex items-center gap-3 mb-4 text-rose-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<h4 class="font-black text-sm tracking-tight italic">{{ __('Some fields need attention') }}</h4> <path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 11-18 0 9 9 0 0118 0zm-9 3.75h.008v.008H12v-.008z"/>
</svg>
<h4 class="font-black text-sm tracking-tight capitalize">{{ __('Some fields need attention') }}</h4>
</div> </div>
<ul class="space-y-1"> <ul class="space-y-2">
@foreach ($errors->all() as $error) @foreach ($errors->all() as $error)
<li class="text-[11px] font-bold text-rose-500/80">{{ $error }}</li> <li class="text-xs font-bold text-rose-500/80 flex items-center gap-2">
<span class="w-1 h-1 rounded-full bg-rose-500/40"></span>
{{ $error }}
</li>
@endforeach @endforeach
</ul> </ul>
</div> </div>
@endif @endif
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Left: Basic info & Hardware --> <!-- Left: Basic info & Hardware -->
<div class="lg:col-span-2 space-y-8"> <div class="lg:col-span-2 space-y-6">
<!-- Basic Information --> <!-- Basic Information -->
<div class="luxury-card rounded-3xl p-7 animate-luxury-in"> <div class="luxury-card rounded-3xl p-7 animate-luxury-in">
<div class="flex items-center gap-3 mb-6"> <div class="flex items-center gap-3 mb-8">
<div class="w-10 h-10 rounded-xl bg-emerald-500/10 flex items-center justify-center text-emerald-500"> <div class="w-10 h-10 rounded-xl bg-emerald-500/10 flex items-center justify-center text-emerald-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 18.75 4.5H5.25a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 18.75 4.5H5.25a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z"/>
</svg>
</div> </div>
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight">{{ __('Basic Information') }}</h3> <h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight">{{ __('Basic Information') }}</h3>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6">
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Machine Name') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Machine Name') }}</label>
<input type="text" name="name" value="{{ old('name', $machine->name) }}" class="luxury-input w-full" required> <input type="text" name="name" value="{{ old('name', $machine->name) }}" class="luxury-input w-full" required>
</div> </div>
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Serial Number') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Serial Number') }}</label>
<input type="text" value="{{ $machine->serial_no }}" class="luxury-input w-full bg-slate-50/50 dark:bg-slate-900/50 text-slate-400 cursor-not-allowed" readonly> <input type="text" value="{{ $machine->serial_no }}" class="luxury-input w-full bg-slate-50/50 dark:bg-slate-900/50 text-slate-400 cursor-not-allowed" readonly>
</div> </div>
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Location') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Location') }}</label>
<input type="text" name="location" value="{{ old('location', $machine->location) }}" class="luxury-input w-full" placeholder="{{ __('e.g., Taipei Station') }}"> <input type="text" name="location" value="{{ old('location', $machine->location) }}" class="luxury-input w-full" placeholder="{{ __('e.g., Taipei Station') }}">
</div> </div>
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Machine Model') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Machine Model') }}</label>
<select name="machine_model_id" class="luxury-select w-full" required> <select name="machine_model_id" class="luxury-select w-full" required>
@foreach($models as $model) @foreach($models as $model)
<option value="{{ $model->id }}" {{ old('machine_model_id', $machine->machine_model_id) == $model->id ? 'selected' : '' }}>{{ $model->name }}</option> <option value="{{ $model->id }}" {{ old('machine_model_id', $machine->machine_model_id) == $model->id ? 'selected' : '' }}>{{ $model->name }}</option>
@@ -79,35 +99,37 @@
<!-- Operational Parameters --> <!-- Operational Parameters -->
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 50ms"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 50ms">
<div class="flex items-center gap-3 mb-8"> <div class="flex items-center gap-3 mb-8">
<div class="w-10 h-10 rounded-xl bg-cyan-500/10 flex items-center justify-center text-cyan-500"> <div class="w-10 h-10 rounded-xl bg-amber-500/10 flex items-center justify-center text-amber-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 12h7.5"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 12h7.5"/>
</svg>
</div> </div>
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight">{{ __('Operational Parameters') }}</h3> <h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight">{{ __('Operational Parameters') }}</h3>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6">
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Card Reader Seconds') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Card Reader Seconds') }}</label>
<input type="number" name="card_reader_seconds" value="{{ $machine->card_reader_seconds }}" class="luxury-input w-full"> <input type="number" name="card_reader_seconds" value="{{ $machine->card_reader_seconds }}" class="luxury-input w-full">
</div> </div>
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Payment Buffer Seconds') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Payment Buffer Seconds') }}</label>
<input type="number" name="payment_buffer_seconds" value="{{ $machine->payment_buffer_seconds }}" class="luxury-input w-full"> <input type="number" name="payment_buffer_seconds" value="{{ $machine->payment_buffer_seconds }}" class="luxury-input w-full">
</div> </div>
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Checkout Time 1') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Checkout Time 1') }}</label>
<x-luxury-time-input name="card_reader_checkout_time_1" value="{{ $machine->card_reader_checkout_time_1 }}" /> <x-luxury-time-input name="card_reader_checkout_time_1" value="{{ $machine->card_reader_checkout_time_1 }}" />
</div> </div>
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Checkout Time 2') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Checkout Time 2') }}</label>
<x-luxury-time-input name="card_reader_checkout_time_2" value="{{ $machine->card_reader_checkout_time_2 }}" /> <x-luxury-time-input name="card_reader_checkout_time_2" value="{{ $machine->card_reader_checkout_time_2 }}" />
</div> </div>
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Heating Start Time') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Heating Start Time') }}</label>
<x-luxury-time-input name="heating_start_time" value="{{ $machine->heating_start_time }}" /> <x-luxury-time-input name="heating_start_time" value="{{ $machine->heating_start_time }}" />
</div> </div>
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Heating End Time') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Heating End Time') }}</label>
<x-luxury-time-input name="heating_end_time" value="{{ $machine->heating_end_time }}" /> <x-luxury-time-input name="heating_end_time" value="{{ $machine->heating_end_time }}" />
</div> </div>
</div> </div>
@@ -115,9 +137,11 @@
<!-- hardware & slot types --> <!-- hardware & slot types -->
<div class="luxury-card rounded-3xl p-7 animate-luxury-in" style="animation-delay: 150ms"> <div class="luxury-card rounded-3xl p-7 animate-luxury-in" style="animation-delay: 150ms">
<div class="flex items-center gap-3 mb-6"> <div class="flex items-center gap-3 mb-8">
<div class="w-10 h-10 rounded-xl bg-indigo-500/10 flex items-center justify-center text-indigo-500"> <div class="w-10 h-10 rounded-xl bg-amber-500/10 flex items-center justify-center text-amber-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M21 7.5V18a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 18V7.5m0-4.5h18M3 7.5h18M3 12h18M3 16.5h18"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5V18a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 18V7.5m0-4.5h18M3 7.5h18M3 12h18M3 16.5h18"/>
</svg>
</div> </div>
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight">{{ __('Hardware & Slots') }}</h3> <h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight">{{ __('Hardware & Slots') }}</h3>
</div> </div>
@@ -125,23 +149,23 @@
<div class="space-y-8"> <div class="space-y-8">
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6"> <div class="grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-6">
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Card Reader No') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Card Reader No') }}</label>
<input type="text" name="card_reader_no" value="{{ $machine->card_reader_no }}" class="luxury-input w-full"> <input type="text" name="card_reader_no" value="{{ $machine->card_reader_no }}" class="luxury-input w-full">
</div> </div>
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Key No') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Key No') }}</label>
<input type="text" name="key_no" value="{{ $machine->key_no }}" class="luxury-input w-full"> <input type="text" name="key_no" value="{{ $machine->key_no }}" class="luxury-input w-full">
</div> </div>
</div> </div>
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-4">{{ __('Slot Mechanism (default: Conveyor, check for Spring)') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-4">{{ __('Slot Mechanism (default: Conveyor, check for Spring)') }}</label>
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-4"> <div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-6 gap-4">
@foreach([['1-10', 'is_spring_slot_1_10'], ['11-20', 'is_spring_slot_11_20'], ['21-30', 'is_spring_slot_21_30'], ['31-40', 'is_spring_slot_31_40'], ['41-50', 'is_spring_slot_41_50'], ['51-60', 'is_spring_slot_51_60']] as $slot) @foreach([['1-10', 'is_spring_slot_1_10'], ['11-20', 'is_spring_slot_11_20'], ['21-30', 'is_spring_slot_21_30'], ['31-40', 'is_spring_slot_31_40'], ['41-50', 'is_spring_slot_41_50'], ['51-60', 'is_spring_slot_51_60']] as $slot)
<label class="flex items-center gap-3 p-3 rounded-xl border border-slate-100 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-900/50 cursor-pointer hover:border-cyan-500/30 transition-all"> <label class="flex items-center gap-3 p-3 rounded-xl border border-slate-100 dark:border-slate-800 bg-slate-50/50 dark:bg-slate-900/50 cursor-pointer hover:border-amber-500/30 transition-all">
<input type="hidden" name="{{ $slot[1] }}" value="0"> <input type="hidden" name="{{ $slot[1] }}" value="0">
<input type="checkbox" name="{{ $slot[1] }}" value="1" {{ $machine->{$slot[1]} ? 'checked' : '' }} class="w-4 h-4 rounded text-cyan-500 focus:ring-cyan-500/20"> <input type="checkbox" name="{{ $slot[1] }}" value="1" {{ $machine->{$slot[1]} ? 'checked' : '' }} class="w-4 h-4 rounded text-amber-500 focus:ring-amber-500/20">
<span class="text-xs font-black text-slate-600 dark:text-slate-400">{{ $slot[0] }}</span> <span class="text-xs font-bold text-slate-600 dark:text-slate-400">{{ $slot[0] }}</span>
</label> </label>
@endforeach @endforeach
</div> </div>
@@ -155,14 +179,16 @@
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 200ms"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 200ms">
<div class="flex items-center gap-3 mb-8"> <div class="flex items-center gap-3 mb-8">
<div class="w-10 h-10 rounded-xl bg-emerald-500/10 flex items-center justify-center text-emerald-500"> <div class="w-10 h-10 rounded-xl bg-emerald-500/10 flex items-center justify-center text-emerald-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M2.25 18.75a60.07 60.07 0 0 1 15.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75m0-1.5a.75.75 0 0 1 .75.75v.75m-.75 0H3m.75 0h.75m-1.5 0a.75.75 0 0 1-.75-.75V3M3 10.5v1.5m10.5-3v-4.5m0 4.5h5.25m-5.25 0V10.5m0-1.5a.75.75 0 0 1 .75-.75h.75m-1.5 0H12m.75 0h.75m-1.5 0a.75.75 0 0 1-.75-.75V3.75M12 4.5H3.75a2.25 2.25 0 0 0-2.25 2.25v10.5a2.25 2.25 0 0 0 2.25 2.25h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 18.75 4.5Z"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 18.75a60.07 60.07 0 0 1 15.797 2.101c.727.198 1.453-.342 1.453-1.096V18.75M3.75 4.5v.75m0-1.5a.75.75 0 0 1 .75.75v.75m-.75 0H3m.75 0h.75m-1.5 0a.75.75 0 0 1-.75-.75V3M3 10.5v1.5m10.5-3v-4.5m0 4.5h5.25m-5.25 0V10.5m0-1.5a.75.75 0 0 1 .75-.75h.75m-1.5 0H12m.75 0h.75m-1.5 0a.75.75 0 0 1-.75-.75V3.75M12 4.5H3.75a2.25 2.25 0 0 0-2.25 2.25v10.5a2.25 2.25 0 0 0 2.25 2.25h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 18.75 4.5Z"/>
</svg>
</div> </div>
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight">{{ __('Payment & Invoice') }}</h3> <h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight">{{ __('Payment & Invoice') }}</h3>
</div> </div>
<div class="space-y-6"> <div class="space-y-6">
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Payment Config') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Payment Config') }}</label>
<select name="payment_config_id" class="luxury-select w-full"> <select name="payment_config_id" class="luxury-select w-full">
<option value="">{{ __('Not Used') }}</option> <option value="">{{ __('Not Used') }}</option>
@foreach($paymentConfigs as $config) @foreach($paymentConfigs as $config)
@@ -172,7 +198,7 @@
</div> </div>
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Invoice Status') }}</label> <label class="block text-xs font-bold text-slate-400 uppercase tracking-[0.15em] mb-2">{{ __('Invoice Status') }}</label>
<select name="invoice_status" class="luxury-select w-full"> <select name="invoice_status" class="luxury-select w-full">
<option value="0" {{ $machine->invoice_status == 0 ? 'selected' : '' }}>{{ __('No Invoice') }}</option> <option value="0" {{ $machine->invoice_status == 0 ? 'selected' : '' }}>{{ __('No Invoice') }}</option>
<option value="1" {{ $machine->invoice_status == 1 ? 'selected' : '' }}>{{ __('Default Donate') }}</option> <option value="1" {{ $machine->invoice_status == 1 ? 'selected' : '' }}>{{ __('Default Donate') }}</option>
@@ -184,34 +210,36 @@
<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 items-center gap-3 mb-8"> <div class="flex items-center gap-3 mb-8">
<div class="w-10 h-10 rounded-xl bg-sky-500/10 flex items-center justify-center text-sky-500"> <div class="w-10 h-10 rounded-xl bg-indigo-500/10 flex items-center justify-center text-indigo-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z"/>
</svg>
</div> </div>
<h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight">{{ __('Member & External') }}</h3> <h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight">{{ __('Member & External') }}</h3>
</div> </div>
<div class="space-y-6"> <div class="space-y-6">
<label class="flex items-center justify-between p-4 rounded-2xl border border-slate-100 dark:border-slate-800 bg-slate-50/30 dark:bg-slate-900/30 cursor-pointer"> <label class="flex items-center justify-between p-4 rounded-2xl border border-slate-100 dark:border-slate-800 bg-slate-50/30 dark:bg-slate-900/30 cursor-pointer group">
<div> <div>
<span class="block text-sm font-black text-slate-700 dark:text-slate-200">{{ __('Welcome Gift') }}</span> <span class="block text-sm font-black text-slate-700 dark:text-slate-200 group-hover:text-indigo-500 transition-colors">{{ __('Welcome Gift') }}</span>
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{{ __('Enabled/Disabled') }}</span> <span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{{ __('Enabled/Disabled') }}</span>
</div> </div>
<div class="relative inline-flex items-center cursor-pointer"> <div class="relative inline-flex items-center cursor-pointer">
<input type="hidden" name="welcome_gift_enabled" value="0"> <input type="hidden" name="welcome_gift_enabled" value="0">
<input type="checkbox" name="welcome_gift_enabled" value="1" {{ $machine->welcome_gift_enabled ? 'checked' : '' }} class="sr-only peer"> <input type="checkbox" name="welcome_gift_enabled" value="1" {{ $machine->welcome_gift_enabled ? 'checked' : '' }} class="sr-only peer">
<div class="w-11 h-6 bg-slate-200 dark:bg-slate-700 rounded-full peer peer-focus:ring-4 peer-focus:ring-cyan-300 dark:peer-focus:ring-cyan-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500 transition-colors"></div> <div class="w-11 h-6 bg-slate-200 dark:bg-slate-700 rounded-full peer peer-focus:ring-4 peer-focus:ring-indigo-500/20 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-indigo-500 transition-colors"></div>
</div> </div>
</label> </label>
<label class="flex items-center justify-between p-4 rounded-2xl border border-slate-100 dark:border-slate-800 bg-slate-50/30 dark:bg-slate-900/30 cursor-pointer"> <label class="flex items-center justify-between p-4 rounded-2xl border border-slate-100 dark:border-slate-800 bg-slate-50/30 dark:bg-slate-900/30 cursor-pointer group">
<div> <div>
<span class="block text-sm font-black text-slate-700 dark:text-slate-200">{{ __('Member System') }}</span> <span class="block text-sm font-black text-slate-700 dark:text-slate-200 group-hover:text-indigo-500 transition-colors">{{ __('Member System') }}</span>
<span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{{ __('Enabled/Disabled') }}</span> <span class="text-[10px] font-bold text-slate-400 uppercase tracking-widest">{{ __('Enabled/Disabled') }}</span>
</div> </div>
<div class="relative inline-flex items-center cursor-pointer"> <div class="relative inline-flex items-center cursor-pointer">
<input type="hidden" name="member_system_enabled" value="0"> <input type="hidden" name="member_system_enabled" value="0">
<input type="checkbox" name="member_system_enabled" value="1" {{ $machine->member_system_enabled ? 'checked' : '' }} class="sr-only peer"> <input type="checkbox" name="member_system_enabled" value="1" {{ $machine->member_system_enabled ? 'checked' : '' }} class="sr-only peer">
<div class="w-11 h-6 bg-slate-200 dark:bg-slate-700 rounded-full peer peer-focus:ring-4 peer-focus:ring-cyan-300 dark:peer-focus:ring-cyan-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-cyan-500 transition-colors"></div> <div class="w-11 h-6 bg-slate-200 dark:bg-slate-700 rounded-full peer peer-focus:ring-4 peer-focus:ring-indigo-500/20 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-indigo-500 transition-colors"></div>
</div> </div>
</label> </label>
</div> </div>

View File

@@ -46,12 +46,19 @@
const input = document.getElementsByName('machine_image_' + index)[0]; const input = document.getElementsByName('machine_image_' + index)[0];
if (input) input.value = ''; if (input) input.value = '';
}, },
isDeleteConfirmOpen: false,
deleteFormAction: '',
confirmDelete(action) {
this.deleteFormAction = action;
this.isDeleteConfirmOpen = true;
}
}"> }">
<!-- 1. Header Area --> <!-- 1. Header Area -->
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4"> <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div> <div>
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Machine Settings') }}</h1> <h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Machine Settings') }}</h1>
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Management of operational parameters and models') }}</p> <p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{
__('Management of operational parameters and models') }}</p>
</div> </div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
@if($tab === 'machines') @if($tab === 'machines')
@@ -111,19 +118,19 @@
<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 <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"> class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Machine Info') }}</th> {{ __('Machine Info') }}</th>
<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"> class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Machine Model') }}</th> {{ __('Machine Model') }}</th>
<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"> class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Status') }}</th> {{ __('Status') }}</th>
<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"> class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Card Reader') }}</th> {{ __('Card Reader') }}</th>
<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"> class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">
{{ __('Owner') }}</th> {{ __('Owner') }}</th>
<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"> 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">
@@ -138,13 +145,13 @@
<div <div
class="w-10 h-10 rounded-xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 overflow-hidden"> class="w-10 h-10 rounded-xl bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 border border-slate-200 dark:border-slate-700 group-hover:bg-cyan-500 group-hover:text-white transition-all duration-300 overflow-hidden">
@if(isset($machine->image_urls[0])) @if(isset($machine->image_urls[0]))
<img src="{{ $machine->image_urls[0] }}" class="w-full h-full object-cover"> <img src="{{ $machine->image_urls[0] }}" class="w-full h-full object-cover">
@else @else
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"
stroke-width="2.5"> stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" <path stroke-linecap="round" stroke-linejoin="round"
d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" /> d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg> </svg>
@endif @endif
</div> </div>
<div> <div>
@@ -199,29 +206,28 @@
{{ $machine->company->name ?? __('None') }} {{ $machine->company->name ?? __('None') }}
</span> </span>
</td> </td>
<td class="px-6 py-6 text-right space-x-2"> <td class="px-6 py-6 text-right flex items-center justify-end gap-2">
<button <button @click="openPhotoModal(@js($machine->only(['id', 'name', 'image_urls'])))"
@click="openPhotoModal(@js($machine->only(['id', 'name', 'image_urls'])))" class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all inline-flex group/btn"
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-emerald-500 hover:bg-emerald-500/5 border border-transparent hover:border-emerald-500/20 transition-all inline-flex"
title="{{ __('Machine Images') }}"> title="{{ __('Machine Images') }}">
<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 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" <path stroke-linecap="round" stroke-linejoin="round"
d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" /> d="m2.25 15.75 5.159-5.159a2.25 2.25 0 0 1 3.182 0l5.159 5.159m-1.5-1.5 1.409-1.409a2.25 2.25 0 0 1 3.182 0l2.909 2.909m-18 3.75h16.5a1.5 1.5 0 0 0 1.5-1.5V6a1.5 1.5 0 0 0-1.5-1.5H3.75A1.5 1.5 0 0 0 2.25 6v12a1.5 1.5 0 0 0 1.5 1.5Zm10.5-11.25h.008v.008h-.008V8.25Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />
</svg> </svg>
</button> </button>
<a href="{{ route('admin.basic-settings.machines.edit', $machine) }}" <a href="{{ route('admin.basic-settings.machines.edit', $machine) }}"
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 border border-transparent hover:border-cyan-500/20 transition-all inline-flex" class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all inline-flex group/btn"
title="{{ __('Edit Settings') }}"> title="{{ __('Edit Settings') }}">
<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 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" <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" /> 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> </svg>
</a> </a>
<button <button
@click="openDetail(@js($machine->only(['name', 'serial_no', 'status', 'location', 'last_heartbeat_at', 'card_reader_no', 'card_reader_seconds', 'firmware_version', 'api_token', 'heating_start_time', 'heating_end_time', 'image_urls'])))" @click="openDetail(@js($machine->only(['name', 'serial_no', 'status', 'location', 'last_heartbeat_at', 'card_reader_no', 'card_reader_seconds', 'firmware_version', 'api_token', 'heating_start_time', 'heating_end_time', 'image_urls'])))"
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-indigo-500 hover:bg-indigo-500/5 border border-transparent hover:border-indigo-500/20 transition-all inline-flex" class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all inline-flex group/btn"
title="{{ __('View Details') }}"> title="{{ __('View Details') }}">
<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 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" <path stroke-linecap="round" stroke-linejoin="round"
d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" /> d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" />
</svg> </svg>
@@ -231,7 +237,7 @@
@empty @empty
<tr> <tr>
<td colspan="5" <td colspan="5"
class="px-6 py-20 text-center text-slate-500 dark:text-slate-400 font-bold tracking-widest italic uppercase"> class="px-6 py-20 text-center text-slate-500 dark:text-slate-400 font-bold tracking-widest uppercase">
{{ __('No data available') }} {{ __('No data available') }}
</td> </td>
</tr> </tr>
@@ -294,39 +300,39 @@
</div> </div>
</td> </td>
<td class="px-6 py-6 text-right space-x-2"> <td class="px-6 py-6 text-right space-x-2">
<button <div class="flex items-center justify-end gap-2">
@click="currentModel = @js($model); modelActionUrl = '{{ route('admin.basic-settings.machine-models.update', $model) }}'; showEditModelModal = true" <button @click="currentModel = @js($model->only(['name'])); modelActionUrl = '{{ route('admin.basic-settings.machine-models.update', $model) }}'; showEditModelModal = true"
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 border border-transparent hover:border-cyan-500/20 transition-all inline-flex" class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 dark:hover:text-cyan-400 hover:bg-cyan-500/5 dark:hover:bg-cyan-500/10 border border-transparent hover:border-cyan-500/20 transition-all group/btn"
title="{{ __('Edit') }}"> title="{{ __('Edit') }}">
<svg class="w-4 h-4" 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">
stroke-width="2.5"> <path stroke-linecap="round" stroke-linejoin="round"
<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" />
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
</button>
<form action="{{ route('admin.basic-settings.machine-models.destroy', $model) }}" method="POST"
class="inline-block" onsubmit="return confirm('{{ addslashes(__('Are you sure?')) }}')">
@csrf
@method('DELETE')
<button type="submit"
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-rose-500 hover:bg-rose-500/5 border border-transparent hover:border-rose-500/20 transition-all inline-flex"
title="{{ __('Delete') }}">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18" />
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
<line x1="10" x2="10" y1="11" y2="17" />
<line x1="14" x2="14" y1="11" y2="17" />
</svg> </svg>
</button> </button>
</form>
<form :id="'delete-model-form-' + {{ $model->id }}"
action="{{ route('admin.basic-settings.machine-models.destroy', $model) }}"
method="POST" class="inline">
@csrf
@method('DELETE')
<button type="button"
@click="confirmDelete('{{ route('admin.basic-settings.machine-models.destroy', $model) }}')"
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-rose-500 dark:hover:text-rose-400 hover:bg-rose-500/5 dark:hover:bg-rose-500/10 border border-transparent hover:border-rose-500/20 transition-all"
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 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-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
</button>
</form>
</div>
</td> </td>
</tr> </tr>
@empty @empty
<tr> <tr>
<td colspan="4" <td colspan="4"
class="px-6 py-20 text-center text-slate-500 dark:text-slate-400 font-bold tracking-widest italic uppercase"> class="px-6 py-20 text-center text-slate-500 dark:text-slate-400 font-bold tracking-widest uppercase">
{{ __('No data available') }} {{ __('No data available') }}
</td> </td>
</tr> </tr>
@@ -357,7 +363,8 @@
class="inline-block align-bottom bg-white dark:bg-slate-900 rounded-3xl text-left overflow-hidden shadow-2xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full animate-luxury-in border border-slate-100 dark:border-slate-800"> class="inline-block align-bottom bg-white dark:bg-slate-900 rounded-3xl text-left overflow-hidden shadow-2xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full animate-luxury-in border border-slate-100 dark:border-slate-800">
<div <div
class="px-8 pt-8 pb-6 border-b border-slate-50 dark:border-slate-800/50 flex justify-between items-center"> class="px-8 pt-8 pb-6 border-b border-slate-50 dark:border-slate-800/50 flex justify-between items-center">
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Add Machine') }}</h3> <h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Add
Machine') }}</h3>
<button @click="showCreateMachineModal = false" <button @click="showCreateMachineModal = false"
class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition-colors"> class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition-colors">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -417,26 +424,35 @@
<label <label
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{
__('Machine Images') }} ({{ __('Max 3') }})</label> __('Machine Images') }} ({{ __('Max 3') }})</label>
<label class="relative flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-slate-200 dark:border-slate-800 rounded-2xl cursor-pointer bg-slate-50/50 dark:bg-slate-900/50 hover:bg-slate-100 dark:hover:bg-slate-800/80 transition-all group"> <label
class="relative flex flex-col items-center justify-center w-full h-32 border-2 border-dashed border-slate-200 dark:border-slate-800 rounded-2xl cursor-pointer bg-slate-50/50 dark:bg-slate-900/50 hover:bg-slate-100 dark:hover:bg-slate-800/80 transition-all group">
<template x-if="selectedFileCount === 0"> <template x-if="selectedFileCount === 0">
<div class="flex flex-col items-center justify-center pt-5 pb-6"> <div class="flex flex-col items-center justify-center pt-5 pb-6">
<svg class="w-8 h-8 mb-3 text-slate-400 group-hover:text-cyan-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-8 h-8 mb-3 text-slate-400 group-hover:text-cyan-500 transition-colors"
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" /> fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
</svg> </svg>
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">{{ __('Click to upload') }}</p> <p
class="text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-widest">
{{ __('Click to upload') }}</p>
</div> </div>
</template> </template>
<template x-if="selectedFileCount > 0"> <template x-if="selectedFileCount > 0">
<div class="flex flex-col items-center justify-center pt-5 pb-6"> <div class="flex flex-col items-center justify-center pt-5 pb-6">
<div class="w-10 h-10 rounded-full bg-emerald-500/10 flex items-center justify-center text-emerald-500 mb-2"> <div
class="w-10 h-10 rounded-full bg-emerald-500/10 flex items-center justify-center text-emerald-500 mb-2">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7" /> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5"
d="M5 13l4 4L19 7" />
</svg> </svg>
</div> </div>
<p class="text-xs font-black text-emerald-500 uppercase tracking-widest" x-text="`${selectedFileCount} {{ __('files selected') }}`"></p> <p class="text-xs font-black text-emerald-500 uppercase tracking-widest"
x-text="`${selectedFileCount} {{ __('files selected') }}`"></p>
</div> </div>
</template> </template>
<input type="file" name="images[]" multiple accept="image/*" class="hidden" @change="handleFileChange"> <input type="file" name="images[]" multiple accept="image/*" class="hidden"
@change="handleFileChange">
</label> </label>
</div> </div>
</div> </div>
@@ -467,7 +483,8 @@
class="inline-block align-bottom bg-white dark:bg-slate-900 rounded-3xl text-left overflow-hidden shadow-2xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full animate-luxury-in border border-slate-100 dark:border-slate-800"> class="inline-block align-bottom bg-white dark:bg-slate-900 rounded-3xl text-left overflow-hidden shadow-2xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full animate-luxury-in border border-slate-100 dark:border-slate-800">
<div <div
class="px-8 pt-8 pb-6 border-b border-slate-50 dark:border-slate-800/50 flex justify-between items-center"> class="px-8 pt-8 pb-6 border-b border-slate-50 dark:border-slate-800/50 flex justify-between items-center">
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Add Machine Model') }}</h3> <h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Add
Machine Model') }}</h3>
<button @click="showCreateModelModal = false" <button @click="showCreateModelModal = false"
class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition-colors"> class="text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition-colors">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -560,7 +577,8 @@
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" @click="showPhotoModal = false"> x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" @click="showPhotoModal = false">
</div> </div>
<div class="fixed inset-0 z-[160] overflow-y-auto pointer-events-none p-4 md:p-8 flex items-center justify-center"> <div
class="fixed inset-0 z-[160] overflow-y-auto pointer-events-none p-4 md:p-8 flex items-center justify-center">
<div <div
class="w-full max-w-2xl bg-white dark:bg-slate-900 rounded-[2.5rem] shadow-2xl border border-slate-100 dark:border-slate-800 pointer-events-auto overflow-hidden animate-luxury-in"> class="w-full max-w-2xl bg-white dark:bg-slate-900 rounded-[2.5rem] shadow-2xl border border-slate-100 dark:border-slate-800 pointer-events-auto overflow-hidden animate-luxury-in">
<div <div
@@ -579,7 +597,8 @@
</button> </button>
</div> </div>
<form :action="'{{ route('admin.basic-settings.machines.photos.update', ':id') }}'.replace(':id', currentMachine?.id)" <form
:action="'{{ route('admin.basic-settings.machines.photos.update', ':id') }}'.replace(':id', currentMachine?.id)"
method="POST" enctype="multipart/form-data"> method="POST" enctype="multipart/form-data">
@csrf @csrf
@method('PATCH') @method('PATCH')
@@ -588,16 +607,18 @@
<div class="grid grid-cols-1 md:grid-cols-3 gap-6"> <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<template x-for="i in [0, 1, 2]" :key="i"> <template x-for="i in [0, 1, 2]" :key="i">
<div class="space-y-3"> <div class="space-y-3">
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em]" <label
class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em]"
x-text="'{{ __('Photo Slot') }} ' + (i + 1)"></label> x-text="'{{ __('Photo Slot') }} ' + (i + 1)"></label>
<div class="relative group aspect-square rounded-[2rem] overflow-hidden border-2 border-dashed border-slate-200 dark:border-slate-800 hover:border-emerald-500/50 transition-all bg-slate-50/50 dark:bg-slate-900/50 flex flex-col items-center justify-center cursor-pointer" <div class="relative group aspect-square rounded-[2rem] overflow-hidden border-2 border-dashed border-slate-200 dark:border-slate-800 hover:border-emerald-500/50 transition-all bg-slate-50/50 dark:bg-slate-900/50 flex flex-col items-center justify-center cursor-pointer"
@click="$el.querySelector('input').click()"> <template @click="$el.querySelector('input').click()"> <template
x-if="(selectedFiles[i] || (currentMachine?.image_urls && currentMachine.image_urls[i])) && !deletedPhotos[i]"> x-if="(selectedFiles[i] || (currentMachine?.image_urls && currentMachine.image_urls[i])) && !deletedPhotos[i]">
<div class="absolute inset-0 w-full h-full"> <div class="absolute inset-0 w-full h-full">
<img :src="selectedFiles[i] || currentMachine.image_urls[i]" <img :src="selectedFiles[i] || currentMachine.image_urls[i]"
class="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"> class="absolute inset-0 w-full h-full object-cover transition-transform duration-700 group-hover:scale-110">
<div class="absolute inset-0 bg-slate-900/60 backdrop-blur-[2px] opacity-0 group-hover:opacity-100 transition-all flex flex-col items-center justify-center gap-3"> <div
class="absolute inset-0 bg-slate-900/60 backdrop-blur-[2px] opacity-0 group-hover:opacity-100 transition-all flex flex-col items-center justify-center gap-3">
<button type="button" <button type="button"
class="bg-white text-emerald-600 px-5 py-2.5 rounded-xl text-xs font-black uppercase tracking-widest shadow-xl transform hover:scale-105 transition-all" class="bg-white text-emerald-600 px-5 py-2.5 rounded-xl text-xs font-black uppercase tracking-widest shadow-xl transform hover:scale-105 transition-all"
@click.stop="$el.closest('.group').querySelector('input').click()"> @click.stop="$el.closest('.group').querySelector('input').click()">
@@ -612,20 +633,27 @@
</div> </div>
</template> </template>
<template x-if="!selectedFiles[i] && !(currentMachine?.image_urls && currentMachine.image_urls[i]) || deletedPhotos[i]"> <template
x-if="!selectedFiles[i] && !(currentMachine?.image_urls && currentMachine.image_urls[i]) || deletedPhotos[i]">
<div class="flex flex-col items-center gap-3"> <div class="flex flex-col items-center gap-3">
<div class="w-12 h-12 rounded-full bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 group-hover:bg-emerald-500 group-hover:text-white transition-all"> <div
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"> class="w-12 h-12 rounded-full bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-slate-400 group-hover:bg-emerald-500 group-hover:text-white transition-all">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" /> <svg class="w-6 h-6" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 4v16m8-8H4" />
</svg> </svg>
</div> </div>
<span class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest" x-text="'Slot ' + (i + 1)"></span> <span
class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest"
x-text="'Slot ' + (i + 1)"></span>
</div> </div>
</template> </template>
<input type="file" :name="'machine_image_' + i" class="hidden" <input type="file" :name="'machine_image_' + i" class="hidden" accept="image/*"
accept="image/*" @change="handlePhotoFileChange($event, i)"> @change="handlePhotoFileChange($event, i)">
<input type="hidden" :name="'delete_photo_' + i" :value="deletedPhotos[i] ? '1' : '0'"> <input type="hidden" :name="'delete_photo_' + i"
:value="deletedPhotos[i] ? '1' : '0'">
</div> </div>
</div> </div>
</template> </template>
@@ -639,7 +667,8 @@
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg> </svg>
</div> </div>
<p class="text-xs font-bold text-amber-700 dark:text-amber-300 leading-relaxed text-left flex-1"> <p
class="text-xs font-bold text-amber-700 dark:text-amber-300 leading-relaxed text-left flex-1">
{{ __('Optimized for display. Supported formats: JPG, PNG, WebP.') {{ __('Optimized for display. Supported formats: JPG, PNG, WebP.')
}} }}
</p> </p>
@@ -660,26 +689,16 @@
<!-- 4.1 Image Lightbox Modal --> <!-- 4.1 Image Lightbox Modal -->
<template x-teleport="body"> <template x-teleport="body">
<div x-show="showImageLightbox" <div x-show="showImageLightbox" class="fixed inset-0 z-[200] flex items-center justify-center p-4 sm:p-10" x-cloak>
class="fixed inset-0 z-[200] flex items-center justify-center p-4 sm:p-10" <div x-show="showImageLightbox" x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0"
x-cloak> x-transition:enter-end="opacity-100" x-transition:leave="ease-in duration-200"
<div x-show="showImageLightbox" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" @click="showImageLightbox = false"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="showImageLightbox = false"
class="absolute inset-0 bg-slate-900/90 backdrop-blur-md transition-opacity"> class="absolute inset-0 bg-slate-900/90 backdrop-blur-md transition-opacity">
</div> </div>
<div x-show="showImageLightbox" <div x-show="showImageLightbox" x-transition:enter="ease-out duration-300"
x-transition:enter="ease-out duration-300" x-transition:enter-start="opacity-0 scale-95" x-transition:enter-end="opacity-100 scale-100"
x-transition:enter-start="opacity-0 scale-95" x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100 scale-100"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95" x-transition:leave-end="opacity-0 scale-95"
class="relative max-w-5xl w-full max-h-full flex items-center justify-center z-[210]"> class="relative max-w-5xl w-full max-h-full flex items-center justify-center z-[210]">
@@ -735,14 +754,17 @@
__('Machine Images') }}</h3> __('Machine Images') }}</h3>
<div class="grid grid-cols-2 gap-3"> <div class="grid grid-cols-2 gap-3">
<template x-for="(url, index) in currentMachine.image_urls" :key="index"> <template x-for="(url, index) in currentMachine.image_urls" :key="index">
<div <div @click="lightboxImageUrl = url; showImageLightbox = true"
@click="lightboxImageUrl = url; showImageLightbox = true"
class="relative group aspect-square rounded-2xl overflow-hidden border border-slate-100 dark:border-slate-800 shadow-sm bg-slate-50 dark:bg-slate-800/50 cursor-pointer"> class="relative group aspect-square rounded-2xl overflow-hidden border border-slate-100 dark:border-slate-800 shadow-sm bg-slate-50 dark:bg-slate-800/50 cursor-pointer">
<img :src="url" <img :src="url"
class="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"> class="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-110">
<div class="absolute inset-0 bg-slate-900/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center"> <div
<svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24"> class="absolute inset-0 bg-slate-900/40 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7" /> <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v3m0 0v3m0-3h3m-3 0H7" />
</svg> </svg>
</div> </div>
</div> </div>
@@ -751,7 +773,8 @@
</section> </section>
</template> </template>
<section class="space-y-6"> <section class="space-y-6">
<h3 class="text-[11px] font-black text-cyan-500 uppercase tracking-[0.3em]">{{ __('Hardware & Network') }}</h3> <h3 class="text-[11px] font-black text-cyan-500 uppercase tracking-[0.3em]">{{ __('Hardware
& Network') }}</h3>
<div class="grid grid-cols-1 gap-4"> <div class="grid grid-cols-1 gap-4">
<div <div
class="bg-slate-50 dark:bg-slate-800/40 p-5 rounded-2xl border border-slate-100 dark:border-slate-800/80"> class="bg-slate-50 dark:bg-slate-800/40 p-5 rounded-2xl border border-slate-100 dark:border-slate-800/80">
@@ -825,5 +848,9 @@
</div> </div>
</div> </div>
</template> </template>
<!-- Global Delete Confirm Modal -->
<x-delete-confirm-modal />
</div> </div>
@endsection @endsection

View File

@@ -1,7 +1,7 @@
@extends('layouts.admin') @extends('layouts.admin')
@section('content') @section('content')
<div class="space-y-10 pb-20"> <div class="space-y-6 pb-20">
<!-- Header --> <!-- Header -->
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6"> <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
@@ -9,7 +9,7 @@
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18"/></svg> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18"/></svg>
</a> </a>
<div> <div>
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Create Payment Config') }}</h1> <h1 class="text-2xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Create Payment Config') }}</h1>
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Define new third-party payment parameters') }}</p> <p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Define new third-party payment parameters') }}</p>
</div> </div>
</div> </div>
@@ -21,12 +21,28 @@
</div> </div>
</div> </div>
<form id="create-form" action="{{ route('admin.basic-settings.payment-configs.store') }}" method="POST" class="space-y-8"> <form id="create-form" action="{{ route('admin.basic-settings.payment-configs.store') }}" method="POST" class="space-y-6">
@csrf @csrf
@if ($errors->any())
<div class="p-5 bg-rose-500/10 border border-rose-500/20 text-rose-600 dark:text-rose-400 rounded-2xl flex items-start gap-4 animate-luxury-in font-bold">
<div class="size-8 bg-rose-500/20 rounded-xl flex items-center justify-center flex-shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
</div>
<div>
<p class="text-sm tracking-wide">{{ __('Please check the following errors:') }}</p>
<ul class="mt-1 text-xs list-disc list-inside opacity-80">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
</div>
@endif
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- Left Column: Primary Info --> <!-- Left Column: Primary Info -->
<div class="lg:col-span-12 space-y-8"> <div class="lg:col-span-12 space-y-6">
<div class="luxury-card rounded-3xl p-8 animate-luxury-in"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in">
<div class="grid grid-cols-1 md:grid-cols-2 gap-8"> <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div> <div>
@@ -47,18 +63,18 @@
</div> </div>
<!-- provider groups --> <!-- provider groups -->
<div class="lg:col-span-6 space-y-8"> <div class="lg:col-span-6 space-y-6">
<!-- ECPay --> <!-- ECPay -->
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 50ms"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in group/card" style="animation-delay: 50ms">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-5 mb-8">
<div class="w-12 h-12 rounded-2xl bg-emerald-50 dark:bg-emerald-500/10 flex items-center justify-center text-emerald-500 border border-emerald-100 dark:border-emerald-500/20 shadow-sm shadow-emerald-500/10"> <div class="w-10 h-10 rounded-xl bg-gradient-to-br from-emerald-500/10 to-teal-500/10 flex items-center justify-center text-emerald-500 border border-emerald-500/20 shadow-lg shadow-emerald-500/5 group-hover/card:scale-110 transition-transform duration-500">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg>
</div> </div>
<div> <div>
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight">{{ __('ECPay Invoice') }}</h3> <h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight font-display uppercase">{{ __('ECPay Invoice') }}</h3>
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">{{ __('ECPay Invoice Settings Description') }}</p>
</div> </div>
</div> </div>
<div class="space-y-6"> <div class="space-y-6">
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Store ID') }}</label> <label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Store ID') }}</label>
@@ -76,16 +92,16 @@
</div> </div>
<!-- E.SUN --> <!-- E.SUN -->
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 100ms"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in group/card" style="animation-delay: 100ms">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-5 mb-8">
<div class="w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-500/10 flex items-center justify-center text-indigo-500 border border-indigo-100 dark:border-indigo-500/20 shadow-sm shadow-indigo-500/10"> <div class="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-500/10 to-blue-500/10 flex items-center justify-center text-indigo-500 border border-indigo-500/20 shadow-lg shadow-indigo-500/5 group-hover/card:scale-110 transition-transform duration-500">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M16.875 12h1.125a3.375 3.375 0 0 1 3.375 3.375v1.125m-11.25 4.5h1.125a3.375 3.375 0 0 0 3.375-3.375V16.5a3.375 3.375 0 0 1 3.375-3.375h1.125m-11.25 4.5h1.125a3.375 3.375 0 0 1 3.375 3.375v1.125m-3.375-1.125h.008v.008h-.008v-.008Zm3.375 3.375h.008v.008h-.008v-.008Zm0-3.375h.008v.008h-.008v-.008Zm-3.375-3.375h.008v.008h-.008v-.008Zm0 3.375h.008v.008h-.008v-.008Z"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M16.875 12h1.125a3.375 3.375 0 0 1 3.375 3.375v1.125m-11.25 4.5h1.125a3.375 3.375 0 0 0 3.375-3.375V16.5a3.375 3.375 0 0 1 3.375-3.375h1.125m-11.25 4.5h1.125a3.375 3.375 0 0 1 3.375 3.375v1.125m-3.375-1.125h.008v.008h-.008v-.008Zm3.375 3.375h.008v.008h-.008v-.008Zm0-3.375h.008v.008h-.008v-.008Zm-3.375-3.375h.008v.008h-.008v-.008Zm0 3.375h.008v.008h-.008v-.008Z"/></svg>
</div> </div>
<div> <div>
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight">{{ __('E.SUN QR Scan') }}</h3> <h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight font-display uppercase">{{ __('E.SUN QR Scan') }}</h3>
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">{{ __('E.SUN QR Scan Settings Description') }}</p>
</div> </div>
</div> </div>
<div class="space-y-6"> <div class="space-y-6">
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('StoreID') }}</label> <label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('StoreID') }}</label>
@@ -103,16 +119,16 @@
</div> </div>
<!-- LINE Pay Direct --> <!-- LINE Pay Direct -->
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 150ms"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in group/card" style="animation-delay: 150ms">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-5 mb-8">
<div class="w-12 h-12 rounded-2xl bg-emerald-50 dark:bg-emerald-500/10 flex items-center justify-center text-emerald-500 border border-emerald-100 dark:border-emerald-500/20 shadow-sm shadow-emerald-500/10"> <div class="w-10 h-10 rounded-xl bg-gradient-to-br from-lime-500/10 to-emerald-500/10 flex items-center justify-center text-emerald-500 border border-emerald-500/20 shadow-lg shadow-emerald-500/5 group-hover/card:scale-110 transition-transform duration-500">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m24.408 0a8.959 8.959 0 0 1 .284-2.253m0 2.253C20.46 17.1 16.485 19.5 12 19.5S3.538 17.1 2.284 14.253"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m24.408 0a8.959 8.959 0 0 1 .284-2.253m0 2.253C20.46 17.1 16.485 19.5 12 19.5S3.538 17.1 2.284 14.253"/></svg>
</div> </div>
<div> <div>
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight">{{ __('LINE Pay Direct') }}</h3> <h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight font-display uppercase">{{ __('LINE Pay Direct') }}</h3>
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">{{ __('LINE Pay Direct Settings Description') }}</p>
</div> </div>
</div> </div>
<div class="space-y-6"> <div class="space-y-6">
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('ChannelId') }}</label> <label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('ChannelId') }}</label>
@@ -126,18 +142,18 @@
</div> </div>
</div> </div>
<div class="lg:col-span-6 space-y-8"> <div class="lg:col-span-6 space-y-6">
<!-- Tappay --> <!-- Tappay -->
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 200ms"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in group/card" style="animation-delay: 200ms">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-5 mb-8">
<div class="w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-500/10 flex items-center justify-center text-indigo-500 border border-indigo-100 dark:border-indigo-500/20 shadow-sm shadow-indigo-500/10"> <div class="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-500/10 to-violet-500/10 flex items-center justify-center text-indigo-500 border border-indigo-500/20 shadow-lg shadow-indigo-500/5 group-hover/card:scale-110 transition-transform duration-500">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z"/></svg>
</div> </div>
<div> <div>
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight">{{ __('TapPay Integration') }}</h3> <h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight font-display uppercase">{{ __('TapPay Integration') }}</h3>
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">{{ __('TapPay Integration Settings Description') }}</p>
</div> </div>
</div> </div>
<div class="space-y-6"> <div class="space-y-6">
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div> <div>

View File

@@ -1,7 +1,7 @@
@extends('layouts.admin') @extends('layouts.admin')
@section('content') @section('content')
<div class="space-y-10 pb-20"> <div class="space-y-6 pb-20">
<!-- Header --> <!-- Header -->
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6"> <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
@@ -9,7 +9,7 @@
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18"/></svg> <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18"/></svg>
</a> </a>
<div> <div>
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Edit Payment Config') }}</h1> <h1 class="text-2xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Edit Payment Config') }}</h1>
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ $paymentConfig->name }}</p> <p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ $paymentConfig->name }}</p>
</div> </div>
</div> </div>
@@ -21,13 +21,29 @@
</div> </div>
</div> </div>
<form id="edit-form" action="{{ route('admin.basic-settings.payment-configs.update', $paymentConfig) }}" method="POST" class="space-y-8"> <form id="edit-form" action="{{ route('admin.basic-settings.payment-configs.update', $paymentConfig) }}" method="POST" class="space-y-6">
@csrf @csrf
@method('PUT') @method('PUT')
@if ($errors->any())
<div class="p-5 bg-rose-500/10 border border-rose-500/20 text-rose-600 dark:text-rose-400 rounded-2xl flex items-start gap-4 animate-luxury-in font-bold">
<div class="size-8 bg-rose-500/20 rounded-xl flex items-center justify-center flex-shrink-0">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
</div>
<div>
<p class="text-sm tracking-wide">{{ __('Please check the following errors:') }}</p>
<ul class="mt-1 text-xs list-disc list-inside opacity-80">
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
</div>
@endif
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
<!-- Left Column: Primary Info --> <!-- Left Column: Primary Info -->
<div class="lg:col-span-12 space-y-8"> <div class="lg:col-span-12 space-y-6">
<div class="luxury-card rounded-3xl p-8 animate-luxury-in"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in">
<div class="grid grid-cols-1 md:grid-cols-2 gap-8"> <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<div> <div>
@@ -51,18 +67,18 @@
@php @php
$settings = $paymentConfig->settings ?? []; $settings = $paymentConfig->settings ?? [];
@endphp @endphp
<div class="lg:col-span-6 space-y-8"> <div class="lg:col-span-6 space-y-6">
<!-- ECPay --> <!-- ECPay -->
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 50ms"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in group/card" style="animation-delay: 50ms">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-5 mb-8">
<div class="w-12 h-12 rounded-2xl bg-emerald-50 dark:bg-emerald-500/10 flex items-center justify-center text-emerald-500 border border-emerald-100 dark:border-emerald-500/20 shadow-sm shadow-emerald-500/10"> <div class="w-10 h-10 rounded-xl bg-gradient-to-br from-emerald-500/10 to-teal-500/10 flex items-center justify-center text-emerald-500 border border-emerald-500/20 shadow-lg shadow-emerald-500/5 group-hover/card:scale-110 transition-transform duration-500">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg>
</div> </div>
<div> <div>
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight">{{ __('ECPay Invoice') }}</h3> <h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight font-display uppercase">{{ __('ECPay Invoice') }}</h3>
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">{{ __('ECPay Invoice Settings Description') }}</p>
</div> </div>
</div> </div>
<div class="space-y-6"> <div class="space-y-6">
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Store ID') }}</label> <label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('Store ID') }}</label>
@@ -80,16 +96,16 @@
</div> </div>
<!-- E.SUN --> <!-- E.SUN -->
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 100ms"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in group/card" style="animation-delay: 100ms">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-5 mb-8">
<div class="w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-500/10 flex items-center justify-center text-indigo-500 border border-indigo-100 dark:border-indigo-500/20 shadow-sm shadow-indigo-500/10"> <div class="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-500/10 to-blue-500/10 flex items-center justify-center text-indigo-500 border border-indigo-500/20 shadow-lg shadow-indigo-500/5 group-hover/card:scale-110 transition-transform duration-500">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M16.875 12h1.125a3.375 3.375 0 0 1 3.375 3.375v1.125m-11.25 4.5h1.125a3.375 3.375 0 0 0 3.375-3.375V16.5a3.375 3.375 0 0 1 3.375-3.375h1.125m-11.25 4.5h1.125a3.375 3.375 0 0 1 3.375 3.375v1.125m-3.375-1.125h.008v.008h-.008v-.008Zm3.375 3.375h.008v.008h-.008v-.008Zm0-3.375h.008v.008h-.008v-.008Zm-3.375-3.375h.008v.008h-.008v-.008Zm0 3.375h.008v.008h-.008v-.008Z"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M16.875 12h1.125a3.375 3.375 0 0 1 3.375 3.375v1.125m-11.25 4.5h1.125a3.375 3.375 0 0 0 3.375-3.375V16.5a3.375 3.375 0 0 1 3.375-3.375h1.125m-11.25 4.5h1.125a3.375 3.375 0 0 1 3.375 3.375v1.125m-3.375-1.125h.008v.008h-.008v-.008Zm3.375 3.375h.008v.008h-.008v-.008Zm0-3.375h.008v.008h-.008v-.008Zm-3.375-3.375h.008v.008h-.008v-.008Zm0 3.375h.008v.008h-.008v-.008Z"/></svg>
</div> </div>
<div> <div>
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight">{{ __('E.SUN QR Scan') }}</h3> <h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight font-display uppercase">{{ __('E.SUN QR Scan') }}</h3>
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">{{ __('E.SUN QR Scan Settings Description') }}</p>
</div> </div>
</div> </div>
<div class="space-y-6"> <div class="space-y-6">
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('StoreID') }}</label> <label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('StoreID') }}</label>
@@ -107,16 +123,16 @@
</div> </div>
<!-- LINE Pay Direct --> <!-- LINE Pay Direct -->
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 150ms"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in group/card" style="animation-delay: 150ms">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-5 mb-8">
<div class="w-12 h-12 rounded-2xl bg-emerald-50 dark:bg-emerald-500/10 flex items-center justify-center text-emerald-500 border border-emerald-100 dark:border-emerald-500/20 shadow-sm shadow-emerald-500/10"> <div class="w-10 h-10 rounded-xl bg-gradient-to-br from-lime-500/10 to-emerald-500/10 flex items-center justify-center text-emerald-500 border border-emerald-500/20 shadow-lg shadow-emerald-500/5 group-hover/card:scale-110 transition-transform duration-500">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m24.408 0a8.959 8.959 0 0 1 .284-2.253m0 2.253C20.46 17.1 16.485 19.5 12 19.5S3.538 17.1 2.284 14.253"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m24.408 0a8.959 8.959 0 0 1 .284-2.253m0 2.253C20.46 17.1 16.485 19.5 12 19.5S3.538 17.1 2.284 14.253"/></svg>
</div> </div>
<div> <div>
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight">{{ __('LINE Pay Direct') }}</h3> <h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight font-display uppercase">{{ __('LINE Pay Direct') }}</h3>
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">{{ __('LINE Pay Direct Settings Description') }}</p>
</div> </div>
</div> </div>
<div class="space-y-6"> <div class="space-y-6">
<div> <div>
<label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('ChannelId') }}</label> <label class="block text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] mb-2">{{ __('ChannelId') }}</label>
@@ -130,18 +146,18 @@
</div> </div>
</div> </div>
<div class="lg:col-span-6 space-y-8"> <div class="lg:col-span-6 space-y-6">
<!-- Tappay --> <!-- Tappay -->
<div class="luxury-card rounded-3xl p-8 animate-luxury-in" style="animation-delay: 200ms"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in group/card" style="animation-delay: 200ms">
<div class="flex items-center gap-4 mb-8"> <div class="flex items-center gap-5 mb-8">
<div class="w-12 h-12 rounded-2xl bg-indigo-50 dark:bg-indigo-500/10 flex items-center justify-center text-indigo-500 border border-indigo-100 dark:border-indigo-500/20 shadow-sm shadow-indigo-500/10"> <div class="w-10 h-10 rounded-xl bg-gradient-to-br from-indigo-500/10 to-violet-500/10 flex items-center justify-center text-indigo-500 border border-indigo-500/20 shadow-lg shadow-indigo-500/5 group-hover/card:scale-110 transition-transform duration-500">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z"/></svg> <svg class="w-5 h-5 stroke-[2.5]" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z"/></svg>
</div> </div>
<div> <div>
<h3 class="text-xl font-black text-slate-800 dark:text-white tracking-tight">{{ __('TapPay Integration') }}</h3> <h3 class="text-lg font-black text-slate-800 dark:text-white tracking-tight font-display uppercase">{{ __('TapPay Integration') }}</h3>
<p class="text-[10px] font-bold text-slate-400 dark:text-slate-500 uppercase tracking-widest mt-0.5">{{ __('TapPay Integration Settings Description') }}</p>
</div> </div>
</div> </div>
<div class="space-y-6"> <div class="space-y-6">
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div> <div>

View File

@@ -1,12 +1,19 @@
@extends('layouts.admin') @extends('layouts.admin')
@section('content') @section('content')
<div class="space-y-10 pb-20"> <div class="space-y-6 pb-20" x-data="{
isDeleteConfirmOpen: false,
deleteFormAction: '',
confirmDelete(action) {
this.deleteFormAction = action;
this.isDeleteConfirmOpen = true;
}
}">
<!-- Header --> <!-- Header -->
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6"> <div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
<div> <div>
<h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Payment Configuration') }}</h1> <h1 class="text-3xl font-black text-slate-800 dark:text-white tracking-tight font-display">{{ __('Payment Configuration') }}</h1>
<p class="text-xs font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Merchant payment gateway settings management') }}</p> <p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">{{ __('Merchant payment gateway settings management') }}</p>
</div> </div>
<div> <div>
<a href="{{ route('admin.basic-settings.payment-configs.create') }}" class="btn-luxury-primary flex items-center gap-2"> <a href="{{ route('admin.basic-settings.payment-configs.create') }}" class="btn-luxury-primary flex items-center gap-2">
@@ -18,18 +25,35 @@
<!-- Main Card --> <!-- Main Card -->
<div class="luxury-card rounded-3xl p-8 animate-luxury-in"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in">
<!-- Toolbar & Filters -->
<div class="flex items-center justify-between mb-8">
<form method="GET" action="{{ route('admin.basic-settings.payment-configs.index') }}" class="relative group">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10">
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round">
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</span>
<input type="text" name="search" value="{{ request('search') }}"
placeholder="{{ __('Search configurations...') }}"
class="luxury-input py-2.5 pl-12 pr-6 block w-64">
</form>
</div>
<div class="overflow-x-auto"> <div class="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/10"> <tr class="bg-slate-50/50 dark:bg-slate-900/10">
<th class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Config Name') }}</th> <th class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Configuration Name') }}</th>
<th class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Belongs To') }}</th> <th class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Belongs To') }}</th>
<th class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Last Updated') }}</th> <th class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800">{{ __('Last Updated') }}</th>
<th class="px-6 py-4 text-[11px] font-black text-slate-400 uppercase tracking-[0.2em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Action') }}</th> <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> </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">
@foreach($paymentConfigs as $config) @forelse($paymentConfigs as $config)
<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-6 font-extrabold text-slate-800 dark:text-slate-100"> <td class="px-6 py-6 font-extrabold text-slate-800 dark:text-slate-100">
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
@@ -42,7 +66,7 @@
</div> </div>
</div> </div>
</td> </td>
<td class="px-6 py-6 border-b border-transparent"> <td class="px-6 py-6">
<span class="px-2.5 py-1 rounded-lg text-xs font-bold border border-sky-100 dark:border-sky-900/30 bg-sky-50 dark:bg-sky-900/20 text-sky-600 dark:text-sky-400 tracking-widest"> <span class="px-2.5 py-1 rounded-lg text-xs font-bold border border-sky-100 dark:border-sky-900/30 bg-sky-50 dark:bg-sky-900/20 text-sky-600 dark:text-sky-400 tracking-widest">
{{ $config->company->name ?? __('None') }} {{ $config->company->name ?? __('None') }}
</span> </span>
@@ -57,7 +81,7 @@
class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 border border-transparent hover:border-cyan-500/20 transition-all inline-flex" title="{{ __('Edit') }}"> class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-cyan-500 hover:bg-cyan-500/5 border border-transparent hover:border-cyan-500/20 transition-all inline-flex" title="{{ __('Edit') }}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg>
</a> </a>
<form action="{{ route('admin.basic-settings.payment-configs.destroy', $config) }}" method="POST" class="inline-block" onsubmit="return confirm('{{ __('Are you sure you want to delete this configuration?') }}')"> <form action="{{ route('admin.basic-settings.payment-configs.destroy', $config) }}" method="POST" class="inline-block" @submit.prevent="confirmDelete($el.getAttribute('action'))">
@csrf @csrf
@method('DELETE') @method('DELETE')
<button type="submit" <button type="submit"
@@ -68,7 +92,16 @@
</form> </form>
</td> </td>
</tr> </tr>
@endforeach @empty
<tr>
<td colspan="4" class="px-6 py-20 text-center">
<div class="inline-flex items-center justify-center w-20 h-20 rounded-3xl bg-slate-50 dark:bg-slate-800/50 mb-6 border border-slate-100 dark:border-slate-800 shadow-sm">
<svg class="w-10 h-10 text-slate-300 dark:text-slate-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="1.5"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75-6.18c0-.424.344-.766.766-.766h.996c.422 0 .766.342.766.766v.996c0 .422-.344.766-.766.766h-.996a.766.766 0 0 1-.766-.766v-.996ZM3.562 18.125c0 .422.344.766.766.766h.996c.422 0 .766-.344.766-.766v-.996c0-.422-.344-.766-.766-.766h-.996a.766.766 0 0 0-.766.766v.996ZM3.562 13.125c0 .422.344.766.766.766h.996c.422 0 .766-.344.766-.766v-.996c0-.422-.344-.766-.766-.766h-.996a.766.766 0 0 0-.766.766v.996Z"/></svg>
</div>
<p class="text-base font-black text-slate-400 dark:text-slate-500 uppercase tracking-widest">{{ __('No configurations found') }}</p>
</td>
</tr>
@endforelse
</tbody> </tbody>
</table> </table>
</div> </div>
@@ -77,5 +110,8 @@
{{ $paymentConfigs->links('vendor.pagination.luxury') }} {{ $paymentConfigs->links('vendor.pagination.luxury') }}
</div> </div>
</div> </div>
<!-- Global Delete Confirm Modal -->
<x-delete-confirm-modal :message="__('Are you sure you want to delete this configuration? This action cannot be undone.')" />
</div> </div>
@endsection @endsection

View File

@@ -25,7 +25,9 @@
this.editing = true; this.editing = true;
this.currentCompany = { ...company }; this.currentCompany = { ...company };
this.showModal = true; this.showModal = true;
} },
isDeleteConfirmOpen: false,
deleteFormAction: ''
}"> }">
<!-- Header --> <!-- Header -->
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6"> <div class="flex flex-col md:flex-row md:items-center justify-between gap-6">
@@ -180,20 +182,15 @@
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" /> 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> </svg>
</button> </button>
<form action="{{ route('admin.permission.companies.destroy', $company->id) }}" <button type="button"
method="POST" @click="deleteFormAction = '{{ route('admin.permission.companies.destroy', $company->id) }}'; isDeleteConfirmOpen = true"
onsubmit="return confirm('{{ addslashes(__('Are you sure to delete this customer?')) }}')" 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="inline"> <svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"
@csrf @method('DELETE') stroke-width="2.5">
<button type="submit" <path stroke-linecap="round" stroke-linejoin="round"
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"> d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" </svg>
stroke-width="2.5"> </button>
<path stroke-linecap="round" stroke-linejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
</button>
</form>
</div> </div>
</td> </td>
</tr> </tr>
@@ -331,7 +328,7 @@
<label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Initial Role') }}</label> <label class="text-xs font-black text-slate-500 uppercase tracking-widest pl-1">{{ __('Initial Role') }}</label>
<select name="admin_role" class="luxury-select w-full"> <select name="admin_role" class="luxury-select w-full">
@foreach($template_roles as $role) @foreach($template_roles as $role)
<option value="{{ $role->name }}" {{ $role->name == '通用客戶角色範本' ? 'selected' : '' }}> <option value="{{ $role->name }}" {{ $role->name == '客戶管理員角色模板' ? 'selected' : '' }}>
{{ $role->name }} {{ $role->name }}
</option> </option>
@endforeach @endforeach
@@ -391,6 +388,8 @@
</div> </div>
</div> </div>
</div> </div>
<x-delete-confirm-modal :message="__('Are you sure to delete this customer?')" />
</div> </div>
@endsection @endsection

View File

@@ -20,6 +20,12 @@
role: '{{ old('role', '') }}', role: '{{ old('role', '') }}',
status: {{ old('status', 1) }} status: {{ old('status', 1) }}
}, },
isDeleteConfirmOpen: false,
deleteFormAction: '',
confirmDelete(action) {
this.deleteFormAction = action;
this.isDeleteConfirmOpen = true;
},
get filteredRoles() { get filteredRoles() {
if (this.currentUser.company_id === '' || this.currentUser.company_id === null) { if (this.currentUser.company_id === '' || this.currentUser.company_id === null) {
// 系統層級:顯示 is_system = 1 的角色 // 系統層級:顯示 is_system = 1 的角色
@@ -28,9 +34,9 @@
// 客戶層級:只顯示該公司的角色 // 客戶層級:只顯示該公司的角色
let roles = this.allRoles.filter(r => r.company_id == this.currentUser.company_id); let roles = this.allRoles.filter(r => r.company_id == this.currentUser.company_id);
// 如果是系統管理員,額外允許選擇「客戶層級範本」 // 如果是系統管理員,額外允許選擇「系統層級的角色範本」(排除 super-admin 以免誤派)
@if(auth()->user()->isSystemAdmin()) @if(auth()->user()->isSystemAdmin())
let templates = this.allRoles.filter(r => !r.is_system && (r.company_id === null || r.company_id === '')); let templates = this.allRoles.filter(r => (r.company_id === null || r.company_id === '') && r.name !== 'super-admin');
roles = [...roles, ...templates]; roles = [...roles, ...templates];
@endif @endif
@@ -59,7 +65,7 @@
} }
}"> }">
<!-- Header --> <!-- Header -->
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-8"> <div class="flex flex-col md:flex-row md:items-center justify-between gap-6">
<div> <div>
<h1 class="text-3xl font-black text-slate-800 dark:text-white font-display tracking-tight">{{ $title }}</h1> <h1 class="text-3xl font-black text-slate-800 dark:text-white font-display tracking-tight">{{ $title }}</h1>
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest"> <p class="text-sm font-bold text-slate-500 dark:text-slate-400 mt-1 uppercase tracking-widest">
@@ -176,22 +182,20 @@
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" /> 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> </svg>
</button> </button>
<form action="{{ route($baseRoute . '.destroy', $user->id) }}" <form action="{{ route($baseRoute . '.destroy', $user->id) }}"
method="POST" method="POST"
onsubmit="return confirm('{{ __('Are you sure you want to delete this account?') }}')" class="inline-block">
class="inline-block"> @csrf
@csrf @method('DELETE')
@method('DELETE') <button type="button"
<button type="submit" @click="confirmDelete('{{ route($baseRoute . '.destroy', $user->id) }}')"
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-xl bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-rose-500 dark:hover:text-rose-400 hover:bg-rose-500/5 dark:hover:bg-rose-500/10 border border-transparent hover:border-rose-500/20 transition-all group/btn"
title="{{ __('Delete') }}"> title="{{ __('Delete Account') }}">
<svg class="size-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" <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" stroke-width="2.5" 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-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
<path stroke-linecap="round" stroke-linejoin="round" </svg>
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" /> </button>
</svg> </form>
</button>
</form>
@else @else
<span class="text-[10px] font-black text-slate-300 dark:text-slate-600 uppercase tracking-[0.15em] px-2">{{ __('Protected') }}</span> <span class="text-[10px] font-black text-slate-300 dark:text-slate-600 uppercase tracking-[0.15em] px-2">{{ __('Protected') }}</span>
@endif @endif
@@ -345,6 +349,8 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Global Delete Confirm Modal -->
<x-delete-confirm-modal :message="__('Are you sure you want to delete this account? This action cannot be undone.')" />
@endsection @endsection

View File

@@ -4,27 +4,9 @@
@php @php
@endphp @endphp
{{-- Toast 通知 --}}
@if(session('success'))
<div x-data="{ show: false }"
x-show="show"
x-cloak
x-init="setTimeout(() => { show = true; setTimeout(() => show = false, 3000) }, 50)"
x-transition:enter="transition cubic-bezier(0.34, 1.56, 0.64, 1) duration-300"
x-transition:enter-start="opacity-0 -translate-y-40"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-400"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 -translate-y-40"
class="fixed top-4 left-0 right-0 mx-auto w-max z-[100] bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
{{ session('success') }}
</div>
@endif
<div class="px-6 py-8">
<div class="px-6 py-8" x-data="{ isDeleteConfirmOpen: false, deleteFormAction: '' }">
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">儲值回饋設定</h3> <h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">儲值回饋設定</h3>
<button onclick="document.getElementById('createModal').classList.remove('hidden')" class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700"> <button onclick="document.getElementById('createModal').classList.remove('hidden')" class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700">
@@ -59,11 +41,7 @@
@endif @endif
</td> </td>
<td class="px-6 py-4"> <td class="px-6 py-4">
<form action="{{ route('admin.deposit-bonus-rules.destroy', $rule) }}" method="POST" class="inline" onsubmit="return confirm('確定要刪除嗎?')"> <button type="button" @click="deleteFormAction = '{{ route('admin.deposit-bonus-rules.destroy', $rule) }}'; isDeleteConfirmOpen = true" class="text-red-600 hover:text-red-800">刪除</button>
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-800">刪除</button>
</form>
</td> </td>
</tr> </tr>
@empty @empty
@@ -119,5 +97,6 @@
</form> </form>
</div> </div>
</div> </div>
<x-delete-confirm-modal :message="__('Are you sure you want to delete this rule?')" />
</div> </div>
@endsection @endsection

View File

@@ -19,27 +19,9 @@
]; ];
@endphp @endphp
{{-- Toast 通知 --}}
@if(session('success'))
<div x-data="{ show: false }"
x-show="show"
x-cloak
x-init="setTimeout(() => { show = true; setTimeout(() => show = false, 3000) }, 50)"
x-transition:enter="transition cubic-bezier(0.34, 1.56, 0.64, 1) duration-300"
x-transition:enter-start="opacity-0 -translate-y-40"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-400"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 -translate-y-40"
class="fixed top-4 left-0 right-0 mx-auto w-max z-[100] bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
{{ session('success') }}
</div>
@endif
<div class="px-6 py-8">
<div class="px-6 py-8" x-data="{ isDeleteConfirmOpen: false, deleteFormAction: '' }">
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">禮品設定</h3> <h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">禮品設定</h3>
<button onclick="document.getElementById('createModal').classList.remove('hidden')" class="btn-luxury-primary"> <button onclick="document.getElementById('createModal').classList.remove('hidden')" class="btn-luxury-primary">
@@ -76,11 +58,7 @@
@endif @endif
</td> </td>
<td class="px-6 py-4"> <td class="px-6 py-4">
<form action="{{ route('admin.gift-definitions.destroy', $gift) }}" method="POST" class="inline" onsubmit="return confirm('確定要刪除嗎?')"> <button type="button" @click="deleteFormAction = '{{ route('admin.gift-definitions.destroy', $gift) }}'; isDeleteConfirmOpen = true" class="text-red-600 hover:text-red-800">刪除</button>
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-800">刪除</button>
</form>
</td> </td>
</tr> </tr>
@empty @empty
@@ -154,5 +132,6 @@
</form> </form>
</div> </div>
</div> </div>
<x-delete-confirm-modal :message="__('Are you sure you want to delete this gift?')" />
</div> </div>
@endsection @endsection

View File

@@ -4,27 +4,9 @@
@php @php
@endphp @endphp
{{-- Toast 通知 --}}
@if(session('success'))
<div x-data="{ show: false }"
x-show="show"
x-cloak
x-init="setTimeout(() => { show = true; setTimeout(() => show = false, 3000) }, 50)"
x-transition:enter="transition cubic-bezier(0.34, 1.56, 0.64, 1) duration-300"
x-transition:enter-start="opacity-0 -translate-y-40"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-400"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 -translate-y-40"
class="fixed top-4 left-0 right-0 mx-auto w-max z-[100] bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
{{ session('success') }}
</div>
@endif
<div class="px-6 py-8">
<div class="px-6 py-8" x-data="{ isDeleteConfirmOpen: false, deleteFormAction: '' }">
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">會員等級設定</h3> <h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">會員等級設定</h3>
<button onclick="document.getElementById('createModal').classList.remove('hidden')" class="btn-luxury-primary"> <button onclick="document.getElementById('createModal').classList.remove('hidden')" class="btn-luxury-primary">
@@ -57,11 +39,7 @@
@endif @endif
</td> </td>
<td class="px-6 py-4"> <td class="px-6 py-4">
<form action="{{ route('admin.membership-tiers.destroy', $tier) }}" method="POST" class="inline" onsubmit="return confirm('確定要刪除嗎?')"> <button type="button" @click="deleteFormAction = '{{ route('admin.membership-tiers.destroy', $tier) }}'; isDeleteConfirmOpen = true" class="text-red-600 hover:text-red-800">刪除</button>
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-800">刪除</button>
</form>
</td> </td>
</tr> </tr>
@empty @empty
@@ -116,5 +94,6 @@
</form> </form>
</div> </div>
</div> </div>
<x-delete-confirm-modal :message="__('Are you sure you want to delete this tier?')" />
</div> </div>
@endsection @endsection

View File

@@ -12,6 +12,7 @@
roleId: '{{ old('roleId', '') }}', roleId: '{{ old('roleId', '') }}',
roleName: '{{ old('name', '') }}', roleName: '{{ old('name', '') }}',
rolePermissions: @js(old('permissions', [])), rolePermissions: @js(old('permissions', [])),
currentUserRoleIds: @js($currentUserRoleIds ?? []),
isSystem: {{ old('is_system', '0') }}, isSystem: {{ old('is_system', '0') }},
modalTitle: '{{ $errors->any() && old('_method') == 'PUT' ? __('Edit Role') : ($errors->any() ? __('Create Role') : __('Create Role')) }}', modalTitle: '{{ $errors->any() && old('_method') == 'PUT' ? __('Edit Role') : ($errors->any() ? __('Create Role') : __('Create Role')) }}',
openModal(edit = false, id = '', name = '', permissions = [], isSys = false) { openModal(edit = false, id = '', name = '', permissions = [], isSys = false) {
@@ -22,6 +23,18 @@
this.isSystem = isSys; this.isSystem = isSys;
this.modalTitle = edit ? '{{ __('Edit Role') }}' : '{{ __('Create Role') }}'; this.modalTitle = edit ? '{{ __('Edit Role') }}' : '{{ __('Create Role') }}';
this.showModal = true; this.showModal = true;
},
isWarningModalOpen: false,
deleteWarningMsg: '',
triggerDeleteWarning(msg) {
this.deleteWarningMsg = msg;
this.isWarningModalOpen = true;
},
isDeleteConfirmOpen: false,
deleteFormAction: '',
confirmDelete(action) {
this.deleteFormAction = action;
this.isDeleteConfirmOpen = true;
} }
}"> }">
<!-- Header --> <!-- Header -->
@@ -36,18 +49,34 @@
</button> </button>
</div> </div>
<!-- Roles Content (Integrated Card) --> <!-- Roles Content (Integrated Card) -->
<div class="luxury-card rounded-3xl p-8 animate-luxury-in"> <div class="luxury-card rounded-3xl p-8 animate-luxury-in">
<!-- Toolbar --> <!-- Toolbar -->
<div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-10"> <div class="flex flex-col md:flex-row md:items-center justify-between gap-6 mb-10">
<form action="{{ route($baseRoute) }}" method="GET" class="relative group"> <form action="{{ route($baseRoute) }}" method="GET" class="flex flex-col md:flex-row md:items-center gap-4">
<span class="absolute inset-y-0 left-0 flex items-center pl-4 pointer-events-none z-10"> <div class="relative group">
<svg class="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <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="h-4 w-4 text-slate-400 group-focus-within:text-cyan-500 transition-colors" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="21" y1="21" x2="16.65" y2="16.65"></line> <circle cx="11" cy="11" r="8"></circle>
</svg> <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</span> </svg>
<input type="text" name="search" value="{{ request('search') }}" class="py-2.5 pl-12 pr-6 block w-full md:w-80 luxury-input" placeholder="{{ __('Search roles...') }}"> </span>
<input type="text" name="search" value="{{ request('search') }}" class="py-2.5 pl-12 pr-6 block w-full md:w-80 luxury-input" placeholder="{{ __('Search roles...') }}">
</div>
@if(auth()->user()->isSystemAdmin())
<div class="relative">
<select name="company_id" onchange="this.form.submit()" class="py-2.5 pl-4 pr-10 block w-full md:w-60 luxury-input">
<option value="">{{ __('All Affiliations') }}</option>
<option value="system" {{ request('company_id') === 'system' ? 'selected' : '' }}>{{ __('System Level') }}</option>
@foreach($companies as $company)
<option value="{{ $company->id }}" {{ request('company_id') == $company->id ? 'selected' : '' }}>{{ $company->name }}</option>
@endforeach
</select>
</div>
@endif
<input type="hidden" name="per_page" value="{{ request('per_page', 10) }}"> <input type="hidden" name="per_page" value="{{ request('per_page', 10) }}">
</form> </form>
</div> </div>
@@ -57,7 +86,7 @@
<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">{{ __('Role Name') }}</th> <th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Role Name') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Type') }}</th> <th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Affiliation') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Permissions') }}</th> <th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800">{{ __('Permissions') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Users') }}</th> <th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-center">{{ __('Users') }}</th>
<th class="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-[0.15em] border-b border-slate-100 dark:border-slate-800 text-right">{{ __('Actions') }}</th> <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>
@@ -85,8 +114,8 @@
{{ __('System Level') }} {{ __('System Level') }}
</span> </span>
@else @else
<span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border border-emerald-500/20 tracking-wider uppercase"> <span class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-bold bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border border-emerald-500/20 tracking-wider">
{{ __('Company Level') }} {{ $role->company->name ?? __('Company Level') }}
</span> </span>
@endif @endif
</td> </td>
@@ -95,7 +124,7 @@
@forelse($role->permissions->take(6) as $permission) @forelse($role->permissions->take(6) as $permission)
<span class="px-2 py-0.5 text-xs bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 rounded border border-slate-200 dark:border-slate-700 uppercase font-bold tracking-widest">{{ __(str_replace('menu.', '', $permission->name)) }}</span> <span class="px-2 py-0.5 text-xs bg-slate-100 dark:bg-slate-800 text-slate-600 dark:text-slate-300 rounded border border-slate-200 dark:border-slate-700 uppercase font-bold tracking-widest">{{ __(str_replace('menu.', '', $permission->name)) }}</span>
@empty @empty
<span class="text-xs font-bold text-slate-500 dark:text-slate-400 italic tracking-widest">{{ __('No permissions') }}</span> <span class="text-xs font-bold text-slate-500 dark:text-slate-400 tracking-widest">{{ __('No permissions') }}</span>
@endforelse @endforelse
@if($role->permissions->count() > 6) @if($role->permissions->count() > 6)
<span class="px-2 py-0.5 text-xs bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 rounded border border-slate-200 dark:border-slate-700 uppercase font-bold tracking-widest">+{{ $role->permissions->count() - 6 }}</span> <span class="px-2 py-0.5 text-xs bg-slate-100 dark:bg-slate-800 text-slate-500 dark:text-slate-400 rounded border border-slate-200 dark:border-slate-700 uppercase font-bold tracking-widest">+{{ $role->permissions->count() - 6 }}</span>
@@ -111,7 +140,7 @@
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg> <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg>
</button> </button>
@if($role->name !== 'super-admin' && (auth()->user()->isSystemAdmin() || !$role->is_system)) @if($role->name !== 'super-admin' && (auth()->user()->isSystemAdmin() || !$role->is_system))
<form action="{{ route($baseRoute . '.destroy', $role->id) }}" method="POST" @submit.prevent="if(confirm('{{ __('Are you sure you want to delete this role?') }}')) $el.submit()" class="inline text-slate-400"> <form action="{{ route($baseRoute . '.destroy', $role->id) }}" method="POST" @submit.prevent="if({{ $role->users()->count() }} > 0) { triggerDeleteWarning('{{ __('Cannot delete role with active users.') }}'); return; } confirmDelete('{{ route($baseRoute . '.destroy', $role->id) }}')" class="inline text-slate-400">
@csrf @csrf
@method('DELETE') @method('DELETE')
<button type="submit" class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-rose-500 hover:bg-rose-500/5 transition-all border border-transparent hover:border-rose-500/20 tooltip" title="{{ __('Delete') }}"> <button type="submit" class="p-2 rounded-lg bg-slate-50 dark:bg-slate-800 text-slate-400 hover:text-rose-500 hover:bg-rose-500/5 transition-all border border-transparent hover:border-rose-500/20 tooltip" title="{{ __('Delete') }}">
@@ -163,6 +192,19 @@
<input type="hidden" name="roleId" x-model="roleId"> <input type="hidden" name="roleId" x-model="roleId">
<div class="p-8 max-h-[65vh] overflow-y-auto custom-scrollbar"> <div class="p-8 max-h-[65vh] overflow-y-auto custom-scrollbar">
<!-- Warning for editing own role -->
<template x-if="isEdit && (currentUserRoleIds || []).map(String).includes(String(roleId))">
<div class="mb-8 p-5 bg-amber-500/10 border border-amber-500/20 text-amber-600 dark:text-amber-400 rounded-2xl font-bold flex items-start gap-4 animate-luxury-in">
<div class="size-10 bg-amber-500/20 rounded-xl flex items-center justify-center flex-shrink-0">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
</div>
<div class="text-sm">
<p class="text-base font-black leading-tight">{{ __('Warning: You are editing your own role!') }}</p>
<p class="font-bold mt-1 opacity-90 leading-relaxed">{{ __('Modifying your own administrative permissions may result in losing access to certain system functions.') }}</p>
</div>
</div>
</template>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8"> <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- Left: Basic Info --> <!-- Left: Basic Info -->
<div class="space-y-6"> <div class="space-y-6">
@@ -185,7 +227,7 @@
@if(auth()->user()->isSystemAdmin()) @if(auth()->user()->isSystemAdmin())
<div class="space-y-4"> <div class="space-y-4">
<label class="text-xs font-black text-slate-400 uppercase tracking-widest pl-1">{{ __('Role Type') }}</label> <label class="text-xs font-black text-slate-400 uppercase tracking-widest pl-1">{{ __('Affiliation') }}</label>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<label class="flex items-center gap-3 p-3 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-800/50 cursor-pointer transition-all border border-slate-200 dark:border-slate-800 group has-[:checked]:border-cyan-500/50 has-[:checked]:bg-cyan-500/5"> <label class="flex items-center gap-3 p-3 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-800/50 cursor-pointer transition-all border border-slate-200 dark:border-slate-800 group has-[:checked]:border-cyan-500/50 has-[:checked]:bg-cyan-500/5">
<input type="radio" name="is_system" value="1" x-model="isSystem" class="w-4 h-4 text-cyan-500 bg-transparent border-slate-300 focus:ring-cyan-500" :disabled="isEdit && roleName === 'super-admin'"> <input type="radio" name="is_system" value="1" x-model="isSystem" class="w-4 h-4 text-cyan-500 bg-transparent border-slate-300 focus:ring-cyan-500" :disabled="isEdit && roleName === 'super-admin'">
@@ -256,5 +298,51 @@
</div> </div>
</div> </div>
</template> </template>
<!-- Global Delete Warning Modal -->
<div x-show="isWarningModalOpen" class="fixed inset-0 z-[200] overflow-y-auto" x-cloak>
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
<div x-show="isWarningModalOpen" x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0" class="fixed inset-0 transition-opacity bg-slate-900/60 backdrop-blur-sm"
@click="isWarningModalOpen = false"></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen">&#8203;</span>
<div x-show="isWarningModalOpen" x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
class="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white dark:bg-slate-900 rounded-3xl shadow-2xl sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-8 border border-slate-100 dark:border-slate-800">
<div class="sm:flex sm:items-start">
<div class="flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto bg-rose-100 dark:bg-rose-500/10 rounded-2xl sm:mx-0 sm:h-12 sm:w-12 text-rose-600 dark:text-rose-400">
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-6 sm:text-left">
<h3 class="text-xl font-black text-slate-800 dark:text-white leading-6 tracking-tight outfit-font">
{{ __('Cannot Delete Role') }}
</h3>
<div class="mt-4">
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 leading-relaxed" x-text="deleteWarningMsg"></p>
</div>
</div>
</div>
<div class="mt-8 sm:mt-10 sm:flex sm:flex-row-reverse">
<button type="button" @click="isWarningModalOpen = false"
class="inline-flex justify-center w-full px-8 py-3 text-sm font-black text-white transition-all bg-slate-800 dark:bg-slate-700 rounded-xl hover:bg-slate-900 dark:hover:bg-slate-600 sm:w-auto tracking-widest uppercase">
{{ __('Got it') }}
</button>
</div>
</div>
</div>
</div>
<!-- Global Delete Confirm Modal -->
<x-delete-confirm-modal :message="__('Are you sure you want to delete this role? This action cannot be undone.')" />
</div> </div>
@endsection @endsection

View File

@@ -12,27 +12,9 @@
]; ];
@endphp @endphp
{{-- Toast 通知 --}}
@if(session('success'))
<div x-data="{ show: false }"
x-show="show"
x-cloak
x-init="setTimeout(() => { show = true; setTimeout(() => show = false, 3000) }, 50)"
x-transition:enter="transition cubic-bezier(0.34, 1.56, 0.64, 1) duration-300"
x-transition:enter-start="opacity-0 -translate-y-40"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-400"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 -translate-y-40"
class="fixed top-4 left-0 right-0 mx-auto w-max z-[100] bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg flex items-center">
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
{{ session('success') }}
</div>
@endif
<div class="px-6 py-8">
<div class="px-6 py-8" x-data="{ isDeleteConfirmOpen: false, deleteFormAction: '' }">
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">點數規則設定</h3> <h3 class="text-gray-900 dark:text-gray-200 text-3xl font-medium">點數規則設定</h3>
<button onclick="document.getElementById('createModal').classList.remove('hidden')" class="btn-luxury-primary"> <button onclick="document.getElementById('createModal').classList.remove('hidden')" class="btn-luxury-primary">
@@ -69,11 +51,7 @@
@endif @endif
</td> </td>
<td class="px-6 py-4"> <td class="px-6 py-4">
<form action="{{ route('admin.point-rules.destroy', $rule) }}" method="POST" class="inline" onsubmit="return confirm('確定要刪除嗎?')"> <button type="button" @click="deleteFormAction = '{{ route('admin.point-rules.destroy', $rule) }}'; isDeleteConfirmOpen = true" class="text-red-600 hover:text-red-800">刪除</button>
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-800">刪除</button>
</form>
</td> </td>
</tr> </tr>
@empty @empty
@@ -134,5 +112,6 @@
</form> </form>
</div> </div>
</div> </div>
<x-delete-confirm-modal :message="__('Are you sure you want to delete this rule?')" />
</div> </div>
@endsection @endsection

View File

@@ -0,0 +1,60 @@
@props([
'message' => __('Are you sure you want to delete this item? This action cannot be undone.'),
'title' => __('Confirm Deletion')
])
<template x-teleport="body">
<div x-show="isDeleteConfirmOpen" class="fixed inset-0 z-[200] overflow-y-auto" x-cloak>
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:block sm:p-0">
<div x-show="isDeleteConfirmOpen" x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200" x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0" class="fixed inset-0 transition-opacity bg-slate-900/60 backdrop-blur-sm"
@click="isDeleteConfirmOpen = false"></div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen">&#8203;</span>
<div x-show="isDeleteConfirmOpen" x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100" x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
class="inline-block px-4 pt-5 pb-4 overflow-hidden text-left align-bottom transition-all transform bg-white dark:bg-slate-900 rounded-3xl shadow-2xl sm:my-8 sm:align-middle sm:max-w-md sm:w-full sm:p-8 border border-slate-100 dark:border-slate-800">
<div class="sm:flex sm:items-start text-center sm:text-left">
<div
class="flex items-center justify-center flex-shrink-0 w-12 h-12 mx-auto bg-amber-100 dark:bg-amber-500/10 rounded-2xl sm:mx-0 sm:h-12 sm:w-12 text-amber-600 dark:text-amber-400">
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z" />
</svg>
</div>
<div class="mt-3 sm:mt-0 sm:ml-6">
<h3 class="text-xl font-black text-slate-800 dark:text-white leading-6 tracking-tight font-display uppercase">
{{ $title }}
</h3>
<div class="mt-4">
<p class="text-sm font-bold text-slate-500 dark:text-slate-400 leading-relaxed">
{{ $message }}
</p>
</div>
</div>
</div>
<div class="mt-8 sm:mt-10 sm:flex sm:flex-row-reverse gap-3">
<form :action="deleteFormAction" method="POST" class="inline">
@csrf
@method('DELETE')
<button type="submit"
class="inline-flex justify-center w-full px-6 py-3 text-sm font-black text-white transition-all bg-rose-500 rounded-xl hover:bg-rose-600 shadow-lg shadow-rose-200 dark:shadow-none hover:scale-[1.02] active:scale-[0.98] sm:w-auto uppercase tracking-widest font-display">
{{ __('Delete Permanently') }}
</button>
</form>
<button type="button" @click="isDeleteConfirmOpen = false"
class="inline-flex justify-center w-full px-6 py-3 mt-3 text-sm font-black text-slate-700 dark:text-slate-200 transition-all bg-slate-100 dark:bg-slate-800 rounded-xl hover:bg-slate-200 dark:hover:bg-slate-700 sm:mt-0 sm:w-auto uppercase tracking-widest font-display">
{{ __('Cancel') }}
</button>
</div>
</div>
</div>
</div>
</template>

View File

@@ -0,0 +1,52 @@
@php
$allErrors = [];
if (isset($errors)) {
foreach ($errors->getBags() as $bag) {
$allErrors = array_merge($allErrors, $bag->all());
}
}
@endphp
<div class="fixed top-8 left-1/2 -translate-x-1/2 z-[99999] w-full max-w-sm px-4 space-y-3 pointer-events-none">
@if(session('success'))
<div x-data="{ show: true }"
x-show="show"
x-init="setTimeout(() => show = false, 3000)"
x-transition:enter="transition ease-out duration-500"
x-transition:enter-start="opacity-0 transform -translate-y-4 scale-95"
x-transition:enter-end="opacity-100 transform translate-y-0 scale-100"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 transform translate-y-0 scale-100"
x-transition:leave-end="opacity-0 transform -translate-y-4 scale-95"
class="p-4 bg-white dark:bg-slate-900 border border-emerald-500/30 text-emerald-600 dark:text-emerald-400 rounded-2xl font-bold shadow-[0_20px_50px_rgba(16,185,129,0.15)] flex items-center gap-3 pointer-events-auto backdrop-blur-xl bg-opacity-90 dark:bg-opacity-90 animate-luxury-in">
<div class="size-8 bg-emerald-500/20 rounded-xl flex items-center justify-center flex-shrink-0 text-emerald-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg>
</div>
<span>{{ session('success') }}</span>
</div>
@endif
@if(session('error') || count($allErrors) > 0)
<div x-data="{ show: true }"
x-show="show"
x-init="setTimeout(() => show = false, 5000)"
x-transition:enter="transition ease-out duration-500"
x-transition:enter-start="opacity-0 transform -translate-y-4 scale-95"
x-transition:enter-end="opacity-100 transform translate-y-0 scale-100"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 transform translate-y-0 scale-100"
x-transition:leave-end="opacity-0 transform -translate-y-4 scale-95"
class="p-4 bg-white dark:bg-slate-900 border border-rose-500/30 text-rose-600 dark:text-rose-400 rounded-2xl font-bold shadow-[0_20px_50px_rgba(244,63,94,0.15)] flex items-center gap-3 pointer-events-auto backdrop-blur-xl bg-opacity-90 dark:bg-opacity-90 animate-luxury-in">
<div class="size-8 bg-rose-500/20 rounded-xl flex items-center justify-center flex-shrink-0 text-rose-500">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>
</div>
<span>
@if(session('error'))
{{ session('error') }}
@else
{{ $allErrors[0] ?? __('Please check the form for errors.') }}
@endif
</span>
</div>
@endif
</div>

View File

@@ -245,6 +245,8 @@
</div> </div>
<!-- End Content --> <!-- End Content -->
<x-toast />
@yield('scripts') @yield('scripts')
</body> </body>
</html> </html>

View File

@@ -38,15 +38,6 @@
<span>{{ __('Update') }}</span> <span>{{ __('Update') }}</span>
</button> </button>
@if (session('status') === 'password-updated')
<p
x-data="{ show: true }"
x-show="show"
x-transition
x-init="setTimeout(() => show = false, 2000)"
class="text-xs font-black text-emerald-500 dark:text-emerald-400 uppercase tracking-widest"
>{{ __('Saved.') }}</p>
@endif
</div> </div>
</form> </form>
</section> </section>

View File

@@ -65,15 +65,6 @@
<span>{{ __('Save') }}</span> <span>{{ __('Save') }}</span>
</button> </button>
@if (session('status') === 'profile-updated')
<p
x-data="{ show: true }"
x-show="show"
x-transition
x-init="setTimeout(() => show = false, 2000)"
class="text-xs font-black text-emerald-500 dark:text-emerald-400 uppercase tracking-widest"
>{{ __('Saved.') }}</p>
@endif
</div> </div>
</form> </form>
</section> </section>

View File

@@ -0,0 +1,91 @@
<?php
namespace Tests\Feature;
use App\Models\System\Company;
use App\Models\System\User;
use App\Models\System\Role;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class RoleDeletionTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->artisan('db:seed', ['--class' => 'RoleSeeder']);
}
public function test_cannot_delete_role_with_active_users()
{
$company = Company::create([
'name' => 'Test Company',
'code' => 'TEST',
'status' => 1,
]);
$admin = User::create([
'name' => 'Admin User',
'username' => 'admin_user_' . uniqid(),
'email' => 'admin_' . uniqid() . '@example.com',
'password' => bcrypt('password'),
'company_id' => $company->id,
]);
$admin->assignRole('super-admin');
$role = Role::create([
'name' => 'Test Role',
'company_id' => $company->id,
'guard_name' => 'web',
'is_system' => false,
]);
$user = User::create([
'name' => 'Test User',
'username' => 'test_user_' . uniqid(),
'email' => 'user_' . uniqid() . '@example.com',
'password' => bcrypt('password'),
'company_id' => $company->id,
]);
$user->assignRole($role);
$response = $this->actingAs($admin)
->delete(route('admin.permission.roles.destroy', $role->id));
$response->assertRedirect();
$response->assertSessionHas('error', __('Cannot delete role with active users.'));
$this->assertDatabaseHas('roles', ['id' => $role->id]);
}
public function test_can_delete_role_without_active_users()
{
$company = Company::create([
'name' => 'Test Company 2',
'code' => 'TEST2',
'status' => 1,
]);
$admin = User::create([
'name' => 'Admin User 2',
'username' => 'admin_user_2_' . uniqid(),
'email' => 'admin2_' . uniqid() . '@example.com',
'password' => bcrypt('password'),
'company_id' => $company->id,
]);
$admin->assignRole('super-admin');
$role = Role::create([
'name' => 'Empty Role',
'company_id' => $company->id,
'guard_name' => 'web',
'is_system' => false,
]);
$response = $this->actingAs($admin)
->delete(route('admin.permission.roles.destroy', $role->id));
$response->assertRedirect();
$response->assertSessionHas('success');
$this->assertDatabaseMissing('roles', ['id' => $role->id]);
}
}