Skip to content

Commit ea70088

Browse files
Added SCEvents ability to create event (#2070)
* Added SCEvents ability to create event * Fix indent issues * Modified the scevents api port * Simplified code, removed unnecessary functions * Implement event registration logic for SCEvents (#2073) * Added SCEvents ability to create event * Fixing rebase problems * Made changes to SCEvents.js with Steven's comments --------- Co-authored-by: Quoc Anh Khoa Nguyen <anhkhoa17092006@gmail.com>
1 parent 18aaa76 commit ea70088

File tree

6 files changed

+595
-30
lines changed

6 files changed

+595
-30
lines changed

src/APIFunctions/SCEvents.js

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,57 @@
11
import { ApiResponse } from './ApiResponses';
22

3-
const SCEVENTS_API_URL = 'http://localhost:8080';
3+
const SCEVENTS_API_URL = 'http://localhost:8002';
44

55
export async function getAllSCEvents() {
6-
let status = new ApiResponse();
7-
6+
const status = new ApiResponse();
87
try {
9-
const url = new URL('/events/', SCEVENTS_API_URL);
10-
const res = await fetch(url.href, {
11-
method: 'GET',
12-
headers: {
13-
'Content-Type': 'application/json',
14-
},
15-
});
16-
17-
if (res.ok) {
18-
const result = await res.json();
19-
status.responseData = result;
20-
} else {
8+
const res = await fetch(`${SCEVENTS_API_URL}/events/`);
9+
const result = await res.json();
10+
status.responseData = result;
11+
if (!res.ok) {
2112
status.error = true;
2213
}
2314
} catch (err) {
24-
status.error = true;
2515
status.responseData = err;
16+
status.error = true;
2617
}
27-
2818
return status;
2919
}
3020

3121
export async function getEventByID(id) {
32-
let status = new ApiResponse();
22+
const status = new ApiResponse();
23+
try {
24+
const res = await fetch(`${SCEVENTS_API_URL}/events/${id}`);
25+
const result = await res.json();
26+
status.responseData = result;
27+
if (!res.ok) {
28+
status.error = true;
29+
}
30+
} catch (err) {
31+
status.error = true;
32+
status.responseData = err;
33+
}
34+
return status;
35+
}
3336

37+
export async function createSCEvent(eventBody) {
38+
const status = new ApiResponse();
3439
try {
35-
const url = new URL(`/events/${id}`, SCEVENTS_API_URL);
36-
const res = await fetch(url.href, {
37-
method: 'GET',
40+
const res = await fetch(`${SCEVENTS_API_URL}/events/`, {
41+
method: 'POST',
3842
headers: {
3943
'Content-Type': 'application/json',
4044
},
45+
body: JSON.stringify(eventBody),
4146
});
42-
43-
if (res.ok) {
44-
const result = await res.json();
45-
status.responseData = result;
46-
} else {
47+
const body = await res.json();
48+
status.responseData = body;
49+
if (!res.ok) {
4750
status.error = true;
4851
}
4952
} catch (err) {
5053
status.error = true;
5154
status.responseData = err;
5255
}
53-
5456
return status;
5557
}

src/Components/Navbar/AdminNavbar.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import React from 'react';
22
import { useSCE } from '../context/SceContext';
3+
import config from '../../config/config.json';
4+
import { membershipState } from '../../Enums';
35

46
export default function UserNavBar(props) {
57
const { user, setAuthenticated } = useSCE();
@@ -41,6 +43,30 @@ export default function UserNavBar(props) {
4143
},
4244
];
4345

46+
const sceventsAdminNavLinks = [];
47+
if (config.SCEvents?.ENABLED) {
48+
sceventsAdminNavLinks.push({
49+
title: 'SCEvents',
50+
route: '/events',
51+
icon: (
52+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
53+
<path strokeLinecap="round" strokeLinejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5a2.25 2.25 0 0 0 2.25-2.25m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5a2.25 2.25 0 0 1 2.25 2.25v7.5" />
54+
</svg>
55+
),
56+
});
57+
if (user?.accessLevel >= membershipState.OFFICER) {
58+
sceventsAdminNavLinks.push({
59+
title: 'Create event',
60+
route: '/events/create',
61+
icon: (
62+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
63+
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
64+
</svg>
65+
),
66+
});
67+
}
68+
}
69+
4470
const adminLinks = [
4571
{
4672
title: 'User Manager',
@@ -82,6 +108,7 @@ export default function UserNavBar(props) {
82108
</svg>
83109
),
84110
},
111+
...sceventsAdminNavLinks,
85112
{
86113
title: 'Card Reader',
87114
route: '/card-reader',
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/* eslint-disable camelcase -- SCEvents registration question shape uses snake_case */
2+
import React from 'react';
3+
4+
export default function CreateEventFormQuestionBlock({
5+
question,
6+
index,
7+
onUpdateField,
8+
onChangeType,
9+
onRemove,
10+
onUpdateAnswerOption,
11+
onAddAnswerOption,
12+
onRemoveAnswerOption,
13+
}) {
14+
return (
15+
<div className="p-4 mb-3 border border-gray-200 rounded-lg shadow-sm bg-white dark:bg-gray-800 dark:border-gray-700">
16+
<div className="flex flex-col gap-3 sm:flex-row sm:items-start">
17+
<div className="flex flex-wrap flex-1 gap-2 items-center">
18+
<span className="flex justify-center items-center w-6 h-6 text-xs font-medium rounded-full bg-primary/15 text-primary">
19+
{index + 1}
20+
</span>
21+
<select
22+
value={question.type}
23+
onChange={(e) => onChangeType(question.id, e.target.value)}
24+
className="text-sm select select-bordered select-sm dark:bg-gray-700"
25+
>
26+
<option value="textbox">Text input</option>
27+
<option value="multiple_choice">Multiple choice</option>
28+
<option value="dropdown">Dropdown</option>
29+
<option value="checkbox">Checkbox</option>
30+
</select>
31+
<label className="flex gap-2 items-center text-sm cursor-pointer label">
32+
<input
33+
type="checkbox"
34+
checked={!!question.required}
35+
onChange={(e) => onUpdateField(question.id, 'required', e.target.checked)}
36+
className="checkbox checkbox-sm"
37+
/>
38+
<span className="label-text">Required</span>
39+
</label>
40+
</div>
41+
<button
42+
type="button"
43+
className="btn btn-outline btn-sm shrink-0"
44+
onClick={() => onRemove(question.id)}
45+
>
46+
Remove
47+
</button>
48+
</div>
49+
50+
<label className="w-full mt-3 form-control">
51+
<div className="label">
52+
<span className="label-text">Question text</span>
53+
</div>
54+
<input
55+
type="text"
56+
className="w-full text-sm input input-bordered sm:text-base"
57+
value={question.question}
58+
onChange={(e) => onUpdateField(question.id, 'question', e.target.value)}
59+
placeholder="Question"
60+
/>
61+
</label>
62+
63+
{question.type === 'textbox' && (
64+
<div className="flex gap-2 items-center mt-2 text-sm">
65+
<span className="text-gray-500 dark:text-gray-400">Max characters</span>
66+
<input
67+
type="number"
68+
min="1"
69+
className="w-24 input input-bordered input-sm"
70+
value={question.answer_details?.max_chars ?? ''}
71+
onChange={(e) =>
72+
onUpdateField(question.id, 'answer_details', {
73+
max_chars: e.target.value ? parseInt(e.target.value, 10) : undefined,
74+
})
75+
}
76+
placeholder="None"
77+
/>
78+
</div>
79+
)}
80+
81+
{(question.type === 'multiple_choice' || question.type === 'dropdown') && (
82+
<div className="mt-3 space-y-2">
83+
<span className="text-sm text-gray-500 dark:text-gray-400">Answer options</span>
84+
{(question.answer_options || []).map((option, optIndex) => (
85+
<div key={`${question.id}-opt-${optIndex}`} className="flex gap-2 items-center">
86+
<input
87+
type="text"
88+
className="flex-1 text-sm input input-bordered input-sm"
89+
value={option}
90+
onChange={(e) => onUpdateAnswerOption(question.id, optIndex, e.target.value)}
91+
placeholder={`Option ${optIndex + 1}`}
92+
/>
93+
{(question.answer_options || []).length > 1 && (
94+
<button
95+
type="button"
96+
className="btn btn-outline btn-sm btn-square"
97+
onClick={() => onRemoveAnswerOption(question.id, optIndex)}
98+
aria-label="Remove option"
99+
>
100+
×
101+
</button>
102+
)}
103+
</div>
104+
))}
105+
<button
106+
type="button"
107+
className="w-full btn btn-outline btn-sm"
108+
onClick={() => onAddAnswerOption(question.id)}
109+
>
110+
Add option
111+
</button>
112+
</div>
113+
)}
114+
</div>
115+
);
116+
}

0 commit comments

Comments
 (0)