Skip to content

Commit 2cb82ad

Browse files
committed
[Admin] finish new admin panel
1 parent 6a29f24 commit 2cb82ad

File tree

14 files changed

+765
-122
lines changed

14 files changed

+765
-122
lines changed

src/components/admin-new/SidebarPanel.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
adminContent
88
} from './adminConfig';
99
import {
10+
useRoute,
1011
useRouter
1112
} from 'vue-router';
1213
1314
const router = useRouter();
14-
const selectedOption: Ref<number> = ref( -1 );
15+
const route = useRoute();
1516
16-
const selectOption = ( id: number, route: string ) => {
17-
selectedOption.value = id;
17+
const selectOption = ( route: string ) => {
1818
router.push( adminBaseRoute + route );
1919
};
2020
</script>
@@ -33,9 +33,9 @@
3333
<p
3434
v-for="option in section.content"
3535
:key="option.id"
36-
:class="selectedOption === option.id ? 'selected' : undefined"
36+
:class="route.path.includes( option.route ) ? 'selected' : undefined"
3737
class="option"
38-
@click="selectOption( option.id, option.route )"
38+
@click="selectOption( option.route )"
3939
>
4040
{{ option.text }}
4141
</p>

src/components/admin-new/SurveyManager.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import SurveyBrowser from './surveys/SurveyBrowser.vue';
33
import SurveyCreator from './surveys/SurveyCreator.vue';
4+
import SurveyMagicLinks from './surveys/SurveyMagicLinks.vue';
45
import SurveyProperties from './surveys/SurveyProperties.vue';
56
import {
67
useRoute
@@ -11,6 +12,7 @@
1112

1213
<template>
1314
<SurveyBrowser />
14-
<SurveyProperties v-if="!route.path.endsWith( 'create-survey' )" />
15-
<SurveyCreator v-else />
15+
<SurveyCreator v-if="route.path.endsWith( 'create-survey' )" />
16+
<SurveyMagicLinks v-else-if="route.path.endsWith( 'magiclinks' )" />
17+
<SurveyProperties v-else />
1618
</template>
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
<script setup lang="ts">
2+
import { useRoute } from 'vue-router';
23
import TextBrowser from './texts/TextBrowser.vue';
34
import TextUpload from './texts/TextUpload.vue';
5+
import TextUploadOptions from './texts/TextUploadOptions.vue';
6+
7+
const route = useRoute();
48
</script>
59

610
<template>
711
<TextBrowser />
8-
<TextUpload />
12+
<TextUploadOptions v-if="route.path.endsWith( 'options' )" />
13+
<TextUpload v-else />
914
</template>

src/components/admin-new/surveys/SurveyBrowser.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@
5858
loading.value = false;
5959
};
6060
61-
const selectSurvey = ( surveyId: number ) => {
62-
surveyStore.setSurveyID( surveyId );
63-
router.push( adminBaseRoute + 'surveys' );
61+
const selectSurvey = ( surveyIndex: number ) => {
62+
surveyStore.setSurveyIndex( surveyIndex );
63+
router.push( adminBaseRoute + 'surveys/' + String( surveyStore.getSelectedSurveyID ) );
6464
};
6565
6666
const addSurvey = () => {
@@ -101,7 +101,7 @@
101101
<tr
102102
v-for="survey, index in surveyStore.surveys"
103103
:key="survey.id"
104-
:class="index === surveyStore.selectedSurveyID ? 'selected' : ''"
104+
:class="index === surveyStore.selectedSurveyIndex ? 'selected' : ''"
105105
@click="selectSurvey( index )"
106106
>
107107
<td class="survey-name">

src/components/admin-new/surveys/SurveyCreator.vue

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import {
2121
useSurveyStore
2222
} from '@/ts/stores/admin';
23+
import { useRouter } from 'vue-router';
24+
import { adminBaseRoute } from '../adminConfig';
2325
2426
interface Text {
2527
'sessions': ShallowReadingSessionDto[];
@@ -28,6 +30,7 @@
2830
'selected': boolean[];
2931
}
3032
33+
const router = useRouter();
3134
const notifications = useNotification();
3235
const surveyStore = useSurveyStore();
3336
const status = useStatusStore();
@@ -36,11 +39,11 @@
3639
const userCount = ref( null );
3740
3841
const dismiss = () => {
39-
surveyStore.selectedSurveyID = -2;
42+
surveyStore.selectedSurveyIndex = -2;
4043
};
4144
4245
const selectText = ( index: number ) => {
43-
surveyStore.setTextID( index );
46+
surveyStore.setTextIndex( index );
4447
};
4548
4649
const loadTexts = async () => {
@@ -125,9 +128,8 @@
125128
surveyStore.texts.map( val => val.sessions.map( val => val.id! ).filter( ( _v, idx ) => val.selected[ idx ] ) ).flat()
126129
).then( links => {
127130
surveyStore.setLinks( links );
131+
router.push( adminBaseRoute + '/magiclinks' );
128132
} );
129-
130-
// TODO set SurveyStore.selectedID to the ID of the survey just created
131133
};
132134
133135
const useTestData = () => {
@@ -142,6 +144,7 @@
142144
} );
143145
};
144146
147+
surveyStore.unselectSurvey();
145148
loadTexts();
146149
</script>
147150

@@ -203,7 +206,7 @@
203206
<tr
204207
v-for="text, index in surveyStore.texts"
205208
:key="index"
206-
:class="index === surveyStore.selectedTextID ? 'selected' : ''"
209+
:class="index === surveyStore.selectedTextIndex ? 'selected' : ''"
207210
>
208211
<td
209212
class="left-td"
@@ -224,7 +227,7 @@
224227
Please upload a text
225228
</div>
226229

