Feature: Tenant Short Name and Branding Implementation
- Added short_name to Tenant model and controller - Updated Landlord/Tenant pages (Create, Edit, Show, Index) - Implemented branding customization (Favicon, Login Copyright, Sidebar Title) - Updated HandleInertiaRequests to share branding data
This commit is contained in:
@@ -454,7 +454,7 @@ export default function AuthenticatedLayout({
|
||||
</button>
|
||||
<Link href="/" className="flex items-center gap-2">
|
||||
<ApplicationLogo className="w-8 h-8 rounded-lg object-contain" />
|
||||
<span className="font-bold text-slate-900">小小冰室 ERP</span>
|
||||
<span className="font-bold text-slate-900">{branding?.short_name || '小小冰室'} ERP</span>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -510,7 +510,7 @@ export default function AuthenticatedLayout({
|
||||
{!isCollapsed && (
|
||||
<Link href="/" className="flex items-center gap-2 group">
|
||||
<ApplicationLogo className="w-8 h-8 rounded-lg object-contain group-hover:scale-110 transition-transform" />
|
||||
<span className="font-extrabold text-primary-main text-lg tracking-tight">小小冰室 ERP</span>
|
||||
<span className="font-extrabold text-primary-main text-lg tracking-tight">{branding?.short_name || '小小冰室'} ERP</span>
|
||||
</Link>
|
||||
)}
|
||||
{isCollapsed && (
|
||||
@@ -559,7 +559,7 @@ export default function AuthenticatedLayout({
|
||||
<div className="h-16 flex items-center justify-between px-6 border-b border-slate-100">
|
||||
<Link href="/" className="flex items-center gap-2">
|
||||
<ApplicationLogo className="w-8 h-8 rounded-lg object-contain" />
|
||||
<span className="font-extrabold text-primary-main text-lg">小小冰室 ERP</span>
|
||||
<span className="font-extrabold text-primary-main text-lg">{branding?.short_name || '小小冰室'} ERP</span>
|
||||
</Link>
|
||||
<button onClick={() => setIsMobileOpen(false)} className="p-2 text-slate-400">
|
||||
<X className="h-5 w-5" />
|
||||
@@ -588,7 +588,7 @@ export default function AuthenticatedLayout({
|
||||
{children}
|
||||
</div>
|
||||
<footer className="mt-auto py-6 text-center text-sm text-slate-400">
|
||||
Copyright © {new Date().getFullYear()} 小小冰室. All rights reserved. Design by 星科技
|
||||
Copyright © {new Date().getFullYear()} {branding?.name || '小小冰室'}. All rights reserved. Design by 星科技
|
||||
</footer>
|
||||
<Toaster richColors closeButton position="top-center" />
|
||||
</main>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Head, useForm } from "@inertiajs/react";
|
||||
import { Head, useForm, usePage } from "@inertiajs/react";
|
||||
import { PageProps } from "@/types/global";
|
||||
import { FormEventHandler, useEffect } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Button } from "@/Components/ui/button";
|
||||
@@ -8,6 +9,7 @@ import InputError from "../../Components/InputError";
|
||||
import ApplicationLogo from "../../Components/ApplicationLogo";
|
||||
|
||||
export default function Login() {
|
||||
const { props } = usePage<PageProps>();
|
||||
const { data, setData, post, processing, errors, reset } = useForm({
|
||||
username: localStorage.getItem("saved_username") || "",
|
||||
password: "",
|
||||
@@ -134,7 +136,7 @@ export default function Login() {
|
||||
</div>
|
||||
|
||||
<p className="text-center text-gray-400 text-sm mt-8">
|
||||
© 2026 小小冰室. All rights reserved.
|
||||
© {new Date().getFullYear()} {props.branding?.name || '小小冰室'}. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ export default function TenantCreate() {
|
||||
const { data, setData, post, processing, errors } = useForm({
|
||||
id: "",
|
||||
name: "",
|
||||
short_name: "",
|
||||
email: "",
|
||||
domain: "",
|
||||
});
|
||||
@@ -55,6 +56,21 @@ export default function TenantCreate() {
|
||||
{errors.name && <p className="mt-1 text-sm text-red-500">{errors.name}</p>}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||
客戶簡稱
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={data.short_name}
|
||||
onChange={(e) => setData("short_name", e.target.value)}
|
||||
placeholder="例如:小冰"
|
||||
className="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-main focus:border-primary-main"
|
||||
/>
|
||||
<p className="mt-1 text-sm text-slate-500">選填</p>
|
||||
{errors.short_name && <p className="mt-1 text-sm text-red-500">{errors.short_name}</p>}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||
聯絡信箱
|
||||
|
||||
@@ -5,6 +5,7 @@ import { FormEvent } from "react";
|
||||
interface Tenant {
|
||||
id: string;
|
||||
name: string;
|
||||
short_name: string | null;
|
||||
email: string | null;
|
||||
is_active: boolean;
|
||||
}
|
||||
@@ -16,6 +17,7 @@ interface Props {
|
||||
export default function TenantEdit({ tenant }: Props) {
|
||||
const { data, setData, put, processing, errors } = useForm({
|
||||
name: tenant.name,
|
||||
short_name: tenant.short_name || "",
|
||||
email: tenant.email || "",
|
||||
is_active: tenant.is_active,
|
||||
});
|
||||
@@ -62,6 +64,19 @@ export default function TenantEdit({ tenant }: Props) {
|
||||
{errors.name && <p className="mt-1 text-sm text-red-500">{errors.name}</p>}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||
客戶簡稱
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={data.short_name}
|
||||
onChange={(e) => setData("short_name", e.target.value)}
|
||||
className="w-full px-4 py-2 border border-slate-300 rounded-lg focus:ring-2 focus:ring-primary-main focus:border-primary-main"
|
||||
/>
|
||||
<p className="mt-1 text-sm text-slate-500">選填</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 mb-2">
|
||||
聯絡信箱
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
interface Tenant {
|
||||
id: string;
|
||||
name: string;
|
||||
short_name: string | null;
|
||||
email: string | null;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
@@ -67,6 +68,9 @@ export default function TenantIndex({ tenants }: Props) {
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase">
|
||||
名稱
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase">
|
||||
簡稱
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-semibold text-slate-500 uppercase">
|
||||
域名
|
||||
</th>
|
||||
@@ -84,7 +88,7 @@ export default function TenantIndex({ tenants }: Props) {
|
||||
<tbody className="divide-y divide-slate-100">
|
||||
{tenants.length === 0 ? (
|
||||
<tr>
|
||||
<td colSpan={6} className="px-6 py-12 text-center text-slate-500">
|
||||
<td colSpan={7} className="px-6 py-12 text-center text-slate-500">
|
||||
尚無客戶資料,請點擊「新增客戶」建立第一個客戶
|
||||
</td>
|
||||
</tr>
|
||||
@@ -102,6 +106,9 @@ export default function TenantIndex({ tenants }: Props) {
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-slate-700">
|
||||
{tenant.short_name || '-'}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
{tenant.domains.length > 0 ? (
|
||||
<div className="flex items-center gap-1 flex-wrap">
|
||||
|
||||
@@ -11,6 +11,7 @@ interface Domain {
|
||||
interface Tenant {
|
||||
id: string;
|
||||
name: string;
|
||||
short_name: string | null;
|
||||
email: string | null;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
@@ -97,6 +98,10 @@ export default function TenantShow({ tenant }: Props) {
|
||||
</span>
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-slate-500">客戶簡稱</dt>
|
||||
<dd className="mt-1 text-slate-900">{tenant.short_name || "-"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm text-slate-500">聯絡信箱</dt>
|
||||
<dd className="mt-1 text-slate-900">{tenant.email || "-"}</dd>
|
||||
|
||||
3
resources/js/types/global.d.ts
vendored
3
resources/js/types/global.d.ts
vendored
@@ -12,6 +12,8 @@ export interface AuthUser {
|
||||
}
|
||||
|
||||
export interface Branding {
|
||||
name?: string;
|
||||
short_name?: string;
|
||||
logo_url?: string | null;
|
||||
primary_color?: string;
|
||||
text_color?: string;
|
||||
@@ -26,6 +28,7 @@ export interface PageProps {
|
||||
error?: string;
|
||||
};
|
||||
branding?: Branding | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
Reference in New Issue
Block a user