feat: 實現版本號自動化更新與修復側邊欄 RWD 問題
This commit is contained in:
@@ -386,13 +386,15 @@ export default function AuthenticatedLayout({
|
||||
});
|
||||
};
|
||||
|
||||
const renderMenuItem = (item: MenuItem, level: number = 0) => {
|
||||
const renderMenuItem = (item: MenuItem, level: number = 0, forceExpand: boolean = false) => {
|
||||
const hasChildren = item.children && item.children.length > 0;
|
||||
const isExpanded = expandedItems.includes(item.id);
|
||||
const isActive = item.route
|
||||
? (item.route === '/' ? url === '/' : url.startsWith(item.route))
|
||||
: false;
|
||||
|
||||
const effectivelyCollapsed = isCollapsed && !forceExpand;
|
||||
|
||||
return (
|
||||
<div key={item.id} className="mb-1">
|
||||
{hasChildren ? (
|
||||
@@ -401,21 +403,21 @@ export default function AuthenticatedLayout({
|
||||
className={cn(
|
||||
"w-full flex items-center transition-all rounded-lg group",
|
||||
level === 0 ? "px-3 py-2.5" : "px-3 py-2 pl-10",
|
||||
level === 0 && !isCollapsed && "hover:bg-slate-100",
|
||||
isCollapsed && level === 0 && "justify-center px-0 h-10 w-10 mx-auto hover:bg-slate-100"
|
||||
level === 0 && !effectivelyCollapsed && "hover:bg-slate-100",
|
||||
effectivelyCollapsed && level === 0 && "justify-center px-0 h-10 w-10 mx-auto hover:bg-slate-100"
|
||||
)}
|
||||
title={isCollapsed ? item.label : ""}
|
||||
title={effectivelyCollapsed ? item.label : ""}
|
||||
>
|
||||
{level === 0 && (
|
||||
<span className={cn(
|
||||
"flex-shrink-0 transition-all",
|
||||
isCollapsed ? "mr-0" : "mr-3 text-slate-500 group-hover:text-slate-900"
|
||||
effectivelyCollapsed ? "mr-0" : "mr-3 text-slate-500 group-hover:text-slate-900"
|
||||
)}>
|
||||
{item.icon}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{!isCollapsed && (
|
||||
{!effectivelyCollapsed && (
|
||||
<>
|
||||
<span className="flex-1 text-left text-base font-medium text-slate-700 group-hover:text-slate-900 truncate">
|
||||
{item.label}
|
||||
@@ -438,22 +440,22 @@ export default function AuthenticatedLayout({
|
||||
className={cn(
|
||||
"w-full flex items-center transition-all rounded-lg group",
|
||||
level === 0 ? "px-3 py-2.5" : "px-3 py-2",
|
||||
level > 0 && !isCollapsed && "pl-11",
|
||||
level > 0 && !effectivelyCollapsed && "pl-11",
|
||||
isActive ? "bg-primary-lightest text-primary-main" : "text-slate-600 hover:bg-slate-100 hover:text-slate-900",
|
||||
isCollapsed && level === 0 && "justify-center px-0 h-10 w-10 mx-auto"
|
||||
effectivelyCollapsed && level === 0 && "justify-center px-0 h-10 w-10 mx-auto"
|
||||
)}
|
||||
title={isCollapsed ? item.label : ""}
|
||||
title={effectivelyCollapsed ? item.label : ""}
|
||||
>
|
||||
{item.icon && (
|
||||
<span className={cn(
|
||||
"flex-shrink-0 transition-all",
|
||||
isCollapsed ? "mr-0" : "mr-3",
|
||||
effectivelyCollapsed ? "mr-0" : "mr-3",
|
||||
isActive ? "text-primary-main" : "text-slate-500 group-hover:text-slate-900"
|
||||
)}>
|
||||
{item.icon}
|
||||
</span>
|
||||
)}
|
||||
{!isCollapsed && (
|
||||
{!effectivelyCollapsed && (
|
||||
<span className="text-base font-medium truncate">
|
||||
{item.label}
|
||||
</span>
|
||||
@@ -461,9 +463,9 @@ export default function AuthenticatedLayout({
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasChildren && isExpanded && !isCollapsed && (
|
||||
{hasChildren && isExpanded && !effectivelyCollapsed && (
|
||||
<div className="mt-1 space-y-1">
|
||||
{item.children?.map((child) => renderMenuItem(child, level + 1))}
|
||||
{item.children?.map((child) => renderMenuItem(child, level + 1, forceExpand))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -573,7 +575,7 @@ export default function AuthenticatedLayout({
|
||||
</div>
|
||||
|
||||
<div className="p-4 border-t border-slate-100 flex items-center justify-between">
|
||||
{!isCollapsed && <p className="text-[10px] font-medium text-slate-400 uppercase tracking-wider px-2">Version 1.0.0</p>}
|
||||
{!isCollapsed && <p className="text-[10px] font-medium text-slate-400 uppercase tracking-wider px-2">Version {props.app_version || '1.0.0'}</p>}
|
||||
<button
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
className={cn(
|
||||
@@ -613,7 +615,7 @@ export default function AuthenticatedLayout({
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto p-4" scroll-region="true">
|
||||
<nav className="space-y-1">
|
||||
{menuItems.map((item) => renderMenuItem(item))}
|
||||
{menuItems.map((item) => renderMenuItem(item, 0, true))}
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Head, router } from '@inertiajs/react';
|
||||
import { PageProps } from '@/types/global';
|
||||
import Pagination from '@/Components/shared/Pagination';
|
||||
import { SearchableSelect } from "@/Components/ui/searchable-select";
|
||||
import { FileText, Search, RotateCcw, Calendar, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { FileText, Search, RotateCcw, Calendar } from 'lucide-react';
|
||||
import LogTable, { Activity } from '@/Components/ActivityLog/LogTable';
|
||||
import ActivityDetailDialog from '@/Components/ActivityLog/ActivityDetailDialog';
|
||||
import { Button } from '@/Components/ui/button';
|
||||
@@ -57,10 +57,7 @@ export default function ActivityLogIndex({ activities, filters, subject_types, u
|
||||
const [causer, setCauser] = useState(filters.causer_id || 'all');
|
||||
const [dateRangeType, setDateRangeType] = useState('custom');
|
||||
|
||||
// Advanced Filter Toggle
|
||||
const [showAdvancedFilter, setShowAdvancedFilter] = useState(
|
||||
!!(filters.date_start || filters.date_end)
|
||||
);
|
||||
|
||||
|
||||
const handleDateRangeChange = (type: string) => {
|
||||
setDateRangeType(type);
|
||||
@@ -161,75 +158,12 @@ export default function ActivityLogIndex({ activities, filters, subject_types, u
|
||||
</div>
|
||||
|
||||
{/* 篩選區塊 */}
|
||||
<div className="bg-white p-5 rounded-lg shadow-sm border border-grey-4 mb-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-4 mb-4">
|
||||
{/* 關鍵字搜尋 */}
|
||||
<div className="md:col-span-4 space-y-1">
|
||||
<Label className="text-xs font-medium text-grey-1">關鍵字搜尋</Label>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||
<Input
|
||||
placeholder="搜尋描述、內容..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="pl-10 h-9 block"
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleFilter()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 事件類型 */}
|
||||
<div className="md:col-span-2 space-y-1">
|
||||
<Label className="text-xs font-medium text-grey-1">事件類型</Label>
|
||||
<Select value={event} onValueChange={setEvent}>
|
||||
<SelectTrigger className="h-9">
|
||||
<SelectValue placeholder="所有事件" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">所有事件</SelectItem>
|
||||
<SelectItem value="created">新增 (Created)</SelectItem>
|
||||
<SelectItem value="updated">更新 (Updated)</SelectItem>
|
||||
<SelectItem value="deleted">刪除 (Deleted)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 操作對象 */}
|
||||
<div className="md:col-span-3 space-y-1">
|
||||
<Label className="text-xs font-medium text-grey-1">操作對象</Label>
|
||||
<SearchableSelect
|
||||
value={subjectType}
|
||||
onValueChange={setSubjectType}
|
||||
options={[
|
||||
{ label: "所有對象", value: "all" },
|
||||
...subject_types
|
||||
]}
|
||||
placeholder="選擇對象"
|
||||
className="w-full h-9"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 操作人員 */}
|
||||
<div className="md:col-span-3 space-y-1">
|
||||
<Label className="text-xs font-medium text-grey-1">操作人員</Label>
|
||||
<SearchableSelect
|
||||
value={causer}
|
||||
onValueChange={setCauser}
|
||||
options={[
|
||||
{ label: "所有人員", value: "all" },
|
||||
...users
|
||||
]}
|
||||
placeholder="選擇人員"
|
||||
className="w-full h-9"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 2: Date Filters (Collapsible) */}
|
||||
{showAdvancedFilter && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-4 items-end animate-in fade-in slide-in-from-top-2 duration-200">
|
||||
<div className="md:col-span-6 space-y-2">
|
||||
<Label className="text-xs font-medium text-grey-1">快速時間區間</Label>
|
||||
<div className="bg-white rounded-xl shadow-sm border border-grey-4 p-5 mb-6">
|
||||
<div className="space-y-4">
|
||||
{/* Top Config: Date Range & Quick Buttons */}
|
||||
<div className="flex flex-col lg:flex-row gap-4 lg:items-end">
|
||||
<div className="flex-none space-y-2">
|
||||
<Label className="text-xs font-medium text-grey-2">快速時間區間</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{[
|
||||
{ label: "今日", value: "today" },
|
||||
@@ -254,10 +188,11 @@ export default function ActivityLogIndex({ activities, filters, subject_types, u
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="md:col-span-6">
|
||||
<div className="grid grid-cols-2 gap-4 items-end">
|
||||
{/* Date Inputs */}
|
||||
<div className="w-full lg:flex-1">
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs font-medium text-grey-2">開始日期</Label>
|
||||
<Label className="text-xs text-grey-2 font-medium">開始日期</Label>
|
||||
<div className="relative">
|
||||
<Calendar className="absolute left-2.5 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400 pointer-events-none" />
|
||||
<Input
|
||||
@@ -267,13 +202,12 @@ export default function ActivityLogIndex({ activities, filters, subject_types, u
|
||||
setDateStart(e.target.value);
|
||||
setDateRangeType('custom');
|
||||
}}
|
||||
// block w-full to ensure it fills space
|
||||
className="pl-9 block w-full h-9 bg-white"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-xs font-medium text-grey-2">結束日期</Label>
|
||||
<Label className="text-xs text-grey-2 font-medium">結束日期</Label>
|
||||
<div className="relative">
|
||||
<Calendar className="absolute left-2.5 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400 pointer-events-none" />
|
||||
<Input
|
||||
@@ -290,43 +224,88 @@ export default function ActivityLogIndex({ activities, filters, subject_types, u
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Action Bar */}
|
||||
<div className="flex items-center justify-end border-t border-grey-4 pt-5 gap-3 mt-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setShowAdvancedFilter(!showAdvancedFilter)}
|
||||
className="mr-auto text-gray-500 hover:text-gray-900 h-9"
|
||||
>
|
||||
{showAdvancedFilter ? (
|
||||
<>
|
||||
<ChevronUp className="h-4 w-4 mr-1" />
|
||||
收合篩選
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ChevronDown className="h-4 w-4 mr-1" />
|
||||
進階篩選
|
||||
{(dateStart || dateEnd) && (
|
||||
<span className="ml-2 w-2 h-2 rounded-full bg-primary-main" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleReset}
|
||||
className="flex items-center gap-2 button-outlined-primary h-9"
|
||||
>
|
||||
<RotateCcw className="h-4 w-4" />
|
||||
重置
|
||||
</Button>
|
||||
<Button onClick={handleFilter} className="button-filled-primary h-9 px-6 gap-2">
|
||||
<Search className="h-4 w-4" />
|
||||
查詢
|
||||
</Button>
|
||||
{/* Detailed Filters row */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-12 gap-4 items-end">
|
||||
{/* 事件類型 */}
|
||||
<div className="md:col-span-2 space-y-1">
|
||||
<Label className="text-xs font-medium text-grey-2">事件類型</Label>
|
||||
<Select value={event} onValueChange={setEvent}>
|
||||
<SelectTrigger className="h-9 bg-white">
|
||||
<SelectValue placeholder="所有事件" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">所有事件</SelectItem>
|
||||
<SelectItem value="created">新增 (Created)</SelectItem>
|
||||
<SelectItem value="updated">更新 (Updated)</SelectItem>
|
||||
<SelectItem value="deleted">刪除 (Deleted)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 操作對象 */}
|
||||
<div className="md:col-span-2 space-y-1">
|
||||
<Label className="text-xs font-medium text-grey-2">操作對象</Label>
|
||||
<SearchableSelect
|
||||
value={subjectType}
|
||||
onValueChange={setSubjectType}
|
||||
options={[
|
||||
{ label: "所有對象", value: "all" },
|
||||
...subject_types
|
||||
]}
|
||||
placeholder="選擇對象"
|
||||
className="w-full h-9 bg-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 操作人員 */}
|
||||
<div className="md:col-span-2 space-y-1">
|
||||
<Label className="text-xs font-medium text-grey-2">操作人員</Label>
|
||||
<SearchableSelect
|
||||
value={causer}
|
||||
onValueChange={setCauser}
|
||||
options={[
|
||||
{ label: "所有人員", value: "all" },
|
||||
...users
|
||||
]}
|
||||
placeholder="選擇人員"
|
||||
className="w-full h-9 bg-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 關鍵字搜尋 */}
|
||||
<div className="md:col-span-3 space-y-1">
|
||||
<Label className="text-xs font-medium text-grey-2">關鍵字搜尋</Label>
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||||
<Input
|
||||
placeholder="搜尋內容..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="pl-10 h-9 block bg-white"
|
||||
onKeyDown={(e) => e.key === 'Enter' && handleFilter()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Action Buttons Integrated */}
|
||||
<div className="md:col-span-3 flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={handleReset}
|
||||
className="flex-1 items-center gap-2 button-outlined-primary h-9"
|
||||
>
|
||||
<RotateCcw className="h-4 w-4" />
|
||||
重置
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleFilter}
|
||||
className="flex-1 button-filled-primary h-9 gap-2 shadow-sm"
|
||||
>
|
||||
<Search className="h-4 w-4" /> 查詢
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
1
resources/js/types/global.d.ts
vendored
1
resources/js/types/global.d.ts
vendored
@@ -28,6 +28,7 @@ export interface PageProps {
|
||||
error?: string;
|
||||
};
|
||||
branding?: Branding | null;
|
||||
app_version?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user