aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Nanis2025-06-10 05:21:19 +0700
committerMax Nanis2025-06-10 05:21:19 +0700
commitd7bb480ab6bd2172a04ecb304d012206e0c03e8f (patch)
tree783c57ea92e9bf50aec460e2e9b4a9e0ef7a512b
parentabec6d734cde8a2bb0924989b1c5801e924137c3 (diff)
downloadpanel-ui-d7bb480ab6bd2172a04ecb304d012206e0c03e8f.tar.gz
panel-ui-d7bb480ab6bd2172a04ecb304d012206e0c03e8f.zip
Adding taskStatus fetch. Showing list of Task Attempts & starting heatmap calendar.
-rw-r--r--src/Widget.tsx15
-rw-r--r--src/components/app-sidebar.tsx16
-rw-r--r--src/components/nav-main.tsx17
-rw-r--r--src/components/site-header.tsx4
-rw-r--r--src/models/app.ts2
-rw-r--r--src/models/taskStatusSlice.ts24
-rw-r--r--src/models/transactionHistorySlice.ts24
-rw-r--r--src/pages/TaskAttemptHistory.tsx129
-rw-r--r--src/pages/TransactionHistory.tsx24
-rw-r--r--src/store.ts6
10 files changed, 253 insertions, 8 deletions
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' && <Offerwall/>}
{app.currentPage === 'questions' && <QuestionsPage/>}
{app.currentPage === 'cashout_methods' && <CashoutMethodsPage/>}
+ {app.currentPage === 'task_attempts' && <TaskAttemptHistoryPage/>}
+
{app.currentPage === 'demographics' && <Demographics/>}
+ {app.currentPage === 'transaction_history' && <TransactionHistoryPage/>}
</div>
</div>
</div>
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<typeof Sidebar>) {
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<typeof Sidebar>) {
</SidebarMenuButton>
</SidebarMenuItem>
- <SidebarMenuItem key="cashout_history">
+ <SidebarMenuItem
+ key="transaction_hisotry"
+ className="cursor-pointer"
+ >
<SidebarMenuButton asChild>
- <a href="#">
+ <a
+ onClick={() => dispatch(setPage("transaction_history"))}
+ >
<SquareStack/>
- <span>History</span>
+ <span>History <Badge
+ className="absolute top-2 right-2 h-5 min-w-5 rounded-full px-1 font-mono tabular-nums cursor-pointer"
+ variant="outline"
+ >{transactionHistory.length.toLocaleString()}</Badge></span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
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 (
<SidebarGroup>
@@ -72,6 +73,20 @@ export function NavMain() {
</SidebarMenuButton>
</SidebarMenuItem>
+ <SidebarMenuItem key="task_attempts"
+ onClick={() => dispatch(setPage("task_attempts"))}
+ >
+ <SidebarMenuButton tooltip="Survey History">
+ <Activity/>
+ <span>
+ Survey History <Badge
+ className="absolute top-2 right-2 h-5 min-w-5 rounded-full px-1 font-mono tabular-nums cursor-pointer"
+ variant="outline"
+ >{taskAttempts.length.toLocaleString()}</Badge>
+ </span>
+ </SidebarMenuButton>
+ </SidebarMenuItem>
+
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
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 = () => {
<h1 className="text-base font-medium">
{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'}
</h1>
</div>
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<TaskStatusResponseOut[]>) {
+ return action.payload;
+ },
+ // taskStatusAdd(state, action: PayloadAction<TaskStatusResponseOut>) {
+ // 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<UserTransactionRow[]>) {
+ return action.payload;
+ },
+ transactionAdded(state, action: PayloadAction<UserTransactionRow>) {
+ 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<TaskStatusResponseOut>[] = [
+ {
+ 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 (
+ <div className="border rounded-md mt-5">
+ <Table>
+ <TableHeader>
+ {table.getHeaderGroups().map((headerGroup) => (
+ <TableRow key={headerGroup.id}>
+ {headerGroup.headers.map((header) => {
+ return (
+ <TableHead key={header.id}>
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+ </TableHead>
+ )
+ })}
+ </TableRow>
+ ))}
+ </TableHeader>
+
+ <TableBody>
+ {
+ table.getRowModel().rows.map((row) => (
+ <TableRow
+ key={row.id}
+ data-state={row.getIsSelected() && "selected"}
+ >
+ {row.getVisibleCells().map((cell) => (
+ <TableCell key={cell.id}>
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ </TableCell>
+ ))}
+ </TableRow>
+ ))
+ }
+ </TableBody>
+ </Table>
+ </div>
+ )
+}
+
+const TaskAttemptHistoryPage = () => {
+ const taskStatuses = useAppSelector(state => state.taskStatus)
+
+ const countByDate: Record<string, number> = taskStatuses.reduce((acc, item) => {
+ const date: string = item.started.split("T")[0]
+ acc[date] = (acc[date] || 0) + 1
+ return acc
+ }, {} as Record<string, number>)
+
+ return (
+ <>
+ <Calendar
+ mode="single"
+ numberOfMonths={2}
+ className="rounded-lg border shadow-sm"
+
+ // dayPickerProps={{
+ // modifiersClassNames: {
+ // highlighted: "bg-blue",
+ // },
+ // modifiers: {
+ // highlighted: (date: Date) =>
+ // !!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"
+ // }}
+ />
+ <TaskAttemptTable/>
+ </>
+ )
+}
+
+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
}
})