-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathHome.tsx
More file actions
164 lines (153 loc) · 6.07 KB
/
Copy pathHome.tsx
File metadata and controls
164 lines (153 loc) · 6.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import { HiOutlineCalendar, HiOutlineCog, HiOutlinePlus, HiOutlineUser, HiOutlineUsers } from "react-icons/hi";
import { NavLink } from "react-router";
import { type InvolvedProjects, involvedProjectsResSchema } from "../../../common/schema";
import Header from "../components/Header";
import { useData } from "../hooks";
import { API_ENDPOINT } from "../utils";
export default function HomePage() {
const { data: involvedProjects, loading } = useData(`${API_ENDPOINT}/projects/mine`, involvedProjectsResSchema);
return (
<>
<Header />
{loading ? (
<div className="min-h-[calc(100dvh_-_64px)] bg-blue-50 flex items-center justify-center">
<div className="py-4">
<span className="loading loading-dots loading-md text-gray-400" />
</div>
</div>
) : involvedProjects ? (
<ProjectDashboard involvedProjects={involvedProjects} />
) : (
<div className="min-h-[calc(100dvh_-_64px)] bg-blue-50 flex items-center justify-center">
<EmptyState />
</div>
)}
</>
);
}
function ProjectDashboard({ involvedProjects }: { involvedProjects: InvolvedProjects }) {
const sortedProjects = [...involvedProjects].sort((a, b) => {
if (a.isHost !== b.isHost) {
return a.isHost ? -1 : 1;
}
return new Date(b.startDate).getTime() - new Date(a.startDate).getTime();
});
return (
<div className="min-h-[calc(100dvh_-_64px)] bg-blue-50">
<div className="container mx-auto px-4 py-8">
{/* Hero Section */}
<div className="text-center mb-12">
<div className="flex items-center justify-center mt-2 mb-6">
<img src="/logo.svg" alt="logo" width={48} className="mr-4" />
<h1 className="text-4xl text-primary font-mplus">イツヒマ</h1>
</div>
<p className="text-xl text-gray-600 mb-8">「いつヒマ?」で日程調整しよう</p>
<NavLink
to="/new"
className="btn btn-primary btn-lg px-8 py-4 text-lg shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1"
>
<HiOutlinePlus className="mr-2" size={20} />
新しいイベントを作成
</NavLink>
</div>
{involvedProjects.length > 0 && (
<div className="space-y-8">
{/* All Projects */}
<section>
<h2 className="text-2xl font-bold text-gray-800 mb-6 flex items-center">
<HiOutlineCalendar className="mr-3 text-gray-700" size={28} />
あなたのイベント
</h2>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{sortedProjects.map((project) => (
<ProjectCard key={project.id} project={project} />
))}
</div>
</section>
</div>
)}
</div>
</div>
);
}
function ProjectCard({ project }: { project: InvolvedProjects[0] }) {
return (
<NavLink
to={`/${project.id}`}
className={`group relative block bg-white rounded-xl shadow-lg hover:shadow-xl
transition-all duration-300 transform hover:-translate-y-1 overflow-hidden
border-l-4 ${project.isHost ? "border-primary" : "border-secondary"}
focus:outline-none focus:ring-4 focus:ring-primary/20`}
aria-label={`「${project.name}」の詳細を見る`}
>
<div className="p-6">
<div className="flex justify-between items-start mb-4">
<div className="flex-1 mr-2">
<h3 className="text-xl font-semibold text-gray-800 mb-2 break-words">{project.name}</h3>
<span className={`badge badge-sm ${project.isHost ? "badge-primary" : "badge-secondary"}`}>
{project.isHost ? (
<>
<HiOutlineUser size={12} />
<span>主催者</span>
</>
) : (
<>
<HiOutlineUsers size={12} />
<span>参加者</span>
</>
)}
</span>
</div>
{project.isHost && (
<NavLink
to={`/${project.id}/edit`}
onClick={(e) => e.stopPropagation()}
className="btn btn-ghost btn-sm px-3 py-1 text-gray-500 hover:text-gray-700 hover:bg-gray-100 transition-all"
>
<HiOutlineCog size={14} />
<span className="text-xs">管理</span>
</NavLink>
)}
</div>
<div className="flex items-center text-gray-600 mb-4">
<HiOutlineCalendar className="mr-2" size={16} />
<span className="text-sm">
{formatDate(project.startDate.toLocaleDateString())} ~{formatDate(project.endDate.toLocaleDateString())}
</span>
</div>
</div>
<span
className={`absolute bottom-4 right-4 ${project.isHost ? "text-primary" : "text-secondary"} text-2xl pointer-events-none
group-hover:translate-x-1 transition-transform`}
aria-hidden="true"
>
›
</span>
</NavLink>
);
}
function EmptyState() {
return (
<div className="text-center py-16">
<div className="mb-8">
<div className="w-32 h-32 mx-auto mb-6 bg-gray-100 rounded-full flex items-center justify-center">
<HiOutlineCalendar className="text-gray-400" size={64} />
</div>
<h3 className="text-2xl font-semibold text-gray-800 mb-3">まだイベントがありません</h3>
<p className="text-gray-600 mb-8 max-w-md mx-auto">イベントを作成して、日程調整を始めましょう</p>
</div>
<NavLink
to="/new"
className="btn btn-primary btn-lg px-8 py-4 text-lg shadow-lg hover:shadow-xl transition-all duration-300"
>
<HiOutlinePlus className="mr-2" size={20} />
イベントを作成する
</NavLink>
</div>
);
}
// ---------- Utility ----------
const formatDate = (isoDate: string) => {
const date = new Date(isoDate);
return date.toLocaleDateString("ja-JP");
};