227-
<div v-if="surveyStore.selectedTextID !== -1" class="right-table-wrapper">
230+
<div v-if="surveyStore.selectedTextIndex !== -1" class="right-table-wrapper">
228231
<table>
229232
<thead>
230233
<tr>
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
<script setup lang="ts">
2+
import {
3+
useNotification
4+
} from '@kyvg/vue3-notification';
5+
import {
6+
useStatusStore
7+
} from '@/ts/stores/status';
8+
import {
9+
useSurveyStore
10+
} from '@/ts/stores/admin';
11+
import {
12+
deleteSurvey, exportSurvey
13+
} from '@/ts/surveys';
14+
import { useRoute } from 'vue-router';
15+
import { watch } from 'vue';
16+
17+
// TODO: Download magic links from server to display (if this is supported)
18+
// --> Likely won't be due to bcrypt or the like being used for passwords
19+
20+
const route = useRoute();
21+
const surveyStore = useSurveyStore();
22+
const maxSurveyLength = 50;
23+
const notifications = useNotification();
24+
const status = useStatusStore();
25+
26+
const copyLinkToClipboard = ( linkStr: string ) => {
27+
navigator.clipboard.writeText( linkStr );
28+
notifications.notify( {
29+
'text': 'Copied magic link to clipboard',
30+
'type': 'success',
31+
'title': 'Copied'
32+
} );
33+
};
34+
35+
const downloadMagicLinks = () => {
36+
const textContent = surveyStore.links.join( '\n' );
37+
const blob = new Blob(
38+
[ textContent ],
39+
{
40+
'type': 'text/plain'
41+
}
42+
);
43+
const url = URL.createObjectURL( blob );
44+
const a: HTMLAnchorElement = document.getElementById( 'linkDownloadAnchor' )! as HTMLAnchorElement;
45+
46+
a.href = url;
47+
a.download = 'magicLinks.txt';
48+
a.click();
49+
URL.revokeObjectURL( url );
50+
};
51+
52+
const truncate = ( text: string, limit: number ) => {
53+
if ( text.length < limit ) return text;
54+
else return text.slice( 0, limit - 3 ) + '...';
55+
};
56+
57+
const useTestData = () => {
58+
let links = [];
59+
60+
for ( let i = 0; i < 20; i++ )
61+
links.push( 'link' + String( i ) );
62+
63+
links.push( 'linkWhichIsVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryVeryLong' );
64+
surveyStore.setLinks( links );
65+
66+
notifications.notify( {
67+
'text': 'Populated link list using testing data for frontend dev.',
68+
'type': 'warn',
69+
'title': 'Loaded Testing Data'
70+
} );
71+
};
72+
73+
if ( status.devMode ) useTestData();
74+
</script>
75+
76+
<template>
77+
<div class="magic-links">
78+
<div class="top-bar">
79+
<h2 class="title">
80+
Magic Links
81+
</h2>
82+
</div>
83+
<div class="content">
84+
<p class="warn-text">
85+
<strong>WARNING</strong> Magic Links are only shown once after creation.
86+
</p>
87+
88+
<div
89+
v-if="surveyStore.links.length > 0"
90+
class="link-table"
91+
>
92+
<table>
93+
<tbody>
94+
<tr
95+
v-for="link, index in surveyStore.links"
96+
:key="index"
97+
@click="copyLinkToClipboard( link )"
98+
>
99+
<td>
100+
<i class="fa-lg fa-regular fa-copy copy-icon"></i>
101+
{{ truncate(link, 50) }}
102+
</td>
103+
</tr>
104+
</tbody>
105+
</table>
106+
</div>
107+
<div
108+
v-else
109+
class="placeholder"
110+
>
111+
<p>Select a Survey to view properties</p>
112+
</div>
113+
<a id="linkDownloadAnchor">
114+
<button
115+
class="button primary"
116+
:class="surveyStore.links.length > 0 ? 'undefined' : 'disabled'"
117+
@click="surveyStore.links.length > 0 ? downloadMagicLinks() : 'undefined'"
118+
>
119+
Download Links
120+
</button>
121+
</a>
122+
</div>
123+
</div>
124+
</template>
125+
126+
<style scoped lang="scss">
127+
@use '@/scss/admin/general';
128+
@use '@/scss/admin/top-bar';
129+
130+
.magic-links {
131+
// Mainly for wide displays
132+
.top-bar {
133+
justify-content: left;
134+
}
135+
136+
.warn-text {
137+
font-size: 1rem;
138+
color: var(--theme-bg-3-20);
139+
margin: 1rem;
140+
}
141+
142+
strong {
143+
color: var(--theme-warning);
144+
background-color: var(--theme-bg-1-shade);
145+
padding: 0.7rem;
146+
border-radius: 10px;
147+
}
148+
149+
>div.content {
150+
overflow-y: auto;
151+
scrollbar-color: var( --theme-interactable-text ) var( --theme-bg-3 );
152+
max-height: 70vh;
153+
154+
>div.link-table {
155+
margin-left: 1rem;
156+
width: max(200px, 35vw);
157+
height: max(200px, 50vh);
158+
159+
overflow-y: auto;
160+
scrollbar-color: var( --theme-interactable-text ) var( --theme-bg-3 );
161+
}
162+
163+
>a>button {
164+
margin-left: 1rem;
165+
margin-top: 2rem;
166+
}
167+
}
168+
}
169+
</style>

0 commit comments

Comments
 (0)