-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update flutter data model + new admin features (#214)
* implement achievements tracking logic * Fix achievement service import * Add cascading delete to achievement tracker * implement event lists from org * Made achievement admin frontend * Bug fixes * fixed bugs, still need to pass tests * Fix event trackers * Fix flutter build * Finish achievement tracking * Fix emit order * Fix other emit error * Fix build errors * Connected achievements to backend * Fix inconsistencies * Complete integration * Remove hardcoded complete * Comment failing tests * Attempt to fix tests * Fix more e2e failures * Add exception for e2e tests in client service * Added extra logging * Fix incorrect nextChallenge * Fix tests * Fix nulls in test * Set progress limit * Add manager rule for achievements * Achivement trackers are made for all * Clarified prisma query * Fix underflow error + journeys --------- Co-authored-by: Jasmine Li <[email protected]>
- Loading branch information
1 parent
7d306cf
commit 2fc02e6
Showing
43 changed files
with
1,128 additions
and
471 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,287 @@ | ||
import { useContext, useMemo, useState } from "react"; | ||
import { DeleteModal } from "./DeleteModal"; | ||
import { | ||
EntryModal, | ||
EntryForm, | ||
NumberEntryForm, | ||
OptionEntryForm, | ||
FreeEntryForm, | ||
DateEntryForm, | ||
} from "./EntryModal"; | ||
import { HButton } from "./HButton"; | ||
import { | ||
ButtonSizer, | ||
CenterText, | ||
ListCardBody, | ||
ListCardBox, | ||
ListCardButtons, | ||
ListCardDescription, | ||
ListCardTitle, | ||
} from "./ListCard"; | ||
import { SearchBar } from "./SearchBar"; | ||
import { ServerDataContext } from "./ServerData"; | ||
|
||
import { compareTwoStrings } from "string-similarity"; | ||
import { | ||
AchievementDto, | ||
AchievementTypeDto, | ||
ChallengeLocationDto, | ||
} from "../all.dto"; | ||
import { AlertModal } from "./AlertModal"; | ||
|
||
const locationOptions = [ | ||
ChallengeLocationDto.ENG_QUAD, | ||
ChallengeLocationDto.ARTS_QUAD, | ||
ChallengeLocationDto.AG_QUAD, | ||
ChallengeLocationDto.NORTH_CAMPUS, | ||
ChallengeLocationDto.WEST_CAMPUS, | ||
ChallengeLocationDto.COLLEGETOWN, | ||
ChallengeLocationDto.ITHACA_COMMONS, | ||
ChallengeLocationDto.ANY, | ||
]; | ||
|
||
const achievementOptions = [ | ||
AchievementTypeDto.TOTAL_CHALLENGES, | ||
AchievementTypeDto.TOTAL_CHALLENGES_OR_JOURNEYS, | ||
AchievementTypeDto.TOTAL_JOURNEYS, | ||
AchievementTypeDto.TOTAL_POINTS, | ||
]; | ||
|
||
function AchiemementCard(props: { | ||
achievement: AchievementDto; | ||
onSelect: () => void; | ||
onEdit: () => void; | ||
onDelete: () => void; | ||
}) { | ||
return ( | ||
<> | ||
<ListCardBox> | ||
<ListCardTitle> | ||
{props.achievement.name} | ||
<ButtonSizer> | ||
<HButton onClick={props.onSelect} float="right"> | ||
{props.achievement.eventId ? "UNLINK EVENT" : "LINK EVENT"} | ||
</HButton> | ||
</ButtonSizer> | ||
</ListCardTitle> | ||
<ListCardDescription> | ||
{props.achievement.description} | ||
</ListCardDescription> | ||
<ListCardBody> | ||
Id: <b>{props.achievement.id}</b> <br /> | ||
Required Points/Event Completions:{" "} | ||
<b>{props.achievement.requiredPoints}</b> <br /> | ||
Linked Event ID: <b>{props.achievement.eventId ?? "NONE"}</b> <br /> | ||
Location Type: <b>{props.achievement.locationType}</b> <br /> | ||
Achievement Type: <b>{props.achievement.achievementType}</b> <br /> | ||
</ListCardBody> | ||
<ListCardButtons> | ||
<HButton onClick={props.onDelete}>DELETE</HButton> | ||
<HButton onClick={props.onEdit} float="right"> | ||
EDIT | ||
</HButton> | ||
</ListCardButtons> | ||
</ListCardBox> | ||
</> | ||
); | ||
} | ||
|
||
// Default Form Creation | ||
function makeForm() { | ||
return [ | ||
{ name: "Name", characterLimit: 256, value: "" }, | ||
{ name: "Description", characterLimit: 2048, value: "" }, | ||
{ | ||
name: "Location Type", | ||
options: locationOptions as string[], | ||
value: 0, | ||
}, | ||
{ | ||
name: "Achievement Type", | ||
options: achievementOptions as string[], | ||
value: 0, | ||
}, | ||
{ name: "Required Points", value: 1, min: 1, max: 999 }, | ||
] as EntryForm[]; | ||
} | ||
|
||
// Form to DTO Conversion | ||
function fromForm(form: EntryForm[], id: string): AchievementDto { | ||
return { | ||
id, | ||
imageUrl: "", | ||
name: (form[0] as FreeEntryForm).value, | ||
description: (form[1] as FreeEntryForm).value, | ||
locationType: locationOptions[(form[2] as OptionEntryForm).value], | ||
achievementType: achievementOptions[(form[3] as OptionEntryForm).value], | ||
requiredPoints: (form[4] as NumberEntryForm).value, | ||
}; | ||
} | ||
|
||
// DTO to Form Conversion | ||
function toForm(achievement: AchievementDto) { | ||
return [ | ||
{ name: "Name", characterLimit: 256, value: achievement.name! }, | ||
{ | ||
name: "Description", | ||
characterLimit: 2048, | ||
value: achievement.description!, | ||
}, | ||
{ | ||
name: "Location Type", | ||
options: locationOptions as string[], | ||
value: locationOptions.indexOf(achievement.locationType!), | ||
}, | ||
{ | ||
name: "Achievement Type", | ||
options: achievementOptions as string[], | ||
value: achievementOptions.indexOf(achievement.achievementType!), | ||
}, | ||
{ | ||
name: "Required Points", | ||
value: achievement.requiredPoints!, | ||
min: 1, | ||
max: 999, | ||
}, | ||
] as EntryForm[]; | ||
} | ||
|
||
export function Achievements() { | ||
const serverData = useContext(ServerDataContext); | ||
const [isCreateModalOpen, setCreateModalOpen] = useState(false); | ||
const [isEditModalOpen, setEditModalOpen] = useState(false); | ||
const [isDeleteModalOpen, setDeleteModalOpen] = useState(false); | ||
const [selectModalOpen, setSelectModalOpen] = useState(false); | ||
const [isLinkedModalOpen, setLinkedModalOpen] = useState(false); | ||
const [form, setForm] = useState(() => makeForm()); | ||
const [currentId, setCurrentId] = useState(""); | ||
const [query, setQuery] = useState(""); | ||
const selectedOrg = serverData.organizations.get(serverData.selectedOrg); | ||
|
||
return ( | ||
<> | ||
<AlertModal | ||
description="To create an achievement, select an organization." | ||
isOpen={selectModalOpen} | ||
onClose={() => setSelectModalOpen(false)} | ||
/> | ||
<AlertModal | ||
description="To link an achievement to an event, select an event." | ||
isOpen={isLinkedModalOpen} | ||
onClose={() => setLinkedModalOpen(false)} | ||
/> | ||
<EntryModal | ||
title="Create Achievement" | ||
isOpen={isCreateModalOpen} | ||
entryButtonText="CREATE" | ||
onEntry={() => { | ||
serverData.updateAchievement({ | ||
...fromForm(form, ""), | ||
initialOrganizationId: serverData.selectedOrg, | ||
}); | ||
setCreateModalOpen(false); | ||
}} | ||
onCancel={() => { | ||
setCreateModalOpen(false); | ||
}} | ||
form={form} | ||
/> | ||
<EntryModal | ||
title="Edit Event" | ||
isOpen={isEditModalOpen} | ||
entryButtonText="EDIT" | ||
onEntry={() => { | ||
const oldAchievement = serverData.achievements.get(currentId)!; | ||
serverData.updateAchievement({ | ||
...oldAchievement, | ||
...fromForm(form, currentId), | ||
}); | ||
setEditModalOpen(false); | ||
}} | ||
onCancel={() => { | ||
setEditModalOpen(false); | ||
}} | ||
form={form} | ||
/> | ||
<DeleteModal | ||
objectName={serverData.achievements.get(currentId)?.name ?? ""} | ||
isOpen={isDeleteModalOpen} | ||
onClose={() => setDeleteModalOpen(false)} | ||
onDelete={() => { | ||
serverData.deleteAchievement(currentId); | ||
setDeleteModalOpen(false); | ||
}} | ||
/> | ||
<SearchBar | ||
onCreate={() => { | ||
if (!selectedOrg) { | ||
setSelectModalOpen(true); | ||
return; | ||
} | ||
setForm(makeForm()); | ||
setCreateModalOpen(true); | ||
}} | ||
onSearch={(query) => setQuery(query)} | ||
/> | ||
{serverData.selectedOrg === "" ? ( | ||
<CenterText>Select an organization to view achievements</CenterText> | ||
) : serverData.organizations.get(serverData.selectedOrg) ? ( | ||
serverData.organizations?.get(serverData.selectedOrg)?.achivements | ||
?.length === 0 && ( | ||
<CenterText>No achievements in organization</CenterText> | ||
) | ||
) : ( | ||
<CenterText>Error getting achievements</CenterText> | ||
)} | ||
{Array.from<AchievementDto>( | ||
serverData.organizations | ||
.get(serverData.selectedOrg) | ||
?.achivements?.map( | ||
(achId: string) => serverData.achievements.get(achId)! | ||
) | ||
.filter((ach?: AchievementDto) => !!ach) ?? [] | ||
) | ||
.sort( | ||
(a: AchievementDto, b: AchievementDto) => | ||
compareTwoStrings(b.name ?? "", query) - | ||
compareTwoStrings(a.name ?? "", query) + | ||
compareTwoStrings(b.description ?? "", query) - | ||
compareTwoStrings(a.description ?? "", query) | ||
) | ||
.map((ach) => ( | ||
<AchiemementCard | ||
key={ach.id} | ||
achievement={ach} | ||
onSelect={() => { | ||
if (ach.eventId) { | ||
serverData.updateAchievement({ | ||
...ach, | ||
eventId: "", | ||
}); | ||
|
||
return; | ||
} | ||
|
||
if (serverData.selectedEvent === "") { | ||
setLinkedModalOpen(true); | ||
} else { | ||
serverData.updateAchievement({ | ||
...ach, | ||
eventId: serverData.selectedEvent, | ||
}); | ||
} | ||
}} | ||
onDelete={() => { | ||
setCurrentId(ach.id); | ||
setDeleteModalOpen(true); | ||
}} | ||
onEdit={() => { | ||
setCurrentId(ach.id); | ||
setForm(toForm(ach)); | ||
setEditModalOpen(true); | ||
}} | ||
/> | ||
))} | ||
</> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.