Skip to content

Commit f3bf545

Browse files
committed
QUIZ-195: ui for flashcard feature
1 parent 78120a0 commit f3bf545

File tree

10 files changed

+322
-0
lines changed

10 files changed

+322
-0
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<script lang="ts" setup>
2+
import Textarea from '@/components/ui/textarea/Textarea.vue'
3+
import { Card } from '@/components/ui/card'
4+
5+
const props = defineProps<{
6+
number: string
7+
term: string
8+
description: string
9+
}>()
10+
11+
const { number, term, description } = toRefs(props)
12+
</script>
13+
14+
<template>
15+
<Card class="border border-[#edeff4] bg-white rounded-xl">
16+
<div class="flex items-center justify-between p-5">
17+
<p class="font-medium text-xl text-slate-700">{{ number }}</p>
18+
<div
19+
class="group flex items-center rounded-full hover:border-red-400 justify-center w-8 h-8 border border-[#edeff4] cursor-pointer"
20+
>
21+
<span
22+
class="i-material-symbols-light-delete-outline cursor-pointer text-xl text-slate-500 group-hover:text-red-500"
23+
></span>
24+
</div>
25+
</div>
26+
<div class="line w-full border border-[#f6f7fb]"></div>
27+
<div class="flex items-start justify-between gap-5 px-5 py-8">
28+
<div class="grid w-full gap-1.5">
29+
<Textarea
30+
id="term"
31+
ref="textAreaRef"
32+
v-model="term"
33+
placeholder="Enter your term"
34+
/>
35+
36+
<Label
37+
class="text-[12px]"
38+
for="term"
39+
>TERM</Label
40+
>
41+
</div>
42+
43+
<div class="grid w-full gap-1.5">
44+
<Textarea
45+
ref="textAreaRef"
46+
v-model="description"
47+
placeholder="Enter your description"
48+
/>
49+
<Label
50+
class="text-[12px]"
51+
for="term"
52+
>DESCRIPTION</Label
53+
>
54+
</div>
55+
56+
<div
57+
class="cursor-pointer items-center justify-center flex p-3 rounded-lg border-dashed border-[##d9dde8] border-2 w-[150px] h-[80px]"
58+
>
59+
<span
60+
class="i-material-symbols-add-photo-alternate-outline-rounded text-base text-red"
61+
></span>
62+
</div>
63+
</div>
64+
</Card>
65+
</template>

