Skip to content
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

Update flutter data model + new admin features #214

Merged
merged 33 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
a285a1a
implement achievements tracking logic
jasmineee-li Apr 28, 2024
4ba5767
Merge branch 'master' into jxl9/achievements-tracking
neketka Apr 28, 2024
47ce5db
Fix achievement service import
neketka Apr 28, 2024
8a509c0
Add cascading delete to achievement tracker
neketka Apr 28, 2024
e82d43b
implement event lists from org
neketka Apr 29, 2024
f41dccd
Made achievement admin frontend
neketka Apr 29, 2024
d781b21
Bug fixes
neketka Apr 29, 2024
22864a3
fixed bugs, still need to pass tests
jasmineee-li Apr 29, 2024
c6b4a9d
Merge branch 'jxl9/achievements-tracking' into nikita/admin-and-data-…
neketka Apr 29, 2024
c89abec
Fix event trackers
neketka Apr 29, 2024
92680c3
Fix flutter build
neketka Apr 29, 2024
071bb8c
Finish achievement tracking
neketka Apr 29, 2024
d920c37
Fix emit order
neketka Apr 29, 2024
5ec6cb5
Fix other emit error
neketka Apr 29, 2024
f31fab2
Fix build errors
neketka Apr 30, 2024
797a559
Connected achievements to backend
neketka Apr 30, 2024
7e4b9fe
Fix inconsistencies
neketka Apr 30, 2024
2ab00ee
Complete integration
neketka Apr 30, 2024
49524af
Remove hardcoded complete
neketka Apr 30, 2024
351156f
Comment failing tests
neketka Apr 30, 2024
7b12a81
Attempt to fix tests
neketka Apr 30, 2024
3cf8e31
Fix more e2e failures
neketka Apr 30, 2024
9518e6a
Add exception for e2e tests in client service
neketka Apr 30, 2024
796f15f
Added extra logging
neketka Apr 30, 2024
11cdb0c
Fix incorrect nextChallenge
neketka Apr 30, 2024
51eeb31
Fix tests
neketka Apr 30, 2024
8782785
Fix nulls in test
neketka Apr 30, 2024
65e9796
Set progress limit
neketka Apr 30, 2024
dc21d2c
Merge branch 'master' into nikita/admin-and-data-fixes
neketka Apr 30, 2024
dbd0f40
Add manager rule for achievements
neketka Apr 30, 2024
a2c8538
Achivement trackers are made for all
neketka Apr 30, 2024
9ea5d39
Clarified prisma query
neketka Apr 30, 2024
4b4e1a8
Fix underflow error + journeys
neketka May 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions admin/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { ServerConnectionContext } from "./components/ServerConnection";
import { ServerDataContext } from "./components/ServerData";
import { AlertModal } from "./components/AlertModal";
import { Groups } from "./components/Groups";
import { Achievements } from "./components/Achievements";

const routes = [
{
Expand All @@ -64,6 +65,12 @@ const routes = [
icon: faLocationDot,
name: "Challenges",
},
{
path: "/achievements",
element: <Achievements />,
icon: faTrophy,
name: "Achievements",
},
{
path: "/users",
element: <Users />,
Expand Down
13 changes: 8 additions & 5 deletions admin/src/all.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ export interface RequestAchievementDataDto {
achievements: string[];
}

export interface RequestAchievementTrackerDataDto {
achievements?: string[];
}

export interface LoginDto {
idToken: string;
noRegister: boolean;
Expand All @@ -118,9 +122,7 @@ export interface RefreshTokenDto {
refreshToken: string;
}

export interface CompletedChallengeDto {
challengeId: string;
}
export interface CompletedChallengeDto {}

export interface ChallengeDto {
id: string;
Expand Down Expand Up @@ -227,7 +229,7 @@ export interface EventTrackerDto {
eventId: string;
isRanked: boolean;
hintsUsed: number;
curChallengeId: string;
curChallengeId?: string;
prevChallenges: PrevChallengeDto[];
}

Expand Down Expand Up @@ -258,7 +260,7 @@ export interface GroupMemberDto {
id: string;
name: string;
points: number;
curChallengeId: string;
curChallengeId?: string;
}

export interface GroupDto {
Expand Down Expand Up @@ -290,6 +292,7 @@ export interface OrganizationDto {
members?: string[];
events?: string[];
managers?: string[];
achivements?: string[];
}

export interface RequestOrganizationDataDto {
Expand Down
287 changes: 287 additions & 0 deletions admin/src/components/Achievements.tsx
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);
}}
/>
))}
</>
);
}
4 changes: 4 additions & 0 deletions admin/src/components/ServerApi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ export class ServerApi {
this.send("requestAchievementData", data);
}

requestAchievementTrackerData(data: dto.RequestAchievementTrackerDataDto) {
this.send("requestAchievementTrackerData", data);
}

updateAchievementData(data: dto.UpdateAchievementDataDto) {
this.send("updateAchievementData", data);
}
Expand Down
Loading
Loading