Skip to content

Commit

Permalink
feat: 학습 진행 상태를 그래프로 조회할 수 있다. (#20)
Browse files Browse the repository at this point in the history
* ✅ 진행 상태 getters 추가

* 📦 install chartjs

* ✨ chartjs 플러그인 생성 및 적용

* ✨ chart rendering
  • Loading branch information
padosum committed Mar 1, 2023
1 parent c15aa96 commit 7023f11
Show file tree
Hide file tree
Showing 14 changed files with 415 additions and 20 deletions.
30 changes: 30 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Expand Up @@ -13,6 +13,7 @@
},
"dependencies": {
"@vueuse/core": "^9.13.0",
"chart.js": "^4.2.1",
"file-dialog": "^0.0.8",
"github-markdown-css": "^5.2.0",
"highlight.js": "^11.7.0",
Expand Down
2 changes: 1 addition & 1 deletion src/App.vue
Expand Up @@ -41,7 +41,7 @@
</teamplte>
</v-app-bar>
<v-main>
<router-view />
<router-view :key="route.path" />
</v-main>
</v-app>
</template>
Expand Down
7 changes: 7 additions & 0 deletions src/assets/style.scss
Expand Up @@ -2,11 +2,18 @@
@import 'github-markdown-css/github-markdown.css';

:root {
--bg-color: #d1c4e9;
--swiper-navigation-sides-offset: -60px;
--swiper-navigation-size: 32px; /* To edit the size of the arrows */
--swiper-navigation-color: #4527a0; /* To edit the color of the arrows */
}

a {
text-decoration: none;
color: #673ab7;
transition: 0.4s;
}

.markdown-body {
padding: 10px;
background-color: rgba(0, 0, 0, 0);
Expand Down
2 changes: 1 addition & 1 deletion src/components/LearnsetCard.vue
@@ -1,6 +1,6 @@
<template>
<div class="d-flex flex-column pa-3 w-100">
<h2 class="text-h5 font-weight-black">{{ card.title }}</h2>
<h2 class="text-h6 font-weight-black">{{ card.title }}</h2>
<v-textarea
label="내 답변"
:disabled="showBack"
Expand Down
123 changes: 123 additions & 0 deletions src/components/LearnsetChart.vue
@@ -0,0 +1,123 @@
<template>
<v-card class="mx-auto mb-4" variant="outlined">
<v-card-item>
<div class="text-h6 font-weight-bold mb-1">학습 진행 상태</div>
</v-card-item>
<div
class="chart-container mb-4"
style="position: relative; height: 100%; width: 100%"
>
<canvas ref="canvas"></canvas>
</div>
<template v-if="cardType">
<v-table density="compact" class="pa-2">
<thead>
<tr>
<th class="text-left font-weight-bold">질문</th>
<th class="text-center font-weight-bold">맞춘 횟수</th>
<th class="text-center font-weight-bold">틀린 횟수</th>
<th class="text-center font-weight-bold">연속 정답 횟수</th>
<th class="text-center font-weight-bold">최근 학습일</th>
</tr>
</thead>
<tbody>
<tr v-for="card in cards" :key="card.id">
<td>
<router-link :to="`/learnset/${card.learnsetId}`">
{{ card.title }}
</router-link>
</td>
<td class="text-center">{{ card.correctCnt || 0 }}</td>
<td class="text-center">{{ card.incorrectCnt || 0 }}</td>
<td class="text-center">{{ card.repetition || 0 }}</td>
<td class="text-center">{{ dateFormat(card.reviewDate) }}</td>
</tr>
</tbody>
</v-table>
</template>
</v-card>
</template>

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { useStore } from 'vuex';
import type { MyStore } from '@/store/types';
import { useChartjs } from '@/plugins/chartjs';
const canvas = ref<HTMLCanvasElement | null>(null);
const store: MyStore = useStore();
const props = defineProps({
learnsetId: {
type: String,
},
});
const cardType = ref('');
const cards = computed(() => {
if (cardType.value === 'know') {
if (props.learnsetId) {
return store.getters.knowCards.filter(
(card) => card.learnsetId === props.learnsetId
);
}
return store.getters.knowCards;
} else {
if (props.learnsetId) {
return store.getters.learningCards.filter(
(card) => card.learnsetId === props.learnsetId
);
}
return store.getters.learningCards;
}
});
const renderChart = () => {
const data = props.learnsetId
? store.getters.progressById(props.learnsetId)
: store.getters.progress;
const ctx = canvas.value?.getContext('2d');
const Chart = useChartjs();
if (ctx && Chart) {
new Chart(ctx as CanvasRenderingContext2D, {
type: 'doughnut',
data: {
labels: data.map((row) => row.label),
datasets: [
{
data: data.map((row) => row.count),
backgroundColor: ['#d1c4e9', '#242424'],
},
],
},
options: {
maintainAspectRatio: false,
onClick: (e, [elements]) => {
if (elements) {
const i = elements.index;
cardType.value = data[i].type;
}
},
},
});
}
};
onMounted(() => {
renderChart();
});
const dateFormat = (date: string) => {
if (date !== '') {
return new Intl.DateTimeFormat('ko-KR').format(new Date(date));
}
return date;
};
</script>

<style scoped></style>
3 changes: 2 additions & 1 deletion src/main.ts
Expand Up @@ -6,7 +6,7 @@ import 'vuetify/styles';
import '@mdi/font/css/materialdesignicons.css';
import vuetify from '@/utils/setupVuetify';
import { markdownItPlugin } from '@/plugins/markdownit';

import { chartjsPlugin } from './plugins/chartjs';
import { createStore } from 'vuex';
import { store } from '@/store';

Expand All @@ -17,5 +17,6 @@ app.use(vuexStore);
app.use(router);
app.use(vuetify);
app.use(markdownItPlugin);
app.use(chartjsPlugin);

app.mount('#app');
17 changes: 17 additions & 0 deletions src/plugins/chartjs.ts
@@ -0,0 +1,17 @@
import { inject } from 'vue';
import type { InjectionKey, Plugin, App } from 'vue';
import Chart from 'chart.js/auto';

const chartjsKey: InjectionKey<typeof Chart> = Symbol('_chartjs_');

export const useChartjs = () => {
return inject(chartjsKey);
};

export const chartjsPlugin: Plugin = {
install(app: App) {
const chart: typeof Chart = Chart;

app.provide(chartjsKey, chart);
},
};
5 changes: 3 additions & 2 deletions src/router/__tests__/router.spec.ts
Expand Up @@ -9,6 +9,7 @@ import { store } from '@/store';
import type { Learnset } from '@/types/interfaces';
import LearnsetView from '@/views/LearnsetView.vue';
import LearnView from '@/views/LearnView.vue';
import { chartjsPlugin } from '@/plugins/chartjs';

const router = createRouter({
history: createWebHistory(),
Expand All @@ -28,7 +29,7 @@ describe('routing', () => {
const storeInstance = createStore(store);
const { getByTestId } = render(App, {
global: {
plugins: [router, storeInstance, vuetify],
plugins: [chartjsPlugin, router, storeInstance, vuetify],
},
});

Expand Down Expand Up @@ -102,7 +103,7 @@ const renderVuexApp = (customStore: StoreOptions<RootState>) => {

return render(App, {
global: {
plugins: [router, mergedStoreInstance, vuetify],
plugins: [chartjsPlugin, router, mergedStoreInstance, vuetify],
},
});
};

1 comment on commit 7023f11

@vercel
Copy link

@vercel vercel bot commented on 7023f11 Mar 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

flashmd – ./

flashmd-git-main-padosum.vercel.app
flashmd.vercel.app
flash.padosum.dev
flashmd-padosum.vercel.app

Please sign in to comment.