diff --git a/src/index.css b/src/index.css index b6b55b9..f131c36 100644 --- a/src/index.css +++ b/src/index.css @@ -5,26 +5,24 @@ @tailwind utilities; @layer utilities { - /* font */ - * { - font-family: 'Inter', sans-serif; - } + /* font */ + * { + font-family: 'Inter', sans-serif; + } - /* background */ - body { - @apply bg-[#fcfafa]; - } + /* background */ + body { + @apply bg-[#fcfafa]; + } - /* form elements */ - input { - @apply placeholder:text-[#8a8a8a] placeholder:text-[14px] placeholder:font-medium !rounded-[4px]; - } + /* form elements */ + input { + @apply placeholder:text-[#8a8a8a] placeholder:text-[14px] placeholder:font-medium !rounded-[4px]; + } - button, - select, - input { - @apply !outline-none !ring-0; - } - - + button, + select, + input { + @apply !outline-none !ring-0; + } } diff --git a/src/renderer/router/index.ts b/src/renderer/router/index.ts index e7da441..0e0f8a4 100644 --- a/src/renderer/router/index.ts +++ b/src/renderer/router/index.ts @@ -9,6 +9,8 @@ import StudentDepartment from '../views/student-department/StudentDepartment.vue import Student from '../views/student-department/student/Student.vue'; import Program from '../views/training-department/program/Program.vue'; import AvailableCourse from '../views/training-department/availableCourse/AvailableCourse.vue'; +import Payment from '../views/finance-department/payment/Payment.vue'; +import Report from '../views/finance-department/report/Report.vue'; import CourseRegistration from '../views/student-department/student/CourseRegistration.vue'; import resolveDepartmentRoute from '../../utils/resolveDepartmentRoute'; import getSession from '../../utils/getSession'; @@ -59,7 +61,16 @@ const routes: Array<RouteRecordRaw> = [ { path: '/finance-department', // phòng kế hoạch tài chính component: FinanceDepartment, - children: [], + children: [ + { + path: 'payment', + component: Payment, + }, + { + path: 'report', + component: Report, + }, + ], }, { path: '/student-department', // phòng công tác sinh viên diff --git a/src/renderer/views/finance-department/FinanceDepartment.vue b/src/renderer/views/finance-department/FinanceDepartment.vue index 7482655..0e290f8 100644 --- a/src/renderer/views/finance-department/FinanceDepartment.vue +++ b/src/renderer/views/finance-department/FinanceDepartment.vue @@ -1,6 +1,13 @@ <template> - <div class="flex"> - <SideBar title="phòng kế hoạch tài chính" /> + <div class="flex overflow-x-scroll"> + <SideBar + :routes="[ + { name: 'Bảng điều khiển', path: '/finance-department' }, + { name: 'Biên lai học phí', path: '/finance-department/payment' }, + { name: 'Báo cáo học phí', path: '/finance-department/report' }, + ]" + title="phòng kế hoạch tài chính" + /> <div class="flex flex-col grow"> <Topbar /> <RouterView /> diff --git a/src/renderer/views/finance-department/payment/Payment.vue b/src/renderer/views/finance-department/payment/Payment.vue new file mode 100644 index 0000000..37d27f3 --- /dev/null +++ b/src/renderer/views/finance-department/payment/Payment.vue @@ -0,0 +1,11 @@ +<template> + <div class="grow flex flex-col gap-8 items-center p-[40px]"> + <FunctionBar /> + <Table /> + </div> +</template> + +<script setup> +import FunctionBar from './components/FunctionBar.vue'; +import Table from './components/Table.vue'; +</script> diff --git a/src/renderer/views/finance-department/payment/components/FunctionBar.vue b/src/renderer/views/finance-department/payment/components/FunctionBar.vue new file mode 100644 index 0000000..d8964c4 --- /dev/null +++ b/src/renderer/views/finance-department/payment/components/FunctionBar.vue @@ -0,0 +1,68 @@ +<template> + <div class="flex items-center gap-[50px]"> + <label> + Năm học: + <select + class="select select-bordered" + v-model="paymentStore.year" + @change="paymentStore.getPayments" + > + <option + v-for="year in getYears()" + :key="year" + :value="year" + :selected="year === new Date().getFullYear()" + > + {{ year }} + </option> + </select> + </label> + + <label> + Học kì: + <select + class="select select-bordered" + v-model="paymentStore.term" + @change="paymentStore.getPayments" + > + <option value="first">Học kì 1</option> + <option value="second">Học kì 2</option> + <option value="third">Học kì 3</option> + </select> + </label> + + <div class="flex"> + <img + :src="searchIcon" + alt="search icon" + class="bg-base-silver pl-4 rounded-l" + /> + <input + type="search" + placeholder="Tìm kiếm họ tên sinh viên" + class="input bg-base-silver max-w-[500px] placeholder:text-black focus:border-transparent" + v-model="paymentStore.studentName" + @input="paymentStore.getPayments" + /> + </div> + </div> +</template> + +<script setup> +import searchIcon from '../../../../../assets/images/searchIcon.svg'; +import { usePaymentStore } from '../stores/payment.ts'; + +const paymentStore = usePaymentStore(); + +const getYears = () => { + const years = []; + + const currentYear = new Date().getFullYear(); + + for (let i = 4; i >= 0; i--) { + years.push(currentYear - i); + } + + return years; +}; +</script> diff --git a/src/renderer/views/finance-department/payment/components/Table.vue b/src/renderer/views/finance-department/payment/components/Table.vue new file mode 100644 index 0000000..c7c2227 --- /dev/null +++ b/src/renderer/views/finance-department/payment/components/Table.vue @@ -0,0 +1,37 @@ +<template> + <div class="overflow-x-auto w-full h-[calc(100vh-300px)]"> + <table class="table table-zebra"> + <thead> + <tr> + <th>Mã biên lai</th> + <th>Mã số sinh viên</th> + <th>Họ tên</th> + <th>Ngày lập biên lai</th> + <th>Số tiền thu</th> + </tr> + </thead> + <tbody> + <tr + v-for="payment in paymentStore.payments" + :key="payment.id" + class="hover:!bg-secondary-100" + > + <td>{{ payment?.id }}</td> + <td>{{ payment?.studentId }}</td> + <td>{{ payment?.studentName }}</td> + <td>{{ formateDateString(payment?.paymentDate) }}</td> + <td>{{ payment?.ammount.toLocaleString() }}</td> + </tr> + </tbody> + </table> + </div> +</template> + +<script setup> +import { usePaymentStore } from '../stores/payment.ts'; +import formateDateString from '../../../../../utils/formatDateString'; + +const paymentStore = usePaymentStore(); + +paymentStore.getPayments(); +</script> diff --git a/src/renderer/views/finance-department/payment/stores/payment.ts b/src/renderer/views/finance-department/payment/stores/payment.ts new file mode 100644 index 0000000..0869662 --- /dev/null +++ b/src/renderer/views/finance-department/payment/stores/payment.ts @@ -0,0 +1,54 @@ +import { defineStore } from 'pinia'; +import { axiosClient } from '../../../../../api/axiosClient'; + +enum Term { + FIRST = 'first', + SECOND = 'second', + THIRD = 'third', +} + +type Payment = { + id: number; + ammount: number; + paymentDate: string; + year: number; + term: Term; + studentId: number; + studentName: string; +}; + +type State = { + payments: Payment[]; + year: number; + term: Term; + studentName: string; +}; + +const usePaymentStore = defineStore('payment', { + state: (): State => ({ + payments: [], + year: new Date().getFullYear(), + term: Term.FIRST, + studentName: '', + }), + + actions: { + async getPayments() { + const response = await axiosClient.get( + `/payment/history?year=${this.year}&term=${this.term}&studentName=${this.studentName}`, + { + id: `list-payment`, + cache: { + update: { + [`list-payment`]: 'delete', + }, + }, + } + ); + + this.payments = response.data; + }, + }, +}); + +export { usePaymentStore }; diff --git a/src/renderer/views/finance-department/report/Report.vue b/src/renderer/views/finance-department/report/Report.vue new file mode 100644 index 0000000..37d27f3 --- /dev/null +++ b/src/renderer/views/finance-department/report/Report.vue @@ -0,0 +1,11 @@ +<template> + <div class="grow flex flex-col gap-8 items-center p-[40px]"> + <FunctionBar /> + <Table /> + </div> +</template> + +<script setup> +import FunctionBar from './components/FunctionBar.vue'; +import Table from './components/Table.vue'; +</script> diff --git a/src/renderer/views/finance-department/report/components/FunctionBar.vue b/src/renderer/views/finance-department/report/components/FunctionBar.vue new file mode 100644 index 0000000..cae123c --- /dev/null +++ b/src/renderer/views/finance-department/report/components/FunctionBar.vue @@ -0,0 +1,81 @@ +<template> + <div class="flex items-center gap-[50px]"> + <label> + Năm học: + <select + class="select select-bordered" + v-model="reportStore.year" + @change="reportStore.getReports" + > + <option + v-for="year in getYears()" + :key="year" + :value="year" + :selected="year === new Date().getFullYear()" + > + {{ year }} + </option> + </select> + </label> + + <label> + Học kì: + <select + class="select select-bordered" + v-model="reportStore.term" + @change="reportStore.getReports" + > + <option value="first">Học kì 1</option> + <option value="second">Học kì 2</option> + <option value="third">Học kì 3</option> + </select> + </label> + + <label> + Trạng thái: + <select + class="select select-bordered" + v-model="reportStore.status" + @change="reportStore.getReports" + > + <option value="ALL">Tất cả</option> + <option value="PAID">Đã đóng đủ</option> + <option value="PENDING">Chưa đóng đủ</option> + </select> + </label> + + <div class="flex"> + <img + :src="searchIcon" + alt="search icon" + class="bg-base-silver pl-4 rounded-l" + /> + <input + type="search" + placeholder="Tìm kiếm họ tên sinh viên" + class="input bg-base-silver max-w-[500px] placeholder:text-black focus:border-transparent" + v-model="reportStore.studentName" + @input="reportStore.getReports" + /> + </div> + </div> +</template> + +<script setup> +import searchIcon from '../../../../../assets/images/searchIcon.svg'; +import { useReportStore } from '../stores/report.ts'; + +const reportStore = useReportStore(); + +const getYears = () => { + const years = []; + + const currentYear = new Date().getFullYear(); + + for (let i = 4; i >= 0; i--) { + years.push(currentYear - i); + } + + return years; +}; +</script> diff --git a/src/renderer/views/finance-department/report/components/Table.vue b/src/renderer/views/finance-department/report/components/Table.vue new file mode 100644 index 0000000..c8921ab --- /dev/null +++ b/src/renderer/views/finance-department/report/components/Table.vue @@ -0,0 +1,46 @@ +<template> + <div class="overflow-x-auto w-full h-[calc(100vh-300px)]"> + <table class="table table-zebra"> + <thead> + <tr> + <th>Mã thu học phí</th> + <th>Mã số sinh viên</th> + <th>Họ tên</th> + <th>Số tiền đăng ký</th> + <th>Số tiền thực tế</th> + <th>Số tiền còn nợ</th> + </tr> + </thead> + <tbody> + <tr + v-for="report in reportStore.reports" + :key="report.id" + class="hover:!bg-secondary-100" + > + <td>{{ report?.id }}</td> + <td>{{ report?.studentId }}</td> + <td>{{ report?.studentName }}</td> + <td>{{ report?.totalRegister.toLocaleString() }}</td> + <td>{{ report?.totalActual.toLocaleString() }}</td> + <td + :class=" + report?.totalActual - report?.totalPaid > 0 + ? 'text-error-text' + : 'text-green-500' + " + > + {{ (report?.totalActual - report?.totalPaid).toLocaleString() }} + </td> + </tr> + </tbody> + </table> + </div> +</template> + +<script setup> +import { useReportStore } from '../stores/report.ts'; + +const reportStore = useReportStore(); + +reportStore.getReports(); +</script> diff --git a/src/renderer/views/finance-department/report/stores/report.ts b/src/renderer/views/finance-department/report/stores/report.ts new file mode 100644 index 0000000..9011731 --- /dev/null +++ b/src/renderer/views/finance-department/report/stores/report.ts @@ -0,0 +1,63 @@ +import { defineStore } from 'pinia'; +import { axiosClient } from '../../../../../api/axiosClient'; + +enum Term { + FIRST = 'first', + SECOND = 'second', + THIRD = 'third', +} + +enum Status { + ALL = 'ALL', + PENDING = 'PENDING', + PAID = 'PAID', +} + +type Report = { + id: number; + totalPaid: number; + studentId: number; + studentName: string; + totalRegister: number; + totalActual: number; +}; + +type State = { + reports: Report[]; + year: number; + term: Term; + status: Status; + studentName: string; +}; + +const useReportStore = defineStore('report', { + state: (): State => ({ + reports: [], + year: new Date().getFullYear(), + term: Term.FIRST, + status: Status.ALL, + studentName: '', + }), + + actions: { + async getReports() { + const response = await axiosClient.get( + `/payment?year=${this.year}&term=${this.term}&status=${this.status}&studentName=${this.studentName}`, + { + id: `list-report`, + cache: { + update: { + [`list-report`]: 'delete', + }, + }, + } + ); + + console.log(response.data); + + this.reports = response.data; + }, + }, +}); + +export { useReportStore }; diff --git a/src/renderer/views/student-department/StudentDepartment.vue b/src/renderer/views/student-department/StudentDepartment.vue index 505a205..361b783 100644 --- a/src/renderer/views/student-department/StudentDepartment.vue +++ b/src/renderer/views/student-department/StudentDepartment.vue @@ -1,8 +1,8 @@ <template> - <div class="flex"> + <div class="flex overflow-x-scroll"> <SideBar :routes="[ - { name: 'bảng điều khiển', path: '/student-department' }, + { name: 'Bảng điều khiển', path: '/student-department' }, { name: 'Sinh viên', path: '/student-department/student' }, { name: 'Đăng ký môn học', diff --git a/src/renderer/views/training-department/TrainingDepartment.vue b/src/renderer/views/training-department/TrainingDepartment.vue index 3cf47a0..124f936 100644 --- a/src/renderer/views/training-department/TrainingDepartment.vue +++ b/src/renderer/views/training-department/TrainingDepartment.vue @@ -1,9 +1,9 @@ <template> - <div class="flex"> + <div class="flex overflow-x-scroll"> <SideBar title="phòng đào tạo" :routes="[ - { name: 'bảng điều khiển', path: '/training-department' }, + { name: 'Bảng điều khiển', path: '/training-department' }, { name: 'môn học', path: '/training-department/course' }, { name: 'chương trình học', path: '/training-department/program' }, { name: 'môn học mở', path: '/training-department/available-course' }, diff --git a/src/renderer/views/training-department/availableCourse/AvailableCourse.vue b/src/renderer/views/training-department/availableCourse/AvailableCourse.vue index 31c84a0..80da910 100644 --- a/src/renderer/views/training-department/availableCourse/AvailableCourse.vue +++ b/src/renderer/views/training-department/availableCourse/AvailableCourse.vue @@ -1,7 +1,5 @@ <template> - <div - class="border border-red-700 grow flex flex-col gap-8 items-center p-[40px]" - > + <div class="grow flex flex-col gap-8 items-center p-[40px]"> <FunctionBar /> <Table /> </div> diff --git a/src/utils/formatDateString.ts b/src/utils/formatDateString.ts new file mode 100644 index 0000000..668f7d9 --- /dev/null +++ b/src/utils/formatDateString.ts @@ -0,0 +1,10 @@ +const formatDateString = (dateString: string) => { + const date = new Date(dateString); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + + return `${day}/${month}/${year}`; +}; + +export default formatDateString;