src/components/layout/sidebar/SidebarMenu.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ const menus = ref<Menu[]>([
5151
title: 'Report',
5252
link: '/reports',
5353
},
54+
{
55+
class: 'i-solar-document-add-bold',
56+
title: 'FlashCard',
57+
link: '/flash-card',
58+
},
5459
],
5560
},
5661
{

src/pages/flash-card/card/setCard.vue

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<script lang="ts" setup>
2+
import cardItem from '@/components/flash-card/card/cardItem.vue'
3+
import Textarea from '@/components/ui/textarea/Textarea.vue'
4+
import InputValidation from '@/components/base/InputValidation.vue'
5+
import type { ICard } from '@/types/flashCard'
6+
7+
import { useForm } from 'vee-validate'
8+
import * as yup from 'yup'
9+
10+
const description = ref<string>('')
11+
// const listCards = ref<ICard>([])
12+
13+
// const { handleSubmit } = useForm({
14+
// validationSchema: yup.object({
15+
// title: yup.string().required('Name is required'),
16+
// }),
17+
// })
18+
19+
// const onSubmit = handleSubmit(async (values) => {
20+
// const data = {
21+
// name: values.name,
22+
// description: values.description,
23+
// }
24+
// })
25+
26+
// const handleAddCard = () => {
27+
// listCards.value.push({
28+
// term: '',
29+
// description: '',
30+
// })
31+
// }
32+
</script>
33+
34+
<template>
35+
<div class="p-5 flex flex-col gap-16">
36+
<div class="flex flex-col gap-6">
37+
<h1 class="text-base font-semibold">Create a new flashcard set</h1>
38+
<div class="flex flex-col gap-1">
39+
<div class="form-data">
40+
<label for="tittle">Title</label>
41+
<InputValidation
42+
id="tittle"
43+
placeholder="Enter a tittle for your flashcard"
44+
type="text"
45+
name="tittle"
46+
class="h-12 mt-1 bg-slate-50 border-slate-200 outline-none"
47+
/>
48+
</div>
49+
<div class="grid w-full gap-1.5">
50+
<Textarea
51+
ref="textAreaRef"
52+
v-model="description"
53+
class="min-w-[100px]"
54+
placeholder="Add a description..."
55+
/>
56+
</div>
57+
</div>
58+
</div>
59+
<card-item
60+
:number="'1'"
61+
:term="'Tell me the truth'"
62+
:description="'Hay noi cho toi su that'"
63+
/>
64+
</div>
65+
</template>

src/pages/flash-card/index.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script lang="ts" setup></script>
2+
3+
<template>
4+
<div>Welcome to flash-card</div>
5+
</template>

src/routers/modules/flashCard.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { RouteRecordRaw } from 'vue-router'
2+
3+
export const flashCardRoute: RouteRecordRaw[] = [
4+
{
5+
path: '',
6+
name: 'flash-card',
7+
component: () => import('@/pages/flash-card/index.vue'),
8+
},
9+
{
10+
path: '/set-card',
11+
name: 'set-card',
12+
component: () => import('@/pages/flash-card/card/setCard.vue'),
13+
},
14+
]

src/routers/modules/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export * from './play'
77
export * from './group'
88
export * from './report'
99
export * from './plan'
10+
export * from './flashCard'

src/routers/router.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
groupRoute,
1010
reportRoute,
1111
planRoute,
12+
flashCardRoute,
1213
} from './modules'
1314
import { authGuard } from './auth-guard'
1415
const { progress } = useIndicator()
@@ -73,6 +74,11 @@ const routes: RouteRecordRaw[] = [
7374
beforeEnter: [authGuard],
7475
children: reportRoute,
7576
},
77+
{
78+
path: '/flash-card',
79+
beforeEnter: [authGuard],
80+
children: flashCardRoute,
81+
},
7682
{
7783
path: '/billing-plan',
7884
beforeEnter: [authGuard],

src/services/flashCard.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { BaseResponse } from '@/types/api'
2+
import type { IGroup, IPost } from '@/types/group'
3+
4+
interface GroupInfo {
5+
name: string
6+
description: string
7+
background: string
8+
}
9+
10+
export const getCardsApi = async ({ page = 1, keyword = '' }): Promise<BaseResponse<IGroup[]>> => {
11+
return $api('/cards', {
12+
method: 'GET',
13+
params: {
14+
page,
15+
keywords: keyword,
16+
},
17+
})
18+
}
19+
20+
export const createCardApi = async (payload: GroupInfo): Promise<BaseResponse<IGroup>> => {
21+
return $api('/cards', {
22+
method: 'POST',
23+
body: payload,
24+
})
25+
}
26+
27+
export const getCardDetailApi = async (idGroup: string): Promise<BaseResponse<IPost>> => {
28+
return $api(`/groups/${idGroup}/posts`, {
29+
method: 'GET',
30+
})
31+
}
32+
33+
export const deleteCardApi = async (id: string): Promise<BaseResponse<IGroup>> => {
34+
return $api(`/cards/${id}`, {
35+
method: 'DELETE',
36+
})
37+
}
38+
39+
//folder
40+
41+
export const getFoldersApi = async (page = 1, idGroup: string): Promise<BaseResponse<IPost[]>> => {
42+
return $api(`/groups/${idGroup}/posts`, {
43+
method: 'GET',
44+
params: { page, order: 'DESC' },
45+
})
46+
}
47+
48+
export const getFolderDetailApi = async (
49+
idGroup: string,
50+
idPost: string,
51+
): Promise<BaseResponse<IPost>> => {
52+
return $api(`/groups/${idGroup}/posts/${idPost}`, {
53+
method: 'GET',
54+
})
55+
}
56+
57+
export const deleteFolderApi = async (idPost: string): Promise<BaseResponse<IPost>> => {
58+
return $api(`/posts/${idPost}`, {
59+
method: 'DELETE',
60+
})
61+
}

src/stores/flashCard/card.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { createCardApi, getCardsApi, getCardDetailApi, deleteCardApi } from '@/services/flashCard'
2+
import type { IGroup, IGroupCreate, IMemberGroup, IDetailGroup } from '@/types/group'
3+
import { showToast } from '@/utils/toast'
4+
import { defineStore } from 'pinia'
5+
import { apiError } from '@/utils/exceptionHandler'
6+
import type { IPaging } from '@/types'
7+
8+
export const useCardStore = defineStore({
9+
id: 'card',
10+
state: () => ({
11+
groupInfo: {} as IDetailGroup,
12+
isUpdating: false,
13+
groups: [] as IGroup[],
14+
groupMeta: null as IPaging | null,
15+
listMembers: [] as IMemberGroup[],
16+
isFetching: false,
17+
}),
18+
actions: {
19+
setIsUpdating(val: boolean) {
20+
this.isUpdating = val
21+
},
22+
async initCard(payload: IGroupCreate) {
23+
try {
24+
this.isUpdating = true
25+
const { data } = await createCardApi(payload)
26+
this.setGroupInfo(data)
27+
} catch (error) {
28+
console.error(error)
29+
showToast({
30+
description: apiError(error).message,
31+
variant: 'destructive',
32+
})
33+
}
34+
this.isUpdating = false
35+
},
36+
async fetchCards({ page = 1, keyword = '' }) {
37+
try {
38+
this.isFetching = true
39+
const { data, meta } = await getCardsApi({ page, keyword })
40+
this.groups = data
41+
this.groupMeta = meta as IPaging
42+
} catch (error) {
43+
console.error(error)
44+
showToast({
45+
description: 'Fetch groups failed',
46+
variant: 'destructive',
47+
})
48+
throw error
49+
} finally {
50+
this.isFetching = false
51+
}
52+
},
53+
async getDetailCard(idGroup: string) {
54+
try {
55+
await getCardDetailApi(idGroup)
56+
// this.setGroupInfo(data)
57+
} catch (error) {
58+
showToast({
59+
description: apiError(error).message,
60+
variant: 'destructive',
61+
})
62+
}
63+
},
64+
async handleDeleteCard(idGroup: string) {
65+
try {
66+
await deleteCardApi(idGroup)
67+
const index = this.groups.findIndex((i) => i.group.id === idGroup)
68+
index > -1 && this.groups.splice(index, 1)
69+
showToast({
70+
description: 'Delete group success',
71+
variant: 'default',
72+
})
73+
} catch (error) {
74+
showToast({
75+
description: apiError(error).message,
76+
variant: 'destructive',
77+
})
78+
}
79+
},
80+
setGroupInfo(val: IGroup) {
81+
this.groupInfo = { ...this.groupInfo, ...val }
82+
},
83+
setMembersGroup(val: any) {
84+
this.listMembers = val
85+
},
86+
},
87+
getters: {
88+
getGroupInfo: (state) => state.groupInfo,
89+
getIsUpdating: (state) => state.isUpdating,
90+
getGroups: (state) => state.groups,
91+
getGroupMeta: (state) => state.groupMeta,
92+
getMemberGroup: (state) => state.listMembers,
93+
getIsFetching: (state) => state.isFetching,
94+
},
95+
})

src/types/flashCard.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface ICard {
2+
id?: string
3+
tearm: string
4+
description: string
5+
}

0 commit comments

Comments
 (0)