Skip to content

Commit

Permalink
feat: add admin form ui for prolific studies
Browse files Browse the repository at this point in the history
Co-authored-by: Saachi Mota <saachibm@users.noreply.github.com>
  • Loading branch information
sgfost and saachibm committed Oct 18, 2024
1 parent 62cda36 commit 61b5380
Show file tree
Hide file tree
Showing 8 changed files with 424 additions and 16 deletions.
32 changes: 29 additions & 3 deletions client/src/api/study/request.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,46 @@
import { url } from "@port-of-mars/client/util";
import { ProlificParticipantStatus } from "@port-of-mars/shared/types";
import { ProlificStudyData, ProlificParticipantStatus } from "@port-of-mars/shared/types";
import { TStore } from "@port-of-mars/client/plugins/tstore";
import { AjaxRequest } from "@port-of-mars/client/plugins/ajax";

export class StudyAPI {
constructor(public store: TStore, public ajax: AjaxRequest) {}

async getProlificParticipantStatus(): Promise<ProlificParticipantStatus> {
return await this.ajax.get(url("/study/prolific/status"), ({ data }) => {
return this.ajax.get(url("/study/prolific/status"), ({ data }) => {
return data;
});
}

async completeProlificStudy(): Promise<string> {
return await this.ajax.get(url("/study/prolific/complete"), ({ data }) => {
return this.ajax.get(url("/study/prolific/complete"), ({ data }) => {
return data;
});
}

async getAllProlificStudies(): Promise<ProlificStudyData[]> {
return this.ajax.get(url("/study/prolific/studies"), ({ data }) => {
return data;
});
}

async addProlificStudy(study: ProlificStudyData): Promise<ProlificStudyData> {
return this.ajax.post(
url("/study/prolific/add"),
({ data }) => {
return data;
},
study
);
}

async updateProlificStudy(study: ProlificStudyData): Promise<ProlificStudyData> {
return this.ajax.post(
url(`/study/prolific/update/?studyId=${study.studyId}`),
({ data }) => {
return data;
},
study
);
}
}
2 changes: 2 additions & 0 deletions client/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Games from "@port-of-mars/client/views/admin/Games.vue";
import Rooms from "@port-of-mars/client/views/admin/Rooms.vue";
import Reports from "@port-of-mars/client/views/admin/Reports.vue";
import Settings from "@port-of-mars/client/views/admin/Settings.vue";
import Studies from "@port-of-mars/client/views/admin/Studies.vue";
import Login from "@port-of-mars/client/views/Login.vue";
import Leaderboard from "@port-of-mars/client/views/Leaderboard.vue";
import PlayerHistory from "@port-of-mars/client/views/PlayerHistory.vue";
Expand Down Expand Up @@ -63,6 +64,7 @@ const router = new VueRouter({
{ path: "rooms", name: "AdminRooms", component: Rooms, meta: ADMIN_META },
{ path: "reports", name: "AdminReports", component: Reports, meta: ADMIN_META },
{ path: "settings", name: "AdminSettings", component: Settings, meta: ADMIN_META },
{ path: "studies", name: "AdminStudies", component: Studies, meta: ADMIN_META },
],
},
{ ...PAGE_META[LOGIN_PAGE], component: Login },
Expand Down
3 changes: 3 additions & 0 deletions client/src/views/Admin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
<b-nav-item class="nav-link" active-class="active" to="/admin/settings">
Settings
</b-nav-item>
<b-nav-item class="nav-link" active-class="active" to="/admin/studies">
Studies
</b-nav-item>
</b-nav>
<router-view
class="h-100 backdrop"
Expand Down
2 changes: 1 addition & 1 deletion client/src/views/admin/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ import { DynamicSettingsData } from "@port-of-mars/shared/types";
import { AdminAPI } from "@port-of-mars/client/api/admin/request";
@Component({})
export default class Reports extends Vue {
export default class Settings extends Vue {
api!: AdminAPI;
form: DynamicSettingsData = {
isTournamentEnabled: false,
Expand Down
251 changes: 251 additions & 0 deletions client/src/views/admin/Studies.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
<template>
<b-container fluid class="h-100 w-100 m-0 p-0 overflow-auto">
<div class="h-100 p-3">
<b-row class="h-100 m-0">
<b-col>
<div class="mb-4" id="prolific-study">
<h4>Prolific Studies</h4>
<hr class="my-2" />
<div class="d-flex justify-content-end align-items-center mb-3">
<b-button variant="success" class="mr-2" @click="showAddStudyModal">
<h4 class="mb-0"><b-icon-plus />New Study</h4>
</b-button>
</div>
<div class="h-100-header w-100 content-container">
<b-table
dark
sticky-header
class="h-100 m-0 custom-table"
:fields="studyFields"
:items="prolificStudies"
>
<template #cell(description)="data">
{{ data.item.description || "-" }}
</template>
<template #cell(studyId)="data">
{{ data.item.studyId || "-" }}
</template>
<template #cell(completionCode)="data">
{{ data.item.completionCode || "-" }}
</template>
<template #cell(actions)="data">
<b-button
@click="showEditStudyModal(data.item)"
variant="primary"
class="mr-2"
size="sm"
>
Edit
</b-button>
<b-button @click="showBonusPaymentsModal(data.item)" size="sm"
>Bonus Payments</b-button
>
</template>
</b-table>
</div>
</div>
<b-modal
id="add-study-modal"
centered
title="Add New Study"
body-bg-variant="dark"
header-bg-variant="dark"
footer-bg-variant="dark"
okTitle="Add Study"
cancelTitle="Cancel"
@ok="addStudy"
>
<b-form>
<b-form-group label="Study ID" label-for="study-id-input">
<b-form-input
id="study-id-input"
v-model="newStudy.studyId"
required
placeholder="The ID of the study on Prolific"
></b-form-input>
</b-form-group>
<b-form-group label="Description" label-for="study-description-input">
<b-form-input
id="study-description-input"
v-model="newStudy.description"
required
placeholder="Short description of the study to disambiguate"
></b-form-input>
</b-form-group>
<b-form-group label="Completion Code" label-for="completion-code-input">
<b-form-input
id="completion-code-input"
v-model="newStudy.completionCode"
required
placeholder="The completion code from Prolific"
></b-form-input>
</b-form-group>
</b-form>
</b-modal>
<b-modal
id="edit-study-modal"
centered
:title="`Edit Study: ${selectedStudy.studyId}`"
body-bg-variant="dark"
header-bg-variant="dark"
footer-bg-variant="dark"
okTitle="Save Changes"
cancelTitle="Cancel"
@ok="saveStudyChanges"
>
<b-form>
<b-form-group label="Description" label-for="edit-study-description-input">
<b-form-input
id="study-description-input"
v-model="selectedStudy.description"
required
placeholder="Short description of the study to disambiguate"
></b-form-input>
</b-form-group>
<b-form-group label="Completion Code" label-for="edit-completion-code-input">
<b-form-input
id="completion-code-input"
v-model="selectedStudy.completionCode"
required
placeholder="The completion code from Prolific"
></b-form-input>
</b-form-group>
</b-form>
</b-modal>
<b-modal
id="bonus-payment-modal"
centered
:title="`Bonus Payment info for study: ${selectedStudy.studyId}`"
body-bg-variant="dark"
header-bg-variant="dark"
footer-bg-variant="dark"
hide-footer
>
<b-form v-if="selectedStudy.participantPoints">
<b-form-checkbox
id="show-zero-bonus-payments"
v-model="showZeroBonusPayments"
class="mb-3"
>
Show participants with $0.00 bonus payments
</b-form-checkbox>
<b-form-textarea
id="bonus-payment-description-input"
:value="selectedBonusPaymentInfo"
required
placeholder="No participants have earned bonus payments"
disabled
class="bg-dark"
style="font-family: monospace"
rows="20"
max-rows="20"
></b-form-textarea>
</b-form>
</b-modal>
</b-col>
</b-row>
</div>
</b-container>
</template>

<script lang="ts">
import { Component, Vue } from "vue-property-decorator";
import { StudyAPI } from "@port-of-mars/client/api/study/request";
import { ProlificStudyData } from "@port-of-mars/shared/types";
@Component({})
export default class Studies extends Vue {
studyApi!: StudyAPI;
prolificStudies: ProlificStudyData[] = [];
newStudy: ProlificStudyData = this.resetStudy();
selectedStudy: ProlificStudyData = this.resetStudy();
showZeroBonusPayments = false;
studyFields = [
{ key: "studyId", label: "Study ID" },
{ key: "description", label: "Description" },
{ key: "completionCode", label: "Completion Code" },
{ key: "actions", label: "" },
];
get selectedBonusPaymentInfo() {
return this.selectedStudy.participantPoints
? this.pointsToPaymentsCsv(this.selectedStudy.participantPoints)
: "";
}
async created() {
this.studyApi = new StudyAPI(this.$tstore, this.$ajax);
await this.loadProlificStudies();
}
resetStudy(): ProlificStudyData {
return {
studyId: "",
description: "",
completionCode: "",
isActive: true,
};
}
async loadProlificStudies() {
try {
this.prolificStudies = await this.studyApi.getAllProlificStudies();
console.log("Loaded studies:", this.prolificStudies);
} catch (error) {
console.error("Failed to load studies:", error);
}
}
showAddStudyModal() {
this.newStudy = this.resetStudy();
this.$bvModal.show("add-study-modal");
}
async addStudy() {
try {
await this.studyApi.addProlificStudy(this.newStudy);
this.$bvModal.hide("add-study-modal");
await this.loadProlificStudies();
this.newStudy = this.resetStudy();
} catch (error) {
console.error("Failed to add study:", error);
}
}
showEditStudyModal(study: ProlificStudyData) {
console.log({ ...study });
this.selectedStudy = { ...study };
this.$bvModal.show("edit-study-modal");
}
async saveStudyChanges() {
if (this.selectedStudy) {
try {
await this.studyApi.updateProlificStudy({ ...this.selectedStudy });
this.$bvModal.hide("edit-study-modal");
await this.loadProlificStudies();
} catch (error) {
console.error("Failed to update study:", error);
}
}
}
showBonusPaymentsModal(study: ProlificStudyData) {
this.selectedStudy = { ...study };
this.$bvModal.show("bonus-payment-modal");
}
pointsToPaymentsCsv(data: ProlificParticipantPointData[]): string {
return data
.filter(item => this.showZeroBonusPayments || item.points > 0)
.map(item => {
const dollars = (item.points / 100).toFixed(2);
return `${item.prolificId},${dollars}`;
})
.join("\n");
}
}
</script>
Loading

0 comments on commit 61b5380

Please sign in to comment.