From d7bb480ab6bd2172a04ecb304d012206e0c03e8f Mon Sep 17 00:00:00 2001 From: Max Nanis Date: Tue, 10 Jun 2025 05:21:19 +0700 Subject: Adding taskStatus fetch. Showing list of Task Attempts & starting heatmap calendar. --- src/Widget.tsx | 15 +++- src/components/app-sidebar.tsx | 16 ++++- src/components/nav-main.tsx | 17 ++++- src/components/site-header.tsx | 4 +- src/models/app.ts | 2 +- src/models/taskStatusSlice.ts | 24 +++++++ src/models/transactionHistorySlice.ts | 24 +++++++ src/pages/TaskAttemptHistory.tsx | 129 ++++++++++++++++++++++++++++++++++ src/pages/TransactionHistory.tsx | 24 +++++++ src/store.ts | 6 +- 10 files changed, 253 insertions(+), 8 deletions(-) create mode 100644 src/models/taskStatusSlice.ts create mode 100644 src/models/transactionHistorySlice.ts create mode 100644 src/pages/TaskAttemptHistory.tsx create mode 100644 src/pages/TransactionHistory.tsx (limited to 'src') diff --git a/src/Widget.tsx b/src/Widget.tsx index 16dba81..0fc0dca 100644 --- a/src/Widget.tsx +++ b/src/Widget.tsx @@ -5,6 +5,9 @@ import {SidebarInset, SidebarProvider} from "@/components/ui/sidebar" import {Offerwall} from "@/pages/Offerwall.tsx" import {QuestionsPage} from "@/pages/Questions.tsx"; import {Demographics} from "@/pages/Demographics.tsx" +import {CashoutMethodsPage} from "@/pages/CashoutMethods.tsx"; +import {TransactionHistoryPage} from "@/pages/TransactionHistory.tsx" +import {TaskAttemptHistoryPage} from "@/pages/TaskAttemptHistory.tsx"; import {useAppDispatch, useAppSelector} from "@/hooks.ts"; import { @@ -14,6 +17,7 @@ import { ProductUserApi, ProfilingQuestionsApi, QuestionInfo, + StatusApi, UserProfileKnowledge, UserWalletBalance, WalletApi @@ -22,12 +26,12 @@ import {ProfileQuestion, setQuestions} from "@/models/questionSlice.ts"; import {setBuckets} from "@/models/bucketSlice.ts"; import {setCashoutMethods} from "@/models/cashoutMethodSlice.ts"; import {setWallet} from "@/models/walletSlice.ts" -import {CashoutMethodsPage} from "@/pages/CashoutMethods.tsx"; import {setUpkQuestions} from "@/models/upkQuestionSlice.ts" import {setAvailabilityCount, setOfferwallId} from "@/models/appSlice.ts" import {setUpkAnswers} from "@/models/userUpkAnswerSlice.ts"; import {setMarketplaceAnswers} from "@/models/userMarketplaceAnswerSlice.ts"; import {setUserProfile} from "@/models/userProfileSlice.ts"; +import {setTaskStatuses} from "@/models/taskStatusSlice.ts" import './index.css'; @@ -52,6 +56,12 @@ const Widget = () => { }) .catch(err => console.log(err)); + new StatusApi().listTaskStatusesProductIdStatusGet(app.bpid, app.bpuid) + .then(res => { + dispatch(setTaskStatuses(res.data.tasks_status)) + }) + .catch(err => console.log(err)) + new ProductUserApi().userProfileProductIdUserProductUserIdProfileGet(app.bpid, app.bpuid) .then(res => { dispatch(setUserProfile(res.data["user-profile"])) @@ -106,7 +116,10 @@ const Widget = () => { {app.currentPage === 'offerwall' && } {app.currentPage === 'questions' && } {app.currentPage === 'cashout_methods' && } + {app.currentPage === 'task_attempts' && } + {app.currentPage === 'demographics' && } + {app.currentPage === 'transaction_history' && } diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index f314c9f..9a13aff 100644 --- a/src/components/app-sidebar.tsx +++ b/src/components/app-sidebar.tsx @@ -21,11 +21,13 @@ import {setPage} from "@/models/appSlice.ts"; import {Badge} from "@/components/ui/badge.tsx"; import {useSelector} from "react-redux"; import {selectCashoutMethods} from "@/models/cashoutMethodSlice.ts"; +import {selectTransactionHistory} from "@/models/transactionHistorySlice.ts"; export function AppSidebar({...props}: React.ComponentProps) { const app = useAppSelector(state => state.app) const dispatch = useAppDispatch() const cashoutMethods = useSelector(selectCashoutMethods) + const transactionHistory = useSelector(selectTransactionHistory) const {isMobile} = useSidebar() @@ -76,11 +78,19 @@ export function AppSidebar({...props}: React.ComponentProps) { - + - + dispatch(setPage("transaction_history"))} + > - History + History {transactionHistory.length.toLocaleString()} diff --git a/src/components/nav-main.tsx b/src/components/nav-main.tsx index 949ced3..9ed1b24 100644 --- a/src/components/nav-main.tsx +++ b/src/components/nav-main.tsx @@ -1,6 +1,6 @@ "use client" -import {ListIcon, NotebookText, Users, User} from "lucide-react" +import {ListIcon, NotebookText, Users, User, Activity} from "lucide-react" import { SidebarGroup, SidebarGroupContent, @@ -21,6 +21,7 @@ export function NavMain() { const app = useAppSelector(state => state.app) const questions = useSelector(selectQuestions) const upkAnswers = useSelector(selectUserUpkAnswers) + const taskAttempts = useAppSelector(state => state.taskStatus) return ( @@ -72,6 +73,20 @@ export function NavMain() { + dispatch(setPage("task_attempts"))} + > + + + + Survey History {taskAttempts.length.toLocaleString()} + + + + diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx index f3e8851..25b3e52 100644 --- a/src/components/site-header.tsx +++ b/src/components/site-header.tsx @@ -20,8 +20,10 @@ const SiteHeader = () => {

{app.currentPage === 'offerwall' && "Offerwall"} {app.currentPage === 'questions' && "Profiling Questions"} - {app.currentPage === 'cashouts' && "Cashout Methods"} {app.currentPage === 'demographics' && "User Demographics"} + {app.currentPage === 'task_attempts' && "Task Attempts"} + {app.currentPage === 'cashouts' && "Cashout Methods"} + {app.currentPage === 'transaction_history' && 'Transaction History'}

diff --git a/src/models/app.ts b/src/models/app.ts index 27923db..8ae71ec 100644 --- a/src/models/app.ts +++ b/src/models/app.ts @@ -1,4 +1,4 @@ -export type Page = 'offerwall' | 'questions' | 'demographics' | 'cashout_methods'; +export type Page = 'offerwall' | 'questions' | 'demographics' | 'task_attempts' | 'cashout_methods' | 'transaction_history'; export interface App { targetId: string, diff --git a/src/models/taskStatusSlice.ts b/src/models/taskStatusSlice.ts new file mode 100644 index 0000000..41fc0b4 --- /dev/null +++ b/src/models/taskStatusSlice.ts @@ -0,0 +1,24 @@ +import {createSlice, PayloadAction} from '@reduxjs/toolkit' +import {TaskStatusResponseOut} from "@/api"; +import type {RootState} from "@/store.ts"; + + +const initialState: TaskStatusResponseOut[] = [] + +const transactionHistorySlice = createSlice({ + name: 'taskStatus', + initialState, + reducers: { + setTaskStatuses(state, action: PayloadAction) { + return action.payload; + }, + // taskStatusAdd(state, action: PayloadAction) { + // state.push(action.payload); + // } + } +}) + +export const {setTaskStatuses} = transactionHistorySlice.actions; +export default transactionHistorySlice.reducer + +export const selectTransactionHistory = (state: RootState) => state.taskStatus diff --git a/src/models/transactionHistorySlice.ts b/src/models/transactionHistorySlice.ts new file mode 100644 index 0000000..71bbf0d --- /dev/null +++ b/src/models/transactionHistorySlice.ts @@ -0,0 +1,24 @@ +import {createSlice, PayloadAction} from '@reduxjs/toolkit' +import {UserTransactionRow} from "@/api"; +import type {RootState} from "@/store.ts"; + + +const initialState: UserTransactionRow[] = [] + +const transactionHistorySlice = createSlice({ + name: 'transactionHistory', + initialState, + reducers: { + setBuckets(state, action: PayloadAction) { + return action.payload; + }, + transactionAdded(state, action: PayloadAction) { + state.push(action.payload); + } + } +}) + +export const {setBuckets, bucketAdded} = transactionHistorySlice.actions; +export default transactionHistorySlice.reducer + +export const selectTransactionHistory = (state: RootState) => state.transactionHistory diff --git a/src/pages/TaskAttemptHistory.tsx b/src/pages/TaskAttemptHistory.tsx new file mode 100644 index 0000000..311c32e --- /dev/null +++ b/src/pages/TaskAttemptHistory.tsx @@ -0,0 +1,129 @@ +import {useAppSelector} from "@/hooks.ts"; +import React from "react"; +import {ColumnDef, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table"; +import {TaskStatusResponseOut} from "@/api"; +import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table.tsx"; +import {formatDistanceToNow} from 'date-fns' +import {formatSecondsVerbose} from "@/lib/utils.ts"; +import {Calendar} from "@/components/ui/calendar" + +export const TaskAttemptTable = () => { + const data = useAppSelector(state => state.taskStatus) + + const columns: ColumnDef[] = [ + { + accessorKey: "started", + header: "Started", + cell: ({getValue}) => formatDistanceToNow(new Date(getValue()), {addSuffix: true}) + }, + { + accessorKey: "finished", + header: "Duration", + cell: ({row}) => { + const started = new Date(row.original.started); + const finished = new Date(row.original.finished); + const durationInSec = Math.floor((finished.getTime() - started.getTime()) / 1000); + return formatSecondsVerbose(durationInSec); + } + }, { + accessorKey: "status", + header: "Status", + }, { + accessorKey: "status_code_1", + header: "Status 1", + }, { + accessorKey: "status_code_2", + header: "Status 2", + } + ] + + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + }) + + return ( +
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ) + })} + + ))} + + + + { + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + } + +
+
+ ) +} + +const TaskAttemptHistoryPage = () => { + const taskStatuses = useAppSelector(state => state.taskStatus) + + const countByDate: Record = taskStatuses.reduce((acc, item) => { + const date: string = item.started.split("T")[0] + acc[date] = (acc[date] || 0) + 1 + return acc + }, {} as Record) + + return ( + <> + + // !!countByDate[date.toISOString().split("T")[0]], + // }, + // showOutsideDays: true, + // }} + + // dayClassName={(date) => { + // const key = date.toISOString().split("T")[0] + // console.log(key) + // return (countByDate[key] ?? 0) > 0 ? "bg-blue-200 text-blue-800" : "bg-white" + // }} + /> + + + ) +} + +export { + TaskAttemptHistoryPage +} \ No newline at end of file diff --git a/src/pages/TransactionHistory.tsx b/src/pages/TransactionHistory.tsx new file mode 100644 index 0000000..a99dd8b --- /dev/null +++ b/src/pages/TransactionHistory.tsx @@ -0,0 +1,24 @@ +import {useAppDispatch, useAppSelector} from "@/hooks.ts"; +import {Pagination, PaginationContent, PaginationItem, PaginationNext} from "@/components/ui/pagination.tsx"; +import React from "react"; +import {ProfileQuestionFull} from "@/pages/Questions.tsx"; + +const TransactionHistoryPage = () => { + const dispatch = useAppDispatch() + const transactionHistory = useAppSelector(state => state.transactionHistory) + + + return ( + <> + { + transactionHistory.map(th => { + + }) + } + + ) +} + +export { + TransactionHistoryPage +} \ No newline at end of file diff --git a/src/store.ts b/src/store.ts index 7ff1bfc..db283f8 100644 --- a/src/store.ts +++ b/src/store.ts @@ -10,6 +10,8 @@ import upkQuestionReducers from "@/models/upkQuestionSlice" import userUpkAnswerReducers from "@/models/userUpkAnswerSlice.ts" import userMarketplaceReducers from "@/models/userMarketplaceAnswerSlice.ts" import userProfileReducers from "@/models/userProfileSlice.ts" +import transactionHistoryReducers from "@/models/transactionHistorySlice.ts" +import taskStatusReducers from "@/models/taskStatusSlice.ts" export const store = configureStore({ reducer: { @@ -18,6 +20,7 @@ export const store = configureStore({ // - Read Only // -- These act as API cache stores to allow background loading buckets: bucketReducers, + taskStatus: taskStatusReducers, questions: questionReducers, upkQuestions: upkQuestionReducers, @@ -31,7 +34,8 @@ export const store = configureStore({ answers: answerReducers, cashoutMethods: cashoutMethodReducers, - wallet: walletReducers + wallet: walletReducers, + transactionHistory: transactionHistoryReducers } }) -- cgit v1.2.3