From 351e6968e1b2cdd710ebc3caf72fc3c3a8c8fb74 Mon Sep 17 00:00:00 2001 From: Vy Ngo Chi Date: Mon, 24 Aug 2026 08:31:07 +0700 Subject: [PATCH 1/3] feat: implement tutor dashboard features including earnings reports, session scheduling, materials management, and chat functionality --- .gitignore | 3 + apps/lms/app/lms/tutor/earnings/loading.tsx | 33 ++++ apps/lms/app/lms/tutor/earnings/page.tsx | 12 ++ .../materials/[lessonId]/preview/page.tsx | 14 +- .../materials/learner/[learnerId]/loading.tsx | 15 ++ .../materials/learner/[learnerId]/page.tsx | 16 ++ apps/lms/app/lms/tutor/materials/loading.tsx | 18 ++ apps/lms/app/lms/tutor/materials/page.tsx | 9 +- .../features/auth/components/FormField.tsx | 1 - .../features/auth/components/LoginForm.tsx | 2 +- .../components/EarningsDashboard.tsx | 138 +++++++++++++ .../components/EarningsDialogs.tsx | 120 ++++++++++++ .../components/EarningsReportsPanel.tsx | 65 +++++++ .../components/EarningsStatusBadge.tsx | 35 ++++ .../components/EarningsSummaryCards.tsx | 69 +++++++ .../components/EarningsTable.tsx | 148 ++++++++++++++ .../components/EarningsToolbar.tsx | 109 +++++++++++ .../components/ReportIssueDialog.tsx | 92 +++++++++ .../tutor-earnings/data/earnings.mock.ts | 182 ++++++++++++++++++ .../tutor-earnings/hooks/useEarnings.ts | 61 ++++++ .../tutor-earnings/types/earnings.types.ts | 38 ++++ .../tutor-earnings/utils/earnings.utils.ts | 102 ++++++++++ .../components/EditMaterialDialog.tsx | 62 ++++++ .../components/LearnerMaterialsList.tsx | 115 +++++++++++ .../components/LearnerMaterialsScreen.tsx | 112 +++++++++++ .../components/LessonDetailCard.tsx | 32 +-- .../components/LessonDetailScreen.tsx | 5 +- .../components/MaterialLibraryList.tsx | 74 +++++++ .../components/MaterialsBadges.tsx | 51 +++++ .../components/MaterialsTable.tsx | 126 ------------ .../components/SessionCoveragePanel.tsx | 39 ++++ .../components/SplitPreview.tsx | 6 +- .../components/TheoryEditor.tsx | 2 +- .../components/TutorMaterialsScreen.tsx | 91 +++++++++ .../components/UploadMaterialDialog.tsx | 135 +++++++++++++ .../hooks/useMaterialsLibrary.ts | 123 ++++++++++++ apps/lms/features/tutor-materials/mockData.ts | 132 ++++++------- apps/lms/features/tutor-materials/types.ts | 47 ++++- .../tutor-materials/utils/materials.utils.ts | 51 +++++ .../components/ChatRoomPanel.tsx | 2 - .../tutor-messages/components/ChatSidebar.tsx | 20 +- .../components/ConnectionInfoPanel.tsx | 1 - .../components/MessageBubble.tsx | 4 - .../tutor-messages/hooks/useChatRoom.ts | 4 +- .../tutor-schedule/TutorSchedulePage.tsx | 1 - .../components/CalendarView.tsx | 1 - apps/lms/proxy.ts | 2 +- packages/core/configs/client.ts | 4 - 48 files changed, 2239 insertions(+), 285 deletions(-) create mode 100644 apps/lms/app/lms/tutor/earnings/loading.tsx create mode 100644 apps/lms/app/lms/tutor/earnings/page.tsx create mode 100644 apps/lms/app/lms/tutor/materials/learner/[learnerId]/loading.tsx create mode 100644 apps/lms/app/lms/tutor/materials/learner/[learnerId]/page.tsx create mode 100644 apps/lms/app/lms/tutor/materials/loading.tsx create mode 100644 apps/lms/features/tutor-earnings/components/EarningsDashboard.tsx create mode 100644 apps/lms/features/tutor-earnings/components/EarningsDialogs.tsx create mode 100644 apps/lms/features/tutor-earnings/components/EarningsReportsPanel.tsx create mode 100644 apps/lms/features/tutor-earnings/components/EarningsStatusBadge.tsx create mode 100644 apps/lms/features/tutor-earnings/components/EarningsSummaryCards.tsx create mode 100644 apps/lms/features/tutor-earnings/components/EarningsTable.tsx create mode 100644 apps/lms/features/tutor-earnings/components/EarningsToolbar.tsx create mode 100644 apps/lms/features/tutor-earnings/components/ReportIssueDialog.tsx create mode 100644 apps/lms/features/tutor-earnings/data/earnings.mock.ts create mode 100644 apps/lms/features/tutor-earnings/hooks/useEarnings.ts create mode 100644 apps/lms/features/tutor-earnings/types/earnings.types.ts create mode 100644 apps/lms/features/tutor-earnings/utils/earnings.utils.ts create mode 100644 apps/lms/features/tutor-materials/components/EditMaterialDialog.tsx create mode 100644 apps/lms/features/tutor-materials/components/LearnerMaterialsList.tsx create mode 100644 apps/lms/features/tutor-materials/components/LearnerMaterialsScreen.tsx create mode 100644 apps/lms/features/tutor-materials/components/MaterialLibraryList.tsx create mode 100644 apps/lms/features/tutor-materials/components/MaterialsBadges.tsx delete mode 100644 apps/lms/features/tutor-materials/components/MaterialsTable.tsx create mode 100644 apps/lms/features/tutor-materials/components/SessionCoveragePanel.tsx create mode 100644 apps/lms/features/tutor-materials/components/TutorMaterialsScreen.tsx create mode 100644 apps/lms/features/tutor-materials/components/UploadMaterialDialog.tsx create mode 100644 apps/lms/features/tutor-materials/hooks/useMaterialsLibrary.ts create mode 100644 apps/lms/features/tutor-materials/utils/materials.utils.ts diff --git a/.gitignore b/.gitignore index b90e61c..39b830a 100644 --- a/.gitignore +++ b/.gitignore @@ -46,5 +46,8 @@ next-env.d.ts # Local Materials .agents-feature-md + +# Document +docs \ No newline at end of file diff --git a/apps/lms/app/lms/tutor/earnings/loading.tsx b/apps/lms/app/lms/tutor/earnings/loading.tsx new file mode 100644 index 0000000..1129c38 --- /dev/null +++ b/apps/lms/app/lms/tutor/earnings/loading.tsx @@ -0,0 +1,33 @@ +function SkeletonBlock({ className }: { className: string }) { + return
; +} + +export default function EarningsLoading() { + return ( +
+
+
+ + + +
+
+ {[0, 1, 2, 3].map((item) => ( +
+ + + +
+ ))} +
+
+ +
+
+ + +
+
+
+ ); +} diff --git a/apps/lms/app/lms/tutor/earnings/page.tsx b/apps/lms/app/lms/tutor/earnings/page.tsx new file mode 100644 index 0000000..29d5062 --- /dev/null +++ b/apps/lms/app/lms/tutor/earnings/page.tsx @@ -0,0 +1,12 @@ +import type { Metadata } from "next"; +import { EarningsDashboard } from "@/features/tutor-earnings/components/EarningsDashboard"; + +export const metadata: Metadata = { + title: "Thu nhập & Thanh toán | BeeWise LMS", + description: "Theo dõi thu nhập và trạng thái quyết toán dành cho gia sư BeeWise.", +}; + +export default function TutorEarningsPage() { + return ; +} + diff --git a/apps/lms/app/lms/tutor/materials/[lessonId]/preview/page.tsx b/apps/lms/app/lms/tutor/materials/[lessonId]/preview/page.tsx index 7c6c2dd..b580e0d 100644 --- a/apps/lms/app/lms/tutor/materials/[lessonId]/preview/page.tsx +++ b/apps/lms/app/lms/tutor/materials/[lessonId]/preview/page.tsx @@ -5,20 +5,10 @@ export const metadata = { title: "Bản xem trước tài liệu | BeeWise Tutor", }; -// Next.js App Router dynamic route props -interface PageProps { - params: Promise<{ lessonId: string }>; -} - -export default async function PreviewPage({ params }: PageProps) { - const resolvedParams = await params; - +export default function PreviewPage() { return (
- +
); } diff --git a/apps/lms/app/lms/tutor/materials/learner/[learnerId]/loading.tsx b/apps/lms/app/lms/tutor/materials/learner/[learnerId]/loading.tsx new file mode 100644 index 0000000..f9a36da --- /dev/null +++ b/apps/lms/app/lms/tutor/materials/learner/[learnerId]/loading.tsx @@ -0,0 +1,15 @@ +function Skeleton({ className }: { className: string }) { + return
; +} + +export default function LearnerMaterialsLoading() { + return ( +
+
+ + +
+
+
+ ); +} diff --git a/apps/lms/app/lms/tutor/materials/learner/[learnerId]/page.tsx b/apps/lms/app/lms/tutor/materials/learner/[learnerId]/page.tsx new file mode 100644 index 0000000..e5be046 --- /dev/null +++ b/apps/lms/app/lms/tutor/materials/learner/[learnerId]/page.tsx @@ -0,0 +1,16 @@ +import type { Metadata } from "next"; +import { LearnerMaterialsScreen } from "@/features/tutor-materials/components/LearnerMaterialsScreen"; + +export const metadata: Metadata = { + title: "Thư viện học viên | BeeWise Tutor", +}; + +interface PageProps { + params: Promise<{ learnerId: string }>; +} + +export default async function LearnerMaterialsPage({ params }: PageProps) { + const { learnerId } = await params; + return ; +} + diff --git a/apps/lms/app/lms/tutor/materials/loading.tsx b/apps/lms/app/lms/tutor/materials/loading.tsx new file mode 100644 index 0000000..5a123ea --- /dev/null +++ b/apps/lms/app/lms/tutor/materials/loading.tsx @@ -0,0 +1,18 @@ +function Skeleton({ className }: { className: string }) { + return
; +} + +export default function MaterialsLoading() { + return ( +
+
+
+ +
+ +
{[0, 1, 2, 3].map((item) => )}
+
+
+
+ ); +} diff --git a/apps/lms/app/lms/tutor/materials/page.tsx b/apps/lms/app/lms/tutor/materials/page.tsx index 5a2fa41..cd89cbe 100644 --- a/apps/lms/app/lms/tutor/materials/page.tsx +++ b/apps/lms/app/lms/tutor/materials/page.tsx @@ -1,14 +1,9 @@ -import React from "react"; -import { MaterialsTable } from "@/features/tutor-materials/components/MaterialsTable"; +import { TutorMaterialsScreen } from "@/features/tutor-materials/components/TutorMaterialsScreen"; export const metadata = { title: "Quản lý tài liệu | BeeWise Tutor", }; export default function TutorMaterialsPage() { - return ( -
- -
- ); + return ; } diff --git a/apps/lms/features/auth/components/FormField.tsx b/apps/lms/features/auth/components/FormField.tsx index e160d49..0da8757 100644 --- a/apps/lms/features/auth/components/FormField.tsx +++ b/apps/lms/features/auth/components/FormField.tsx @@ -1,6 +1,5 @@ "use client"; -import { useId } from "react"; import type { FieldError } from "react-hook-form"; interface FormFieldProps { diff --git a/apps/lms/features/auth/components/LoginForm.tsx b/apps/lms/features/auth/components/LoginForm.tsx index 939dcf6..dd1cae9 100644 --- a/apps/lms/features/auth/components/LoginForm.tsx +++ b/apps/lms/features/auth/components/LoginForm.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; import Image from "next/image"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; -import { Eye, EyeSlash, ArrowRight } from "@phosphor-icons/react"; +import { Eye, EyeSlash } from "@phosphor-icons/react"; import { loginSchema, type LoginFormValues } from "../schemas/auth.schema"; import { FormField, Input } from "./FormField"; import { useLogin } from "@workspace/core/hooks/useLogin"; diff --git a/apps/lms/features/tutor-earnings/components/EarningsDashboard.tsx b/apps/lms/features/tutor-earnings/components/EarningsDashboard.tsx new file mode 100644 index 0000000..1eff81b --- /dev/null +++ b/apps/lms/features/tutor-earnings/components/EarningsDashboard.tsx @@ -0,0 +1,138 @@ +"use client"; + +import { useState } from "react"; +import { Info, MicrosoftExcelLogo } from "@phosphor-icons/react"; +import { toast } from "@workspace/ui/components/ui/bee-toast"; +import { EARNINGS_REPORTS } from "../data/earnings.mock"; +import { useEarnings } from "../hooks/useEarnings"; +import type { EarningSession, EarningsReport } from "../types/earnings.types"; +import { exportEarningsToExcel } from "../utils/earnings.utils"; +import { EarningDetailDialog } from "./EarningsDialogs"; +import { EarningsReportsPanel } from "./EarningsReportsPanel"; +import { EarningsSummaryCards } from "./EarningsSummaryCards"; +import { EarningsTable } from "./EarningsTable"; +import { EarningsToolbar } from "./EarningsToolbar"; +import { ReportIssueDialog } from "./ReportIssueDialog"; + +export function EarningsDashboard() { + const earnings = useEarnings(); + const [selectedSession, setSelectedSession] = useState( + null, + ); + const [detailOpen, setDetailOpen] = useState(false); + const [reportOpen, setReportOpen] = useState(false); + const [reports, setReports] = useState(EARNINGS_REPORTS); + + function openDetail(session: EarningSession) { + setSelectedSession(session); + setDetailOpen(true); + } + + function openReport(session: EarningSession) { + setSelectedSession(session); + setReportOpen(true); + } + + function submitReport(title: string, description: string) { + if (!selectedSession) return; + const newReport: EarningsReport = { + id: `report-${Date.now()}`, + reportCode: `BC-${new Date().getDate().toString().padStart(2, "0")}${(new Date().getMonth() + 1).toString().padStart(2, "0")}-${(reports.length + 1).toString().padStart(3, "0")}`, + sessionId: selectedSession.id, + title, + description, + createdAt: new Date().toISOString(), + status: "received", + }; + setReports((current) => [newReport, ...current]); + setReportOpen(false); + toast.success("Đã gửi báo cáo", { + description: + "Admin đã tiếp nhận và sẽ phản hồi trong danh sách đơn báo cáo.", + }); + } + + function handleExport() { + exportEarningsToExcel(earnings.filteredSessions); + toast.success("Đã xuất file Excel", { + description: `${earnings.filteredSessions.length} buổi học đã được đưa vào báo cáo.`, + }); + } + + return ( +
+
+
+
+

+ Thu nhập & Thanh toán +

+

+ Theo dõi học phí từng buổi đã dạy, trạng thái quyết toán và các + yêu cầu hỗ trợ. +

+
+
+
+
+ + + +
+
+ + + +
+ + +
+
+ + + +
+ ); +} diff --git a/apps/lms/features/tutor-earnings/components/EarningsDialogs.tsx b/apps/lms/features/tutor-earnings/components/EarningsDialogs.tsx new file mode 100644 index 0000000..9902eff --- /dev/null +++ b/apps/lms/features/tutor-earnings/components/EarningsDialogs.tsx @@ -0,0 +1,120 @@ +"use client"; + +import { + CalendarBlank, + CheckCircle, + Clock, + Hash, + Receipt, + User, + WarningCircle, +} from "@phosphor-icons/react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@workspace/ui/components/ui/dialog"; +import type { EarningSession } from "../types/earnings.types"; +import { formatCurrency, formatDateTime } from "../utils/earnings.utils"; +import { EarningsStatusBadge } from "./EarningsStatusBadge"; + +interface DetailDialogProps { + session: EarningSession | null; + open: boolean; + onOpenChange: (open: boolean) => void; + onReport: (session: EarningSession) => void; +} + +export function EarningDetailDialog({ + session, + open, + onOpenChange, + onReport, +}: DetailDialogProps) { + if (!session) return null; + + return ( + + + + Chi tiết thu nhập + {session.sessionCode} + + +
+
+
+

Học phí buổi học

+

{formatCurrency(session.fee)}

+
+ +
+ +
+ + + + + + +
+ +
+ Học phí của mỗi buổi được thiết lập trước trong thỏa thuận lớp học. Khoản thu được ghi nhận sau khi buổi học hoàn thành. +
+
+ + + + + +
+
+ ); +} + +function DetailItem({ + icon: Icon, + label, + value, +}: { + icon: typeof Receipt; + label: string; + value: string; +}) { + return ( +
+ + +
+
{label}
+
{value}
+
+
+ ); +} + diff --git a/apps/lms/features/tutor-earnings/components/EarningsReportsPanel.tsx b/apps/lms/features/tutor-earnings/components/EarningsReportsPanel.tsx new file mode 100644 index 0000000..2987784 --- /dev/null +++ b/apps/lms/features/tutor-earnings/components/EarningsReportsPanel.tsx @@ -0,0 +1,65 @@ +import { CaretDown, CheckCircle, Clock, Headset } from "@phosphor-icons/react"; +import type { EarningsReport } from "../types/earnings.types"; +import { formatDateTime } from "../utils/earnings.utils"; + +const REPORT_STATUS = { + received: { label: "Đã tiếp nhận", className: "text-[#280F91] bg-[#280F91]/8", icon: Clock }, + processing: { label: "Đang xử lý", className: "text-[#905B0F] bg-[#FFC500]/15", icon: Clock }, + resolved: { label: "Đã giải quyết", className: "text-[#447353] bg-[#447353]/10", icon: CheckCircle }, +} as const; + +export function EarningsReportsPanel({ reports }: { reports: EarningsReport[] }) { + return ( + + ); +} diff --git a/apps/lms/features/tutor-earnings/components/EarningsStatusBadge.tsx b/apps/lms/features/tutor-earnings/components/EarningsStatusBadge.tsx new file mode 100644 index 0000000..0548850 --- /dev/null +++ b/apps/lms/features/tutor-earnings/components/EarningsStatusBadge.tsx @@ -0,0 +1,35 @@ +import { CheckCircle, Clock, MagnifyingGlass } from "@phosphor-icons/react"; +import type { SettlementStatus } from "../types/earnings.types"; + +const STATUS_CONFIG = { + settled: { + label: "Đã quyết toán", + icon: CheckCircle, + className: "bg-[#447353]/10 text-[#447353] border-[#447353]/20", + }, + pending: { + label: "Chờ quyết toán", + icon: Clock, + className: "bg-[#FFC500]/15 text-[#905B0F] border-[#FFC500]/35", + }, + reviewing: { + label: "Đang kiểm tra", + icon: MagnifyingGlass, + className: "bg-[#280F91]/8 text-[#280F91] border-[#280F91]/15", + }, +} as const; + +export function EarningsStatusBadge({ status }: { status: SettlementStatus }) { + const config = STATUS_CONFIG[status]; + const Icon = config.icon; + + return ( + + + ); +} + diff --git a/apps/lms/features/tutor-earnings/components/EarningsSummaryCards.tsx b/apps/lms/features/tutor-earnings/components/EarningsSummaryCards.tsx new file mode 100644 index 0000000..2e7b913 --- /dev/null +++ b/apps/lms/features/tutor-earnings/components/EarningsSummaryCards.tsx @@ -0,0 +1,69 @@ +import { + CalendarCheck, + CurrencyCircleDollar, + HandCoins, + HourglassMedium, +} from "@phosphor-icons/react"; +import type { EarningsSummary } from "../types/earnings.types"; +import { formatCurrency } from "../utils/earnings.utils"; + +export function EarningsSummaryCards({ summary }: { summary: EarningsSummary }) { + const items = [ + { + label: "Tổng thu nhập", + value: formatCurrency(summary.total), + helper: `${summary.sessionCount} buổi học hoàn thành`, + icon: CurrencyCircleDollar, + iconClass: "bg-[#280F91] text-white", + }, + { + label: "Đã quyết toán", + value: formatCurrency(summary.settled), + helper: "Đã chuyển vào số dư", + icon: HandCoins, + iconClass: "bg-[#447353] text-white", + }, + { + label: "Chờ quyết toán", + value: formatCurrency(summary.pending), + helper: "Gồm khoản đang kiểm tra", + icon: HourglassMedium, + iconClass: "bg-[#FFC500] text-[#0C0C0B]", + }, + { + label: "Buổi đã dạy", + value: summary.sessionCount.toLocaleString("vi-VN"), + helper: "Theo bộ lọc hiện tại", + icon: CalendarCheck, + iconClass: "bg-[#CFE1FA] text-[#280F91]", + }, + ]; + + return ( +
+ {items.map((item) => { + const Icon = item.icon; + return ( +
+
+
+

{item.label}

+

+ {item.value} +

+

{item.helper}

+
+ + +
+
+ ); + })} +
+ ); +} + diff --git a/apps/lms/features/tutor-earnings/components/EarningsTable.tsx b/apps/lms/features/tutor-earnings/components/EarningsTable.tsx new file mode 100644 index 0000000..f13a770 --- /dev/null +++ b/apps/lms/features/tutor-earnings/components/EarningsTable.tsx @@ -0,0 +1,148 @@ +import { + ArrowRight, + CalendarX, + WarningCircle, +} from "@phosphor-icons/react"; +import type { EarningSession } from "../types/earnings.types"; +import { formatCurrency, formatDateTime } from "../utils/earnings.utils"; +import { EarningsStatusBadge } from "./EarningsStatusBadge"; + +interface EarningsTableProps { + sessions: EarningSession[]; + onViewDetail: (session: EarningSession) => void; + onReport: (session: EarningSession) => void; +} + +function ActionButtons({ + session, + onViewDetail, + onReport, +}: EarningsTableProps & { session: EarningSession }) { + return ( +
+ + +
+ ); +} + +export function EarningsTable(props: EarningsTableProps) { + if (props.sessions.length === 0) { + return ( +
+
+ + +

Chưa có dữ liệu phù hợp

+

+ Hãy đổi mốc thời gian, trạng thái hoặc từ khóa để xem các buổi học khác. +

+
+
+ ); + } + + return ( +
+
+
+

+ Chi tiết theo buổi học +

+

+ Học phí được ghi nhận sau khi buổi học hoàn thành. +

+
+ + {props.sessions.length} buổi + +
+ +
+ + + + + + + + + + + + + {props.sessions.map((session) => ( + + + + + + + + + ))} + +
Buổi họcHọc viênThời gianHọc phíTrạng tháiThao tác
+

{session.className}

+

{session.sessionCode}

+
{session.learnerName} +

{formatDateTime(session.taughtAt)}

+

{session.durationMinutes} phút

+
+ {formatCurrency(session.fee)} + + + + +
+
+ +
+ {props.sessions.map((session) => ( +
+
+
+

{session.className}

+

{session.sessionCode}

+
+ +
+
+
+
Học viên
+
{session.learnerName}
+
+
+
Học phí
+
{formatCurrency(session.fee)}
+
+
+
Thời gian
+
{formatDateTime(session.taughtAt)} ({session.durationMinutes} phút)
+
+
+
+ +
+
+ ))} +
+
+ ); +} diff --git a/apps/lms/features/tutor-earnings/components/EarningsToolbar.tsx b/apps/lms/features/tutor-earnings/components/EarningsToolbar.tsx new file mode 100644 index 0000000..d11c0c4 --- /dev/null +++ b/apps/lms/features/tutor-earnings/components/EarningsToolbar.tsx @@ -0,0 +1,109 @@ +import { + CalendarBlank, + DownloadSimple, + MagnifyingGlass, +} from "@phosphor-icons/react"; +import type { EarningsPeriod } from "../types/earnings.types"; +import type { SettlementFilter } from "../hooks/useEarnings"; + +const PERIODS: { value: EarningsPeriod; label: string }[] = [ + { value: "day", label: "Ngày" }, + { value: "week", label: "Tuần" }, + { value: "month", label: "Tháng" }, + { value: "year", label: "Năm" }, +]; + +interface EarningsToolbarProps { + period: EarningsPeriod; + referenceDate: string; + status: SettlementFilter; + search: string; + exportDisabled: boolean; + onPeriodChange: (period: EarningsPeriod) => void; + onReferenceDateChange: (date: string) => void; + onStatusChange: (status: SettlementFilter) => void; + onSearchChange: (search: string) => void; + onExport: () => void; +} + +export function EarningsToolbar(props: EarningsToolbarProps) { + return ( +
+
+
+ {PERIODS.map((item) => ( + + ))} +
+ +
+ + + + + + + +
+
+
+ ); +} + diff --git a/apps/lms/features/tutor-earnings/components/ReportIssueDialog.tsx b/apps/lms/features/tutor-earnings/components/ReportIssueDialog.tsx new file mode 100644 index 0000000..cd18882 --- /dev/null +++ b/apps/lms/features/tutor-earnings/components/ReportIssueDialog.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { useState, type FormEvent } from "react"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@workspace/ui/components/ui/dialog"; +import type { EarningSession } from "../types/earnings.types"; + +interface ReportDialogProps { + session: EarningSession | null; + open: boolean; + onOpenChange: (open: boolean) => void; + onSubmit: (title: string, description: string) => void; +} + +export function ReportIssueDialog({ session, open, onOpenChange, onSubmit }: ReportDialogProps) { + const [title, setTitle] = useState("Trạng thái quyết toán chưa chính xác"); + const [description, setDescription] = useState(""); + + if (!session) return null; + + function handleSubmit(event: FormEvent) { + event.preventDefault(); + if (!description.trim()) return; + onSubmit(title, description.trim()); + setDescription(""); + } + + return ( + + +
+ + Báo cáo vấn đề + + Gửi yêu cầu hỗ trợ cho buổi {session.sessionCode}. Admin sẽ phản hồi trong danh sách đơn báo cáo. + + + +
+ +