From a3adee3467594b4c0ea5ca39f4ccd52c7cf2fb4f Mon Sep 17 00:00:00 2001 From: austinkelsay <austinkelsay@yahoo.com> Date: Tue, 19 Nov 2024 17:45:09 -0600 Subject: [PATCH] Full listing out of lesson and course starts / completions in progress, new styles for progress and purchase tables --- .../charts/GithubContributionChart.js | 14 +- .../profile/DataTables/UserProgressTable.js | 163 +++++++++++------- .../profile/DataTables/UserPurchaseTable.js | 95 ++++++++++ src/components/profile/UserProfile.js | 51 ++---- src/hooks/tracking/useTrackVideoLesson.js | 2 +- 5 files changed, 222 insertions(+), 103 deletions(-) create mode 100644 src/components/profile/DataTables/UserPurchaseTable.js diff --git a/src/components/charts/GithubContributionChart.js b/src/components/charts/GithubContributionChart.js index 1ef3c36..24c6df0 100644 --- a/src/components/charts/GithubContributionChart.js +++ b/src/components/charts/GithubContributionChart.js @@ -91,7 +91,7 @@ const GithubContributionChart = ({ username }) => { {/* Days of week labels */} <div className="flex flex-col gap-[3px] text-[11px] text-gray-400 pr-3"> {weekDays.map((day, index) => ( - <div key={day} className="h-[12px] leading-[12px]"> + <div key={day} className="h-[13px] leading-[13px]"> {index % 2 === 0 && day} </div> ))} @@ -105,7 +105,7 @@ const GithubContributionChart = ({ username }) => { row[weekIndex] && ( <div key={`${weekIndex}-${dayIndex}`} - className={`w-[12px] h-[12px] ${getColor(row[weekIndex].count)} rounded-[2px] cursor-pointer transition-colors duration-100`} + className={`w-[13px] h-[13px] ${getColor(row[weekIndex].count)} rounded-[2px] cursor-pointer transition-colors duration-100`} title={`${row[weekIndex].date.toDateString()}: ${row[weekIndex].count} contribution${row[weekIndex].count !== 1 ? 's' : ''}`} ></div> ) @@ -130,11 +130,11 @@ const GithubContributionChart = ({ username }) => { <div className="text-[11px] text-gray-400 flex items-center justify-end"> <span className="mr-2">Less</span> <div className="flex gap-[3px]"> - <div className="w-[12px] h-[12px] bg-gray-100 rounded-[2px]"></div> - <div className="w-[12px] h-[12px] bg-green-300 rounded-[2px]"></div> - <div className="w-[12px] h-[12px] bg-green-400 rounded-[2px]"></div> - <div className="w-[12px] h-[12px] bg-green-600 rounded-[2px]"></div> - <div className="w-[12px] h-[12px] bg-green-700 rounded-[2px]"></div> + <div className="w-[13px] h-[13px] bg-gray-100 rounded-[2px]"></div> + <div className="w-[13px] h-[13px] bg-green-300 rounded-[2px]"></div> + <div className="w-[13px] h-[13px] bg-green-400 rounded-[2px]"></div> + <div className="w-[13px] h-[13px] bg-green-600 rounded-[2px]"></div> + <div className="w-[13px] h-[13px] bg-green-700 rounded-[2px]"></div> </div> <span className="ml-2">More</span> </div> diff --git a/src/components/profile/DataTables/UserProgressTable.js b/src/components/profile/DataTables/UserProgressTable.js index 431aa96..13cf250 100644 --- a/src/components/profile/DataTables/UserProgressTable.js +++ b/src/components/profile/DataTables/UserProgressTable.js @@ -13,16 +13,29 @@ const UserProgressTable = ({ session, ndk, windowWidth }) => { const progressData = []; session.user.userCourses.forEach(courseProgress => { - progressData.push({ - id: courseProgress.id, - type: 'course', - name: courseProgress.course?.name, - started: courseProgress.started, - startedAt: courseProgress.startedAt, - completed: courseProgress.completed, - completedAt: courseProgress.completedAt, - courseId: courseProgress.courseId - }); + // Add course start entry + if (courseProgress.started) { + progressData.push({ + id: `${courseProgress.id}-start`, + type: 'course', + name: courseProgress.course?.name, + eventType: 'started', + date: courseProgress.startedAt, + courseId: courseProgress.courseId + }); + } + + // Add course completion entry + if (courseProgress.completed) { + progressData.push({ + id: `${courseProgress.id}-complete`, + type: 'course', + name: courseProgress.course?.name, + eventType: 'completed', + date: courseProgress.completedAt, + courseId: courseProgress.courseId + }); + } // Add lesson entries const courseLessons = session.user.userLessons?.filter( @@ -30,22 +43,38 @@ const UserProgressTable = ({ session, ndk, windowWidth }) => { ) || []; courseLessons.forEach(lessonProgress => { - progressData.push({ - id: lessonProgress.id, - type: 'lesson', - name: lessonProgress.lesson?.name, - started: lessonProgress.opened, - startedAt: lessonProgress.openedAt, - completed: lessonProgress.completed, - completedAt: lessonProgress.completedAt, - courseId: courseProgress.courseId, - lessonId: lessonProgress.lessonId, - resourceId: lessonProgress.lesson?.resourceId - }); + // Add lesson start entry + if (lessonProgress.opened) { + progressData.push({ + id: `${lessonProgress.id}-start`, + type: 'lesson', + name: lessonProgress.lesson?.name, + eventType: 'started', + date: lessonProgress.openedAt, + courseId: courseProgress.courseId, + lessonId: lessonProgress.lessonId, + resourceId: lessonProgress.lesson?.resourceId + }); + } + + // Add lesson completion entry + if (lessonProgress.completed) { + progressData.push({ + id: `${lessonProgress.id}-complete`, + type: 'lesson', + name: lessonProgress.lesson?.name, + eventType: 'completed', + date: lessonProgress.completedAt, + courseId: courseProgress.courseId, + lessonId: lessonProgress.lessonId, + resourceId: lessonProgress.lesson?.resourceId + }); + } }); }); - return progressData; + // Sort by date, most recent first + return progressData.sort((a, b) => new Date(b.date) - new Date(a.date)); }; const header = ( @@ -54,6 +83,37 @@ const UserProgressTable = ({ session, ndk, windowWidth }) => { </div> ); + const typeTemplate = (rowData) => ( + <div className="flex items-center gap-2"> + <i className={`pi ${rowData.type === 'course' ? 'pi-book' : 'pi-file'} text-lg`}></i> + <span className="capitalize">{rowData.type}</span> + </div> + ); + + const eventTemplate = (rowData) => ( + <div className="flex items-center gap-2"> + <i className={`pi ${rowData.eventType === 'started' ? 'pi-play' : 'pi-check-circle'} + ${rowData.eventType === 'started' ? 'text-blue-500' : 'text-green-500'} text-lg`}></i> + <span className="capitalize">{rowData.eventType}</span> + </div> + ); + + const nameTemplate = (rowData) => ( + <div className="flex items-center"> + {rowData.type === 'course' + ? <ProgressListItem dTag={rowData.courseId} category="name" type="course" /> + : <ProgressListItem dTag={rowData.resourceId} category="name" type="lesson" /> + } + </div> + ); + + const dateTemplate = (rowData) => ( + <div className="flex items-center gap-2"> + <i className="pi pi-calendar text-gray-400"></i> + <span>{formatDateTime(rowData.date)}</span> + </div> + ); + if (!session || !session?.user || !ndk) { return <div className='w-full h-full flex items-center justify-center'><ProgressSpinner /></div>; } @@ -63,60 +123,45 @@ const UserProgressTable = ({ session, ndk, windowWidth }) => { emptyMessage="No Courses or Milestones completed" value={prepareProgressData()} header={header} - style={{ maxWidth: windowWidth < 768 ? "100%" : "90%", margin: "0 auto", borderRadius: "10px" }} + style={{ maxWidth: windowWidth < 768 ? "100%" : "90%", margin: "0 auto" }} pt={{ wrapper: { - className: "rounded-lg rounded-t-none" + className: "rounded-b-lg shadow-md" }, header: { - className: "rounded-t-lg" + className: "rounded-t-lg border-b border-gray-700" + }, + th: { + className: "text-gray-300 font-semibold" + }, + bodyRow: { + className: "border-b border-gray-700" + }, + bodyCell: { + className: "text-gray-200 p-4" } }} + stripedRows > <Column field="type" header="Type" - body={(rowData) => ( - <span>{rowData.type}</span> - )} + body={typeTemplate} ></Column> <Column - field="started" - header="Started" - body={(rowData) => ( - <i className={classNames('pi', { - 'pi-check-circle text-blue-500': rowData.started, - 'pi-times-circle text-gray-500': !rowData.started - })}></i> - )} - ></Column> - <Column - field="completed" - header="Completed" - body={(rowData) => ( - <i className={classNames('pi', { - 'pi-check-circle text-green-500': rowData.completed, - 'pi-times-circle text-red-500': !rowData.completed - })}></i> - )} + field="eventType" + header="Event" + body={eventTemplate} ></Column> <Column field="name" header="Name" - body={(rowData) => ( - rowData.type === 'course' - ? <ProgressListItem dTag={rowData.courseId} category="name" type="course" /> - : <ProgressListItem dTag={rowData.resourceId} category="name" type="lesson" /> - )} + body={nameTemplate} ></Column> <Column - body={rowData => { - if (rowData.completed) { - return formatDateTime(rowData.completedAt); - } - return formatDateTime(rowData.startedAt) || formatDateTime(rowData.createdAt); - }} - header="Last Activity" + field="date" + body={dateTemplate} + header="Date" ></Column> </DataTable> ); diff --git a/src/components/profile/DataTables/UserPurchaseTable.js b/src/components/profile/DataTables/UserPurchaseTable.js new file mode 100644 index 0000000..bdac7d8 --- /dev/null +++ b/src/components/profile/DataTables/UserPurchaseTable.js @@ -0,0 +1,95 @@ +import React from 'react'; +import { DataTable } from "primereact/datatable"; +import { Column } from "primereact/column"; +import PurchasedListItem from "@/components/content/lists/PurchasedListItem"; +import { formatDateTime } from "@/utils/time"; + +const UserPurchaseTable = ({ session, windowWidth }) => { + const purchasesHeader = ( + <div className="flex flex-wrap align-items-center justify-content-between gap-2"> + <span className="text-xl text-gray-200 font-bold">Purchases</span> + </div> + ); + + const costTemplate = (rowData) => ( + <div className="flex items-center gap-2"> + <i className="pi pi-wallet text-yellow-500 text-lg"></i> + <span>{rowData.amountPaid} sats</span> + </div> + ); + + const nameTemplate = (rowData) => ( + <div className="flex items-center"> + <PurchasedListItem + eventId={rowData?.resource?.noteId || rowData?.course?.noteId} + category={rowData?.course ? "courses" : "resources"} + /> + </div> + ); + + const categoryTemplate = (rowData) => ( + <div className="flex items-center gap-2"> + <i className={`pi ${rowData?.course ? 'pi-book' : 'pi-file'} text-lg`}></i> + <span className="capitalize">{rowData?.course ? 'course' : 'resource'}</span> + </div> + ); + + const dateTemplate = (rowData) => ( + <div className="flex items-center gap-2"> + <i className="pi pi-calendar text-gray-400"></i> + <span>{formatDateTime(rowData?.createdAt)}</span> + </div> + ); + + return ( + session && session?.user && ( + <DataTable + emptyMessage="No purchases" + value={session.user?.purchased} + header={purchasesHeader} + style={{ maxWidth: windowWidth < 768 ? "100%" : "90%", margin: "0 auto", marginTop: "2rem" }} + pt={{ + wrapper: { + className: "rounded-b-lg shadow-md" + }, + header: { + className: "rounded-t-lg border-b border-gray-700" + }, + th: { + className: "text-gray-300 font-semibold" + }, + bodyRow: { + className: "border-b border-gray-700" + }, + bodyCell: { + className: "text-gray-200 p-4" + } + }} + stripedRows + > + <Column + field="amountPaid" + header="Cost" + body={costTemplate} + ></Column> + <Column + field="name" + header="Name" + body={nameTemplate} + ></Column> + <Column + field="category" + header="Category" + body={categoryTemplate} + ></Column> + <Column + field="createdAt" + header="Date" + body={dateTemplate} + ></Column> + </DataTable> + ) + ); +}; + +export default UserPurchaseTable; diff --git a/src/components/profile/UserProfile.js b/src/components/profile/UserProfile.js index c7251f8..a83e410 100644 --- a/src/components/profile/UserProfile.js +++ b/src/components/profile/UserProfile.js @@ -20,6 +20,7 @@ import { useToast } from "@/hooks/useToast"; import UserProgress from "@/components/profile/progress/UserProgress"; import { classNames } from "primereact/utils"; import UserProgressTable from '@/components/profile/DataTables/UserProgressTable'; +import UserPurchaseTable from '@/components/profile/DataTables/UserPurchaseTable'; const UserProfile = () => { const windowWidth = useWindowWidth(); @@ -103,14 +104,14 @@ const UserProfile = () => { className="rounded-full my-4" /> <div className="absolute top-8 right-80 max-tab:right-20 max-mob:left-0"> - <i + <i className="pi pi-ellipsis-h text-2xl cursor-pointer" onClick={(e) => menu.current.toggle(e)} /> - <Menu - model={menuItems} - popup - ref={menu} + <Menu + model={menuItems} + popup + ref={menu} id="profile-options-menu" /> </div> @@ -143,37 +144,15 @@ const UserProfile = () => { )} <UserProgress /> </div> - <UserProgressTable - session={session} - ndk={ndk} - windowWidth={windowWidth} - /> - {session && session?.user && ( - <DataTable - emptyMessage="No purchases" - value={session.user?.purchased} - header={purchasesHeader} - style={{ maxWidth: windowWidth < 768 ? "100%" : "90%", margin: "0 auto", borderRadius: "10px" }} - pt={{ - wrapper: { - className: "rounded-lg rounded-t-none" - }, - header: { - className: "rounded-t-lg mt-4" - } - }} - > - <Column field="amountPaid" header="Cost"></Column> - <Column - body={(rowData) => { - return <PurchasedListItem eventId={rowData?.resource?.noteId || rowData?.course?.noteId} category={rowData?.course ? "courses" : "resources"} /> - }} - header="Name" - ></Column> - <Column body={session.user?.purchased?.some((item) => item.courseId) ? "course" : "resource"} header="Category"></Column> - <Column body={rowData => formatDateTime(rowData?.createdAt)} header="Date"></Column> - </DataTable> - )} + <UserProgressTable + session={session} + ndk={ndk} + windowWidth={windowWidth} + /> + <UserPurchaseTable + session={session} + windowWidth={windowWidth} + /> </div> ) ); diff --git a/src/hooks/tracking/useTrackVideoLesson.js b/src/hooks/tracking/useTrackVideoLesson.js index c2a5af2..9a77669 100644 --- a/src/hooks/tracking/useTrackVideoLesson.js +++ b/src/hooks/tracking/useTrackVideoLesson.js @@ -85,7 +85,7 @@ const useTrackVideoLesson = ({lessonId, videoDuration, courseId, videoPlayed, pa timerRef.current = setInterval(() => { setTimeSpent(prevTime => { const newTime = prevTime + 1; - // console.log(`⏱️ Time spent: ${newTime}s / ${videoDuration}s (${((newTime/videoDuration)*100).toFixed(1)}%)`); + console.log(`⏱️ Time spent: ${newTime}s / ${videoDuration}s (${((newTime/videoDuration)*100).toFixed(1)}%)`); return newTime; }); }, 1000);