-
Notifications
You must be signed in to change notification settings - Fork 2
refactor: activities results endpoints & clean code by SonarScan results #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
8d453fd
34d0745
c3f8ebe
0805d6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -37,29 +37,28 @@ export function OptionFormSection({ | |||||||||
| editOption, | ||||||||||
| removeOption, | ||||||||||
| resetForm, | ||||||||||
| }: OptionFormSectionProps) { | ||||||||||
| }: Readonly<OptionFormSectionProps>) { | ||||||||||
| const handleAddOrUpdate = () => { | ||||||||||
| if (!currentOption.candidate.name) { | ||||||||||
| return; | ||||||||||
| if (currentOption.candidate.name) { | ||||||||||
| addOrUpdateOption(); | ||||||||||
| } | ||||||||||
| addOrUpdateOption(); | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| const handleRemove = (index: number) => { | ||||||||||
| removeOption(index); | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| const cardTitle = | ||||||||||
| editingIndex === null | ||||||||||
| ? `新增候選人組合 #${options.length + 1}` | ||||||||||
| : `編輯候選人 #${editingIndex + 1}`; | ||||||||||
|
|
||||||||||
| return ( | ||||||||||
| <div className="space-y-6"> | ||||||||||
| {/* Current option form */} | ||||||||||
| <Card className="border-primary/20 bg-primary/5"> | ||||||||||
| <CardHeader> | ||||||||||
| <CardTitle className="text-lg"> | ||||||||||
| {editingIndex !== null | ||||||||||
| ? `編輯候選人 #${editingIndex + 1}` | ||||||||||
| : `新增候選人組合 #${options.length + 1}` | ||||||||||
| } | ||||||||||
| </CardTitle> | ||||||||||
| <CardTitle className="text-lg">{cardTitle}</CardTitle> | ||||||||||
| </CardHeader> | ||||||||||
| <CardContent className="space-y-4"> | ||||||||||
| <div className="space-y-2"> | ||||||||||
|
|
@@ -134,7 +133,12 @@ export function OptionFormSection({ | |||||||||
| 已新增的候選人 ({options.length}) | ||||||||||
| </h3> | ||||||||||
| {options.map((option, index) => ( | ||||||||||
| <Card key={index} className={editingIndex === index ? "border-primary" : ""}> | ||||||||||
| <Card | ||||||||||
| key={`${option.label}-${option.candidate.name}-${option.vice | ||||||||||
| .map((v) => v.name) | ||||||||||
| .join("-")}`} | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a key derived from editable fields like
|
||||||||||
| key={`${option.label}-${option.candidate.name}-${option.vice | |
| .map((v) => v.name) | |
| .join("-")}`} | |
| key={`option-${index}`} |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -17,12 +17,12 @@ export function ViceCandidateSection({ | |||||
| onAddVice, | ||||||
| onRemoveVice, | ||||||
| onViceChange, | ||||||
| }: ViceCandidateSectionProps) { | ||||||
| }: Readonly<ViceCandidateSectionProps>) { | ||||||
| return ( | ||||||
| <div className="space-y-3"> | ||||||
| {vices.map((vice, index) => ( | ||||||
| <div | ||||||
| key={index} | ||||||
| key={`${vice.name}-${vice.department}-${vice.college}`} | ||||||
|
||||||
| key={`${vice.name}-${vice.department}-${vice.college}`} | |
| key={`vice-${index}-${vice.name ?? ""}-${vice.department ?? ""}-${vice.college ?? ""}`} |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -12,6 +12,31 @@ | |||||||||
| import { validateDateRange, isValidRule } from "@/lib/validation"; | ||||||||||
| import { API_CONSTANTS } from "@/lib/constants"; | ||||||||||
|
|
||||||||||
| interface ActivityUpdateBody { | ||||||||||
| name?: string; | ||||||||||
| type?: string; | ||||||||||
| description?: string; | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||
| rule?: string; | ||||||||||
| open_from?: string; | ||||||||||
| open_to?: string; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| function buildActivityUpdateData(body: ActivityUpdateBody) { | ||||||||||
| const { name, type, description, rule, open_from, open_to } = body; | ||||||||||
| const updateData: Record<string, unknown> = { | ||||||||||
| updated_at: new Date(), | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| if (name) updateData.name = name; | ||||||||||
| if (type) updateData.type = type; | ||||||||||
| if (description !== undefined) updateData.description = description; | ||||||||||
| if (rule) updateData.rule = rule; | ||||||||||
| if (open_from) updateData.open_from = new Date(open_from); | ||||||||||
| if (open_to) updateData.open_to = new Date(open_to); | ||||||||||
|
|
||||||||||
| return updateData; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // GET /api/activities/[id] - Get single activity | ||||||||||
| export async function GET( | ||||||||||
| request: NextRequest, | ||||||||||
|
|
@@ -72,8 +97,18 @@ | |||||||||
| return invalidIdResponse; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const body = await request.json(); | ||||||||||
| const { name, type, description, rule, open_from, open_to } = body; | ||||||||||
| const rawBody = (await request.json()) as Record<string, unknown>; | ||||||||||
| const body: ActivityUpdateBody = { | ||||||||||
| name: typeof rawBody.name === "string" ? rawBody.name : undefined, | ||||||||||
| type: typeof rawBody.type === "string" ? rawBody.type : undefined, | ||||||||||
| description: | ||||||||||
| typeof rawBody.description === "string" ? rawBody.description : undefined, | ||||||||||
|
Comment on lines
+104
to
+105
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The manual type check for
Suggested change
|
||||||||||
| rule: typeof rawBody.rule === "string" ? rawBody.rule : undefined, | ||||||||||
| open_from: | ||||||||||
| typeof rawBody.open_from === "string" ? rawBody.open_from : undefined, | ||||||||||
| open_to: typeof rawBody.open_to === "string" ? rawBody.open_to : undefined, | ||||||||||
| }; | ||||||||||
|
Comment on lines
+100
to
+110
|
||||||||||
| const { rule, open_from, open_to } = body; | ||||||||||
|
|
||||||||||
| // Validate rule if provided | ||||||||||
| if (rule && !isValidRule(rule)) { | ||||||||||
|
|
@@ -82,25 +117,16 @@ | |||||||||
|
|
||||||||||
| // Validate dates if provided | ||||||||||
| if (open_from && open_to) { | ||||||||||
| const openFrom = new Date(open_from); | ||||||||||
| const openTo = new Date(open_to); | ||||||||||
| const openFrom = new Date(open_from as string); | ||||||||||
|
Check warning on line 120 in app/api/activities/[id]/route.ts
|
||||||||||
| const openTo = new Date(open_to as string); | ||||||||||
|
Check warning on line 121 in app/api/activities/[id]/route.ts
|
||||||||||
|
|
||||||||||
| const dateValidation = validateDateRange(openFrom, openTo); | ||||||||||
| if (!dateValidation.valid) { | ||||||||||
| return createErrorResponse(dateValidation.error!); | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const updateData: Record<string, unknown> = { | ||||||||||
| updated_at: new Date(), | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| if (name) updateData.name = name; | ||||||||||
| if (type) updateData.type = type; | ||||||||||
| if (description !== undefined) updateData.description = description; | ||||||||||
| if (rule) updateData.rule = rule; | ||||||||||
| if (open_from) updateData.open_from = new Date(open_from); | ||||||||||
| if (open_to) updateData.open_to = new Date(open_to); | ||||||||||
| const updateData = buildActivityUpdateData(body); | ||||||||||
|
|
||||||||||
| const activity = await Activity.findByIdAndUpdate(id, updateData, { | ||||||||||
| new: true, | ||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The vice list item
keyis derived from vice fields that can be missing or duplicated. Duplicate keys can cause incorrect rendering/state reuse. Consider includingviceIndex(or another stable unique identifier) in the key.