diff --git a/app/Http/Controllers/Landlord/TenantController.php b/app/Http/Controllers/Landlord/TenantController.php index cc24ad5..2e980b7 100644 --- a/app/Http/Controllers/Landlord/TenantController.php +++ b/app/Http/Controllers/Landlord/TenantController.php @@ -92,6 +92,26 @@ class TenantController extends Controller ]); } + /** + * 顯示租戶樣式管理頁面 + */ + public function showBranding(Tenant $tenant) + { + $logoUrl = null; + if (isset($tenant->branding['logo_path'])) { + $logoUrl = \Storage::url($tenant->branding['logo_path']); + } + + return Inertia::render('Landlord/Tenant/Branding', [ + 'tenant' => [ + 'id' => $tenant->id, + 'name' => $tenant->name ?? $tenant->id, + 'branding' => $tenant->branding ?? [], + ], + 'logo_url' => $logoUrl, + ]); + } + /** * 顯示編輯租戶表單 */ @@ -171,4 +191,44 @@ class TenantController extends Controller return back()->with('success', "域名 {$domainName} 已移除!"); } + + /** + * 更新租戶品牌樣式設定 + */ + public function updateBranding(Request $request, Tenant $tenant) + { + $validated = $request->validate([ + 'logo' => 'nullable|image|max:2048', + 'primary_color' => 'required|regex:/^#[0-9A-Fa-f]{6}$/', + 'text_color' => 'nullable|regex:/^#[0-9A-Fa-f]{6}$/', + ]); + + $branding = $tenant->branding ?? []; + + // 處理 Logo 上傳 + if ($request->hasFile('logo')) { + // 刪除舊 Logo + if (isset($branding['logo_path'])) { + \Storage::disk('public')->delete($branding['logo_path']); + } + + // 儲存新 Logo + $path = $request->file('logo')->store('tenant-logos', 'public'); + $branding['logo_path'] = $path; + } + + // 更新主色系 + $branding['primary_color'] = $validated['primary_color']; + + // 如果有傳入字體顏色則更新,否則保留原值(或預設值) + if (isset($validated['text_color'])) { + $branding['text_color'] = $validated['text_color']; + } elseif (!isset($branding['text_color'])) { + $branding['text_color'] = '#1a1a1a'; + } + + $tenant->update(['branding' => $branding]); + + return redirect()->back()->with('success', '樣式設定已更新'); + } } diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php index 231a939..3e1edba 100644 --- a/app/Http/Middleware/HandleInertiaRequests.php +++ b/app/Http/Middleware/HandleInertiaRequests.php @@ -55,6 +55,23 @@ class HandleInertiaRequests extends Middleware 'success' => $request->session()->get('success'), 'error' => $request->session()->get('error'), ], + 'branding' => function () { + $tenant = tenancy()->tenant; + if (!$tenant) { + return null; + } + + $logoUrl = null; + if (isset($tenant->branding['logo_path'])) { + $logoUrl = \Storage::url($tenant->branding['logo_path']); + } + + return [ + 'logo_url' => $logoUrl, + 'primary_color' => $tenant->branding['primary_color'] ?? '#01ab83', + 'text_color' => $tenant->branding['text_color'] ?? '#1a1a1a', + ]; + }, ]; } } diff --git a/public/favicon-landlord.png b/public/favicon-landlord.png new file mode 100644 index 0000000..93a4ba8 Binary files /dev/null and b/public/favicon-landlord.png differ diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index e69de29..0000000 diff --git a/public/images/star-erp-icon.png b/public/images/star-erp-icon.png new file mode 100644 index 0000000..93a4ba8 Binary files /dev/null and b/public/images/star-erp-icon.png differ diff --git a/resources/js/Components/ApplicationLogo.tsx b/resources/js/Components/ApplicationLogo.tsx index 686fe6d..85b1438 100644 --- a/resources/js/Components/ApplicationLogo.tsx +++ b/resources/js/Components/ApplicationLogo.tsx @@ -1,6 +1,22 @@ import { ImgHTMLAttributes } from 'react'; +import { usePage } from '@inertiajs/react'; +import { PageProps } from '@/types/global'; export default function ApplicationLogo(props: ImgHTMLAttributes) { + const { branding } = usePage().props; + + // 如果有自訂 Logo,優先使用 + if (branding?.logo_url) { + return ( + Logo + ); + } + + // 預設 Logo return ( { {item.label} diff --git a/resources/js/Layouts/AuthenticatedLayout.tsx b/resources/js/Layouts/AuthenticatedLayout.tsx index 3a0a6c4..4f23bad 100644 --- a/resources/js/Layouts/AuthenticatedLayout.tsx +++ b/resources/js/Layouts/AuthenticatedLayout.tsx @@ -20,7 +20,7 @@ import { } from "lucide-react"; import { toast, Toaster } from "sonner"; import { useState, useEffect, useMemo } from "react"; -import { Link, usePage } from "@inertiajs/react"; +import { Link, usePage, Head } from "@inertiajs/react"; import { cn } from "@/lib/utils"; import BreadcrumbNav, { BreadcrumbItemType } from "@/Components/shared/BreadcrumbNav"; import { @@ -33,6 +33,7 @@ import { } from "@/Components/ui/dropdown-menu"; import { usePermission } from "@/hooks/usePermission"; import ApplicationLogo from "@/Components/ApplicationLogo"; +import { generateLightestColor, generateLightColor, generateDarkColor, generateActiveColor } from "@/utils/colorUtils"; interface MenuItem { id: string; @@ -328,6 +329,18 @@ export default function AuthenticatedLayout({ return (
+ + + + {/* Mobile Header -> Global Header */}
@@ -395,7 +408,7 @@ export default function AuthenticatedLayout({ {!isCollapsed && ( - 小小冰室 ERP + 小小冰室 ERP )} {isCollapsed && ( @@ -444,7 +457,7 @@ export default function AuthenticatedLayout({
- 小小冰室 ERP + 小小冰室 ERP + {!isCollapsed && ( + + )}
-