Skip to content

Commit d34a212

Browse files
authored
Merge branch 'NCUAppTeam:main' into main
2 parents 3a9e7b6 + ee32368 commit d34a212

File tree

9 files changed

+276
-108
lines changed

9 files changed

+276
-108
lines changed

src/components/pages/select/belong.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ const SelectionContent: React.FC<SelectionContentProps> = ({options, onSelect})
3232
key={value}
3333
onClick={() => onSelect(value)}
3434
>
35-
<Link className="w-full h-full flex justify-center items-center" key={value} to={`/events/create`}>
35+
<Link
36+
className="w-full h-full flex justify-center items-center"
37+
key={value}
38+
to="/events/create"
39+
search={{ type: value }}
40+
>
3641
{ label }
3742
</Link>
3843
</button>
Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,21 @@
11
interface ScaleContentProps {
2-
onNext: (scale: 'small' | 'formal') => void; // 定义 onNext 是一个无参数、无返回值的函数
2+
onNext: (scale: 'small' | 'formal') => void;
33
}
44

55
export default function ScaleContent({onNext}: ScaleContentProps) {
66
return (
7-
<>
8-
<div></div>
9-
<div className="flex items-center justify-center">
10-
<div className="flex w-full flex-col">
11-
<button className="card bg-neutral-content rounded-box grid h-20 place-items-center text-gray-800 font-bold text-2xl mb-4 hover:scale-105 transition-transform"
12-
onClick={() => onNext('small')}
13-
aria-label="Create small event"
14-
role="button"
15-
>
16-
我要揪人!
17-
</button>
18-
<p className="mx-auto text-xl">快速建立小型活動</p>
19-
<div className="divider"></div>
20-
<button className="card bg-neutral-content rounded-box grid h-20 place-items-center text-gray-800 font-bold text-2xl mb-4 hover:scale-105 transition-transform"
21-
onClick={() => onNext('formal')}
22-
aria-label="Create formal event"
23-
role="button"
24-
>
25-
我要活動!
26-
</button>
27-
<p className="mx-auto text-xl">建立正式活動</p>
28-
</div>
7+
<div className="flex items-center justify-center min-h-screen">
8+
<div className="flex w-full flex-col">
9+
<button
10+
className="card bg-neutral-content rounded-box grid h-20 place-items-center text-gray-800 font-bold text-2xl mb-4 hover:scale-105 transition-transform"
11+
onClick={() => onNext('small')}
12+
aria-label="Create small event"
13+
role="button"
14+
>
15+
我要揪人!
16+
</button>
17+
<p className="text-xl mx-auto">快速建立小型活動</p>
2918
</div>
30-
</>
19+
</div>
3120
)
3221
}
Lines changed: 54 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,65 @@
1+
import { Link } from '@tanstack/react-router';
2+
import { useEffect, useState } from 'react';
3+
import { supabase } from '../../../utils/supabase';
4+
5+
interface EventType {
6+
type_id: number;
7+
type_name: string;
8+
hashtag_relation: number[];
9+
}
10+
111
interface TypeContentProps {
2-
onNext: () => void; // 定义 onNext 是一个无参数、无返回值的函数
12+
onNext: () => void;
313
}
414

515
export default function TypeContent({onNext}: TypeContentProps) {
16+
const [eventTypes, setEventTypes] = useState<EventType[]>([]);
17+
const [loading, setLoading] = useState(true);
18+
19+
useEffect(() => {
20+
async function fetchEventTypes() {
21+
try {
22+
setLoading(true);
23+
// Fetch only the big types (where hashtag_relation contains 0)
24+
const { data, error } = await supabase
25+
.from('event_type')
26+
.select('*')
27+
.contains('hashtag_relation', [0])
28+
.order('type_id', { ascending: true });
29+
30+
if (error) throw error;
31+
setEventTypes(data || []);
32+
} catch (error) {
33+
console.error('Error fetching event types:', error);
34+
} finally {
35+
setLoading(false);
36+
}
37+
}
38+
39+
fetchEventTypes();
40+
}, []);
41+
642
return (
743
<>
8-
<div></div>
944
<div className="flex items-center justify-center">
10-
<div className="flex w-full flex-col">
11-
<button className="card bg-neutral-content rounded-box grid h-20 place-items-center text-gray-800 font-bold text-2xl mb-8 hover:scale-105 transition-transform"
12-
onClick={onNext}
13-
>
14-
揪人共乘
15-
</button>
16-
<button className="card bg-neutral-content rounded-box grid h-20 place-items-center text-gray-800 font-bold text-2xl mb-8 hover:scale-105 transition-transform"
17-
onClick={onNext}
18-
>
19-
揪人運動
20-
</button>
21-
<button className="card bg-neutral-content rounded-box grid h-20 place-items-center text-gray-800 font-bold text-2xl mb-8 hover:scale-105 transition-transform"
22-
onClick={onNext}
23-
>
24-
揪人遊戲
25-
</button>
26-
<button className="card bg-neutral-content rounded-box grid h-20 place-items-center text-gray-800 font-bold text-2xl mb-8 hover:scale-105 transition-transform"
27-
onClick={onNext}
28-
>
29-
其他
30-
</button>
45+
<div className="flex w-full flex-col justify-center min-h-screen">
46+
{loading ? (
47+
<div className="text-center py-8">載入中...</div>
48+
) : (
49+
eventTypes.map((type) => (
50+
<Link
51+
key={type.type_id}
52+
to="/events/create"
53+
search={{ type: type.type_id.toString() }}
54+
className="card bg-neutral-content rounded-box grid h-20 place-items-center text-gray-800 font-bold text-2xl mb-8 hover:scale-105 transition-transform"
55+
onClick={onNext}
56+
>
57+
{type.type_name}
58+
</Link>
59+
))
60+
)}
3161
</div>
3262
</div>
3363
</>
34-
)
64+
);
3565
}

