feat(pagination): paginate activity and progress lists
This commit is contained in:
@@ -407,7 +407,10 @@ func (api *API) koCheckDocumentsSync(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
wantedDocs, err := api.db.Queries.GetWantedDocuments(c, string(jsonHaves))
|
||||
wantedDocs, err := api.db.Queries.GetWantedDocuments(c, database.GetWantedDocumentsParams{
|
||||
JsonEach: string(jsonHaves),
|
||||
DocumentIds: string(jsonHaves),
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("GetWantedDocuments DB Error", err)
|
||||
apiErrorPage(c, http.StatusBadRequest, "Invalid Request")
|
||||
|
||||
@@ -25,12 +25,12 @@ func (s *Server) GetActivity(ctx context.Context, request GetActivityRequestObje
|
||||
documentID = *request.Params.DocumentId
|
||||
}
|
||||
|
||||
offset := int64(0)
|
||||
if request.Params.Offset != nil {
|
||||
offset = *request.Params.Offset
|
||||
page := int64(1)
|
||||
if request.Params.Page != nil {
|
||||
page = *request.Params.Page
|
||||
}
|
||||
|
||||
limit := int64(100)
|
||||
limit := int64(25)
|
||||
if request.Params.Limit != nil {
|
||||
limit = *request.Params.Limit
|
||||
}
|
||||
@@ -39,13 +39,33 @@ func (s *Server) GetActivity(ctx context.Context, request GetActivityRequestObje
|
||||
UserID: auth.UserName,
|
||||
DocFilter: docFilter,
|
||||
DocumentID: documentID,
|
||||
Offset: offset,
|
||||
Offset: (page - 1) * limit,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return GetActivity500JSONResponse{Code: 500, Message: err.Error()}, nil
|
||||
}
|
||||
|
||||
// Get Total Count
|
||||
total, err := s.db.Queries.GetActivityCount(ctx, database.GetActivityCountParams{
|
||||
UserID: auth.UserName,
|
||||
DocFilter: docFilter,
|
||||
DocumentID: documentID,
|
||||
})
|
||||
if err != nil {
|
||||
return GetActivity500JSONResponse{Code: 500, Message: err.Error()}, nil
|
||||
}
|
||||
|
||||
// Calculate Pagination
|
||||
var nextPage *int64
|
||||
var previousPage *int64
|
||||
if page*limit < total {
|
||||
nextPage = ptrOf(page + 1)
|
||||
}
|
||||
if page > 1 {
|
||||
previousPage = ptrOf(page - 1)
|
||||
}
|
||||
|
||||
apiActivities := make([]Activity, len(activities))
|
||||
for i, a := range activities {
|
||||
// Convert StartTime from interface{} to string
|
||||
@@ -70,7 +90,12 @@ func (s *Server) GetActivity(ctx context.Context, request GetActivityRequestObje
|
||||
}
|
||||
|
||||
response := ActivityResponse{
|
||||
Activities: apiActivities,
|
||||
Activities: apiActivities,
|
||||
Page: page,
|
||||
Limit: limit,
|
||||
Total: total,
|
||||
NextPage: nextPage,
|
||||
PreviousPage: previousPage,
|
||||
}
|
||||
return GetActivity200JSONResponse(response), nil
|
||||
}
|
||||
|
||||
@@ -158,7 +158,12 @@ type Activity struct {
|
||||
|
||||
// ActivityResponse defines model for ActivityResponse.
|
||||
type ActivityResponse struct {
|
||||
Activities []Activity `json:"activities"`
|
||||
Activities []Activity `json:"activities"`
|
||||
Limit int64 `json:"limit"`
|
||||
NextPage *int64 `json:"next_page,omitempty"`
|
||||
Page int64 `json:"page"`
|
||||
PreviousPage *int64 `json:"previous_page,omitempty"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
// BackupType defines model for BackupType.
|
||||
@@ -470,7 +475,7 @@ type UsersResponse struct {
|
||||
type GetActivityParams struct {
|
||||
DocFilter *bool `form:"doc_filter,omitempty" json:"doc_filter,omitempty"`
|
||||
DocumentId *string `form:"document_id,omitempty" json:"document_id,omitempty"`
|
||||
Offset *int64 `form:"offset,omitempty" json:"offset,omitempty"`
|
||||
Page *int64 `form:"page,omitempty" json:"page,omitempty"`
|
||||
Limit *int64 `form:"limit,omitempty" json:"limit,omitempty"`
|
||||
}
|
||||
|
||||
@@ -740,11 +745,11 @@ func (siw *ServerInterfaceWrapper) GetActivity(w http.ResponseWriter, r *http.Re
|
||||
return
|
||||
}
|
||||
|
||||
// ------------- Optional query parameter "offset" -------------
|
||||
// ------------- Optional query parameter "page" -------------
|
||||
|
||||
err = runtime.BindQueryParameterWithOptions("form", true, false, "offset", r.URL.Query(), ¶ms.Offset, runtime.BindQueryParameterOptions{Type: "integer", Format: "int64"})
|
||||
err = runtime.BindQueryParameterWithOptions("form", true, false, "page", r.URL.Query(), ¶ms.Page, runtime.BindQueryParameterOptions{Type: "integer", Format: "int64"})
|
||||
if err != nil {
|
||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "offset", Err: err})
|
||||
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "page", Err: err})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -33,16 +33,16 @@ func (s *Server) GetDocuments(ctx context.Context, request GetDocumentsRequestOb
|
||||
limit = *request.Params.Limit
|
||||
}
|
||||
|
||||
search := ""
|
||||
if request.Params.Search != nil {
|
||||
search = "%" + *request.Params.Search + "%"
|
||||
var search *string
|
||||
if request.Params.Search != nil && *request.Params.Search != "" {
|
||||
search = ptrOf("%" + *request.Params.Search + "%")
|
||||
}
|
||||
|
||||
rows, err := s.db.Queries.GetDocumentsWithStats(
|
||||
ctx,
|
||||
database.GetDocumentsWithStatsParams{
|
||||
UserID: auth.UserName,
|
||||
Query: &search,
|
||||
Query: search,
|
||||
Deleted: ptrOf(false),
|
||||
Offset: (page - 1) * limit,
|
||||
Limit: limit,
|
||||
@@ -52,7 +52,19 @@ func (s *Server) GetDocuments(ctx context.Context, request GetDocumentsRequestOb
|
||||
return GetDocuments500JSONResponse{Code: 500, Message: err.Error()}, nil
|
||||
}
|
||||
|
||||
total := int64(len(rows))
|
||||
// Get Total Count
|
||||
total, err := s.db.Queries.GetDocumentsWithStatsCount(
|
||||
ctx,
|
||||
database.GetDocumentsWithStatsCountParams{
|
||||
Query: search,
|
||||
Deleted: ptrOf(false),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return GetDocuments500JSONResponse{Code: 500, Message: err.Error()}, nil
|
||||
}
|
||||
|
||||
// Calculate Pagination
|
||||
var nextPage *int64
|
||||
var previousPage *int64
|
||||
if page*limit < total {
|
||||
@@ -219,7 +231,6 @@ func (s *Server) EditDocument(ctx context.Context, request EditDocumentRequestOb
|
||||
|
||||
doc := docs[0]
|
||||
|
||||
|
||||
apiDoc := Document{
|
||||
Id: doc.ID,
|
||||
Title: *doc.Title,
|
||||
@@ -561,7 +572,6 @@ func (s *Server) UploadDocumentCover(ctx context.Context, request UploadDocument
|
||||
|
||||
doc := docs[0]
|
||||
|
||||
|
||||
apiDoc := Document{
|
||||
Id: doc.ID,
|
||||
Title: *doc.Title,
|
||||
|
||||
@@ -350,8 +350,26 @@ components:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Activity'
|
||||
page:
|
||||
type: integer
|
||||
format: int64
|
||||
limit:
|
||||
type: integer
|
||||
format: int64
|
||||
next_page:
|
||||
type: integer
|
||||
format: int64
|
||||
previous_page:
|
||||
type: integer
|
||||
format: int64
|
||||
total:
|
||||
type: integer
|
||||
format: int64
|
||||
required:
|
||||
- activities
|
||||
- page
|
||||
- limit
|
||||
- total
|
||||
|
||||
Device:
|
||||
type: object
|
||||
@@ -1174,18 +1192,18 @@ paths:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: offset
|
||||
- name: page
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
default: 0
|
||||
default: 1
|
||||
- name: limit
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
default: 100
|
||||
default: 25
|
||||
security:
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
|
||||
@@ -2,7 +2,6 @@ package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -43,13 +42,21 @@ func (s *Server) GetProgressList(ctx context.Context, request GetProgressListReq
|
||||
return GetProgressList500JSONResponse{Code: 500, Message: "Database error"}, nil
|
||||
}
|
||||
|
||||
total := int64(len(progress))
|
||||
// Get Total Count
|
||||
total, err := s.db.Queries.GetProgressCount(ctx, database.GetProgressCountParams{
|
||||
UserID: auth.UserName,
|
||||
DocFilter: filter.DocFilter,
|
||||
DocumentID: filter.DocumentID,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("GetProgressCount DB Error:", err)
|
||||
return GetProgressList500JSONResponse{Code: 500, Message: "Database error"}, nil
|
||||
}
|
||||
|
||||
// Calculate Pagination
|
||||
var nextPage *int64
|
||||
var previousPage *int64
|
||||
|
||||
// Calculate total pages
|
||||
totalPages := int64(math.Ceil(float64(total) / float64(limit)))
|
||||
if page < totalPages {
|
||||
if page*limit < total {
|
||||
nextPage = ptrOf(page + 1)
|
||||
}
|
||||
if page > 1 {
|
||||
|
||||
@@ -396,3 +396,40 @@ SET
|
||||
isbn10 = COALESCE(excluded.isbn10, isbn10),
|
||||
isbn13 = COALESCE(excluded.isbn13, isbn13)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetDocumentsWithStatsCount :one
|
||||
SELECT COUNT(*) AS count
|
||||
FROM documents AS docs
|
||||
WHERE
|
||||
(docs.id = sqlc.narg('id') OR $id IS NULL)
|
||||
AND (docs.deleted = sqlc.narg(deleted) OR $deleted IS NULL)
|
||||
AND (
|
||||
(
|
||||
docs.title LIKE sqlc.narg('query') OR
|
||||
docs.author LIKE $query
|
||||
) OR $query IS NULL
|
||||
);
|
||||
|
||||
-- name: GetProgressCount :one
|
||||
SELECT COUNT(*) AS count
|
||||
FROM document_progress AS progress
|
||||
WHERE
|
||||
progress.user_id = $user_id
|
||||
AND (
|
||||
(
|
||||
CAST($doc_filter AS BOOLEAN) = TRUE
|
||||
AND document_id = $document_id
|
||||
) OR $doc_filter = FALSE
|
||||
);
|
||||
|
||||
-- name: GetActivityCount :one
|
||||
SELECT COUNT(*) AS count
|
||||
FROM activity
|
||||
WHERE
|
||||
activity.user_id = $user_id
|
||||
AND (
|
||||
(
|
||||
CAST($doc_filter AS BOOLEAN) = TRUE
|
||||
AND document_id = $document_id
|
||||
) OR $doc_filter = FALSE
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.29.0
|
||||
// sqlc v1.31.1
|
||||
// source: query.sql
|
||||
|
||||
package database
|
||||
@@ -264,6 +264,32 @@ func (q *Queries) GetActivity(ctx context.Context, arg GetActivityParams) ([]Get
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getActivityCount = `-- name: GetActivityCount :one
|
||||
SELECT COUNT(*) AS count
|
||||
FROM activity
|
||||
WHERE
|
||||
activity.user_id = ?1
|
||||
AND (
|
||||
(
|
||||
CAST(?2 AS BOOLEAN) = TRUE
|
||||
AND document_id = ?3
|
||||
) OR ?2 = FALSE
|
||||
)
|
||||
`
|
||||
|
||||
type GetActivityCountParams struct {
|
||||
UserID string `json:"user_id"`
|
||||
DocFilter bool `json:"doc_filter"`
|
||||
DocumentID string `json:"document_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetActivityCount(ctx context.Context, arg GetActivityCountParams) (int64, error) {
|
||||
row := q.db.QueryRowContext(ctx, getActivityCount, arg.UserID, arg.DocFilter, arg.DocumentID)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const getDailyReadStats = `-- name: GetDailyReadStats :many
|
||||
WITH RECURSIVE last_30_days AS (
|
||||
SELECT LOCAL_DATE(STRFTIME('%Y-%m-%dT%H:%M:%SZ', 'now'), timezone) AS date
|
||||
@@ -734,6 +760,33 @@ func (q *Queries) GetDocumentsWithStats(ctx context.Context, arg GetDocumentsWit
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getDocumentsWithStatsCount = `-- name: GetDocumentsWithStatsCount :one
|
||||
SELECT COUNT(*) AS count
|
||||
FROM documents AS docs
|
||||
WHERE
|
||||
(docs.id = ?1 OR ?1 IS NULL)
|
||||
AND (docs.deleted = ?2 OR ?2 IS NULL)
|
||||
AND (
|
||||
(
|
||||
docs.title LIKE ?3 OR
|
||||
docs.author LIKE ?3
|
||||
) OR ?3 IS NULL
|
||||
)
|
||||
`
|
||||
|
||||
type GetDocumentsWithStatsCountParams struct {
|
||||
ID *string `json:"id"`
|
||||
Deleted *bool `json:"-"`
|
||||
Query *string `json:"query"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetDocumentsWithStatsCount(ctx context.Context, arg GetDocumentsWithStatsCountParams) (int64, error) {
|
||||
row := q.db.QueryRowContext(ctx, getDocumentsWithStatsCount, arg.ID, arg.Deleted, arg.Query)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const getLastActivity = `-- name: GetLastActivity :one
|
||||
SELECT start_time
|
||||
FROM activity
|
||||
@@ -897,6 +950,32 @@ func (q *Queries) GetProgress(ctx context.Context, arg GetProgressParams) ([]Get
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getProgressCount = `-- name: GetProgressCount :one
|
||||
SELECT COUNT(*) AS count
|
||||
FROM document_progress AS progress
|
||||
WHERE
|
||||
progress.user_id = ?1
|
||||
AND (
|
||||
(
|
||||
CAST(?2 AS BOOLEAN) = TRUE
|
||||
AND document_id = ?3
|
||||
) OR ?2 = FALSE
|
||||
)
|
||||
`
|
||||
|
||||
type GetProgressCountParams struct {
|
||||
UserID string `json:"user_id"`
|
||||
DocFilter bool `json:"doc_filter"`
|
||||
DocumentID string `json:"document_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetProgressCount(ctx context.Context, arg GetProgressCountParams) (int64, error) {
|
||||
row := q.db.QueryRowContext(ctx, getProgressCount, arg.UserID, arg.DocFilter, arg.DocumentID)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const getUser = `-- name: GetUser :one
|
||||
SELECT id, pass, auth_hash, admin, timezone, created_at FROM users
|
||||
WHERE id = ?1 LIMIT 1
|
||||
@@ -1088,17 +1167,22 @@ WHERE (
|
||||
AND documents.filepath IS NULL
|
||||
)
|
||||
OR (documents.id IS NULL)
|
||||
OR CAST(?1 AS TEXT) != CAST(?1 AS TEXT)
|
||||
OR CAST(?2 AS TEXT) != CAST(?2 AS TEXT)
|
||||
`
|
||||
|
||||
type GetWantedDocumentsParams struct {
|
||||
JsonEach interface{} `json:"json_each"`
|
||||
DocumentIds string `json:"document_ids"`
|
||||
}
|
||||
|
||||
type GetWantedDocumentsRow struct {
|
||||
ID string `json:"id"`
|
||||
WantFile bool `json:"want_file"`
|
||||
WantMetadata bool `json:"want_metadata"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetWantedDocuments(ctx context.Context, documentIds string) ([]GetWantedDocumentsRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getWantedDocuments, documentIds)
|
||||
func (q *Queries) GetWantedDocuments(ctx context.Context, arg GetWantedDocumentsParams) ([]GetWantedDocumentsRow, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getWantedDocuments, arg.JsonEach, arg.DocumentIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
51
frontend/src/components/Pagination.tsx
Normal file
51
frontend/src/components/Pagination.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
interface PaginationProps {
|
||||
page: number;
|
||||
previousPage?: number;
|
||||
nextPage?: number;
|
||||
total?: number;
|
||||
limit?: number;
|
||||
onPageChange: (page: number) => void;
|
||||
}
|
||||
|
||||
export function Pagination({
|
||||
page,
|
||||
previousPage,
|
||||
nextPage,
|
||||
total,
|
||||
limit,
|
||||
onPageChange,
|
||||
}: PaginationProps) {
|
||||
if (!previousPage && !nextPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalPages = total && limit ? Math.ceil(total / limit) : undefined;
|
||||
|
||||
return (
|
||||
<div className="mt-4 flex w-full items-center justify-center gap-4 text-content">
|
||||
{previousPage && previousPage > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onPageChange(previousPage)}
|
||||
className="w-24 rounded bg-surface p-2 text-center text-sm font-medium shadow-lg hover:bg-surface-strong focus:outline-none"
|
||||
>
|
||||
◄
|
||||
</button>
|
||||
) : null}
|
||||
{totalPages ? (
|
||||
<span className="text-sm text-content-muted">
|
||||
Page {page} of {totalPages}
|
||||
</span>
|
||||
) : null}
|
||||
{nextPage && nextPage > 0 ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onPageChange(nextPage)}
|
||||
className="w-24 rounded bg-surface p-2 text-center text-sm font-medium shadow-lg hover:bg-surface-strong focus:outline-none"
|
||||
>
|
||||
►
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ export {
|
||||
InlineLoader,
|
||||
} from './Skeleton';
|
||||
export { LoadingState } from './LoadingState';
|
||||
export { Pagination } from './Pagination';
|
||||
|
||||
// Field components
|
||||
export { Field, FieldLabel, FieldValue, FieldActions } from './Field';
|
||||
|
||||
@@ -9,4 +9,9 @@ import type { Activity } from './activity';
|
||||
|
||||
export interface ActivityResponse {
|
||||
activities: Activity[];
|
||||
page: number;
|
||||
limit: number;
|
||||
next_page?: number;
|
||||
previous_page?: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
export type GetActivityParams = {
|
||||
doc_filter?: boolean;
|
||||
document_id?: string;
|
||||
offset?: number;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
};
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link, useSearchParams } from 'react-router-dom';
|
||||
import { useGetActivity } from '../generated/anthoLumeAPIV1';
|
||||
import type { Activity } from '../generated/model';
|
||||
import { Pagination } from '../components';
|
||||
import { Table, type Column } from '../components/Table';
|
||||
import { formatDuration } from '../utils/formatters';
|
||||
|
||||
export default function ActivityPage() {
|
||||
const { data, isLoading } = useGetActivity({ offset: 0, limit: 100 });
|
||||
const activities = data?.status === 200 ? data.data.activities : [];
|
||||
const [searchParams] = useSearchParams();
|
||||
const documentID = searchParams.get('document') || undefined;
|
||||
const [page, setPage] = useState(1);
|
||||
const limit = 25;
|
||||
|
||||
useEffect(() => {
|
||||
setPage(1);
|
||||
}, [documentID]);
|
||||
|
||||
const { data, isLoading } = useGetActivity({
|
||||
doc_filter: Boolean(documentID),
|
||||
document_id: documentID,
|
||||
page,
|
||||
limit,
|
||||
});
|
||||
const response = data?.status === 200 ? data.data : undefined;
|
||||
const activities = response?.activities ?? [];
|
||||
|
||||
const columns: Column<Activity>[] = [
|
||||
{
|
||||
@@ -35,5 +52,17 @@ export default function ActivityPage() {
|
||||
},
|
||||
];
|
||||
|
||||
return <Table columns={columns} data={activities || []} loading={isLoading} />;
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Table columns={columns} data={activities} loading={isLoading} />
|
||||
<Pagination
|
||||
page={page}
|
||||
previousPage={response?.previous_page}
|
||||
nextPage={response?.next_page}
|
||||
total={response?.total}
|
||||
limit={limit}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useGetDocuments, useCreateDocument } from '../generated/anthoLumeAPIV1';
|
||||
import type { Document, DocumentsResponse } from '../generated/model';
|
||||
import { ActivityIcon, DownloadIcon, Search2Icon, UploadIcon } from '../icons';
|
||||
import { LoadingState } from '../components';
|
||||
import { LoadingState, Pagination } from '../components';
|
||||
import { useToasts } from '../components/ToastContext';
|
||||
import { formatDuration } from '../utils/formatters';
|
||||
import { useDebounce } from '../hooks/useDebounce';
|
||||
@@ -272,24 +272,14 @@ export default function DocumentsPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 flex w-full justify-center gap-4 text-content">
|
||||
{previousPage && previousPage > 0 && (
|
||||
<button
|
||||
onClick={() => setPage(page - 1)}
|
||||
className="w-24 rounded bg-surface p-2 text-center text-sm font-medium shadow-lg hover:bg-surface-strong focus:outline-none"
|
||||
>
|
||||
◄
|
||||
</button>
|
||||
)}
|
||||
{nextPage && nextPage > 0 && (
|
||||
<button
|
||||
onClick={() => setPage(page + 1)}
|
||||
className="w-24 rounded bg-surface p-2 text-center text-sm font-medium shadow-lg hover:bg-surface-strong focus:outline-none"
|
||||
>
|
||||
►
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Pagination
|
||||
page={page}
|
||||
previousPage={previousPage}
|
||||
nextPage={nextPage}
|
||||
total={(data?.data as DocumentsResponse | undefined)?.total}
|
||||
limit={limit}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
|
||||
<div className="fixed bottom-6 right-6 flex items-center justify-center rounded-full">
|
||||
<input
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useGetProgressList } from '../generated/anthoLumeAPIV1';
|
||||
import type { Progress } from '../generated/model';
|
||||
import { Pagination } from '../components';
|
||||
import { Table, type Column } from '../components/Table';
|
||||
|
||||
export default function ProgressPage() {
|
||||
const { data, isLoading } = useGetProgressList({ page: 1, limit: 15 });
|
||||
const progress = data?.status === 200 ? (data.data.progress ?? []) : [];
|
||||
const [page, setPage] = useState(1);
|
||||
const limit = 15;
|
||||
const { data, isLoading } = useGetProgressList({ page, limit });
|
||||
const response = data?.status === 200 ? data.data : undefined;
|
||||
const progress = response?.progress ?? [];
|
||||
|
||||
const columns: Column<Progress>[] = [
|
||||
{
|
||||
@@ -35,5 +40,17 @@ export default function ProgressPage() {
|
||||
},
|
||||
];
|
||||
|
||||
return <Table columns={columns} data={progress || []} loading={isLoading} />;
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Table columns={columns} data={progress} loading={isLoading} />
|
||||
<Pagination
|
||||
page={page}
|
||||
previousPage={response?.previous_page}
|
||||
nextPage={response?.next_page}
|
||||
total={response?.total}
|
||||
limit={limit}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user