src/routes/events/$eventId.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { createFileRoute } from '@tanstack/react-router';
1+
import { createFileRoute, Link } from '@tanstack/react-router';
2+
import { ArrowLeft } from "flowbite-react-icons/outline";
23
import { useState } from 'react';
34
import { Header } from '../../components';
45
import { supabase } from '../../utils/supabase';
@@ -47,6 +48,13 @@ function EventDetails() {
4748
<div className="container mx-auto">
4849
<div className="relative z-10">
4950
<Header />
51+
<Link
52+
to="/events"
53+
className="absolute top-4 left-4 w-10 h-10 flex items-center justify-center bg-white bg-opacity-70 rounded-full shadow-md hover:bg-opacity-100 transition-all duration-300"
54+
aria-label="Return to events"
55+
>
56+
<ArrowLeft className="w-5 h-5" />
57+
</Link>
5058
</div>
5159
<div className='bg-gray-800'>
5260

src/routes/events/create.tsx

Lines changed: 154 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@ import { UserController } from '../../controllers/user';
55
import { AuthGuard } from '../../utils/auth';
66
import { supabase } from '../../utils/supabase';
77

8+
interface EventTag {
9+
type_id: number;
10+
type_name: string;
11+
hashtag_relation: number[];
12+
}
13+
814
export const Route = createFileRoute('/events/create')({
915
beforeLoad: AuthGuard,
10-
component: CreateEventScreen
16+
component: CreateEventScreen,
17+
validateSearch: (search: Record<string, unknown>) => {
18+
return {
19+
type: search.type as string
20+
};
21+
}
1122
})
1223

13-
1424
const styles = {
1525
container: {
1626
flex: 1,
@@ -30,17 +40,74 @@ const styles = {
3040
}
3141

3242
function CreateEventScreen() {
33-
const navigate = Route.useNavigate()
34-
const [selectedPhotos, setSelectedPhotos] = useState<File>()
35-
const [preview, setPreview] = useState<string>()
43+
const navigate = Route.useNavigate();
44+
const { type } = Route.useSearch();
45+
const selectedType = type ? parseInt(type) : null;
46+
const [selectedPhotos, setSelectedPhotos] = useState<File>();
47+
const [preview, setPreview] = useState<string>();
3648
const [inputs, setInputs] = useState({
3749
name: '',
3850
start_time: '',
3951
end_time: '',
4052
location: '',
4153
fee: 0,
42-
description: ''
43-
})
54+
description: '',
55+
type: selectedType,
56+
});
57+
const [eventTypeInfo, setEventTypeInfo] = useState<{ type_id: number, type_name: string } | null>(null);
58+
const [eventTags, setEventTags] = useState<EventTag[]>([]);
59+
const [loading, setLoading] = useState(true);
60+
const [selectedHashtags, setSelectedHashtags] = useState<number[]>([]);
61+
// Fetch event type information
62+
useEffect(() => {
63+
async function fetchTypeInfo() {
64+
if (selectedType) {
65+
const { data, error } = await supabase
66+
.from('event_type')
67+
.select('type_id, type_name')
68+
.eq('type_id', selectedType)
69+
.single();
70+
71+
if (!error && data) {
72+
setEventTypeInfo(data);
73+
}
74+
}
75+
}
76+
77+
fetchTypeInfo();
78+
}, [selectedType]);
79+
80+
useEffect(() => {
81+
async function fetchEventTags() {
82+
try {
83+
setLoading(true);
84+
85+
if (!selectedType) {
86+
setEventTags([]);
87+
return;
88+
}
89+
90+
// Find all hashtags that are associated with this event type
91+
// These are records where hashtag_relation contains the selected type_id
92+
const { data, error } = await supabase
93+
.from('event_type')
94+
.select('*')
95+
.not('hashtag_relation', 'eq', [0]) // Exclude main event types
96+
.contains('hashtag_relation', [selectedType]) // Find hashtags related to this event type
97+
.order('type_id', { ascending: true });
98+
99+
if (error) throw error;
100+
101+
setEventTags(data || []);
102+
} catch (error) {
103+
console.error('Error fetching event tags:', error);
104+
} finally {
105+
setLoading(false);
106+
}
107+
}
108+
109+
fetchEventTags();
110+
}, [selectedType]);
44111

45112
// create a preview as a side effect, whenever selected file is changed
46113
useEffect(() => {
@@ -64,28 +131,47 @@ function CreateEventScreen() {
64131
setSelectedPhotos(e.target.files[0])
65132
}
66133

67-
68134
async function addEvent(e: FormEvent) {
69135
e.preventDefault()
70-
const { data, error } = await supabase
71-
.from('events')
72-
.insert({
136+
137+
if (!selectedType) {
138+
alert('請先選擇活動類型');
139+
navigate({ to: '/events/select' });
140+
return;
141+
}
142+
143+
try {
144+
// Insert the event with the basic data and hashtags
145+
const eventInsertData = {
73146
...inputs,
74-
user_id: (await UserController.get()).id
75-
})
76-
.select('*')
77-
.single()
147+
user_id: (await UserController.get()).id,
148+
type: selectedType,
149+
// Store the selected hashtags as an array (even if empty)
150+
hashtags: selectedHashtags
151+
};
152+
153+
// Insert the event
154+
const { data: createdEvent, error: eventError } = await supabase
155+
.from('events')
156+
.insert(eventInsertData)
157+
.select('*')
158+
.single();
78159

79-
if (error !== null) {
80-
throw error
81-
}
160+
if (eventError) throw eventError;
161+
162+
console.log('Event created with hashtags:', selectedHashtags);
82163

83-
navigate({
84-
to: '/events/$eventId',
85-
params: {
86-
'eventId': data.id.toString()
87-
}
88-
})
164+
// Navigate to the event page
165+
navigate({
166+
to: '/events/$eventId',
167+
params: {
168+
'eventId': createdEvent.id.toString()
169+
}
170+
});
171+
} catch (error) {
172+
console.error('Error creating event:', error);
173+
alert('建立活動時發生錯誤');
174+
}
89175
}
90176

91177
return (
@@ -96,6 +182,50 @@ function CreateEventScreen() {
96182
</Link>
97183
<h1 className='text-xl text-white' style={{ marginLeft: 60 }}>新增活動</h1>
98184
</div>
185+
186+
{/* Display selected type */}
187+
<div className="px-4 py-2">
188+
<div className="bg-gray-700 rounded-lg p-3 mb-4">
189+
<h3 className="text-white font-bold mb-2">已選擇的類型</h3>
190+
<div className="flex flex-wrap gap-2">
191+
{eventTypeInfo && (
192+
<span className="badge badge-primary badge-lg">{eventTypeInfo.type_name}</span>
193+
)}
194+
</div>
195+
</div>
196+
</div>
197+
198+
{/* Display hashtags selection */}
199+
<div className="px-4 py-2">
200+
<div className="bg-gray-700 rounded-lg p-3 mb-4">
201+
<h3 className="text-white font-bold mb-2">選擇標籤 (可多選)</h3>
202+
<div className="flex flex-wrap gap-2">
203+
{loading ? (
204+
<p className="text-white">載入中...</p>
205+
) : eventTags.length > 0 ? (
206+
eventTags.map((tag) => (
207+
<button
208+
key={tag.type_id}
209+
type="button"
210+
className={`badge ${selectedHashtags.includes(tag.type_id) ? 'badge-primary' : 'badge-outline'} badge-lg cursor-pointer`}
211+
onClick={() => {
212+
setSelectedHashtags(prev =>
213+
prev.includes(tag.type_id)
214+
? prev.filter(id => id !== tag.type_id)
215+
: [...prev, tag.type_id]
216+
);
217+
}}
218+
>
219+
{tag.type_name}
220+
</button>
221+
))
222+
) : (
223+
<p className="text-white">沒有可用的標籤</p>
224+
)}
225+
</div>
226+
</div>
227+
</div>
228+
99229
<div className="grid gap-3 grid-cols-1">
100230
<h2 style={styles.text}>活動名稱</h2>
101231
<input

0 commit comments

Comments
 (0)