strapi-plugin-payone-provider 1.6.7 → 4.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin/src/pages/App/components/AppTabs.jsx +6 -0
- package/admin/src/pages/App/components/CustomerInfoPopover.jsx +147 -0
- package/admin/src/pages/App/components/HistoryPanel.jsx +22 -240
- package/admin/src/pages/App/components/RawDataPopover.jsx +113 -0
- package/admin/src/pages/App/components/StatusBadge.jsx +72 -14
- package/admin/src/pages/App/components/TransactionHistoryTable/TransactionHistoryTableFilters.jsx +113 -0
- package/admin/src/pages/App/components/TransactionHistoryTable/TransactionHistoryTablePagination.jsx +180 -0
- package/admin/src/pages/App/components/TransactionHistoryTable/index.jsx +225 -0
- package/admin/src/pages/App/index.jsx +3 -0
- package/admin/src/pages/hooks/useTransactionHistory.js +73 -8
- package/package.json +3 -2
- package/server/services/transactionService.js +69 -5
package/admin/src/pages/App/components/TransactionHistoryTable/TransactionHistoryTableFilters.jsx
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Flex,
|
|
5
|
+
Select,
|
|
6
|
+
Option,
|
|
7
|
+
Button,
|
|
8
|
+
TextInput,
|
|
9
|
+
Typography,
|
|
10
|
+
} from "@strapi/design-system";
|
|
11
|
+
import { Search } from "@strapi/icons";
|
|
12
|
+
|
|
13
|
+
const TransactionHistoryTableFilters = ({
|
|
14
|
+
filters,
|
|
15
|
+
onFilterChange,
|
|
16
|
+
onFilterApply,
|
|
17
|
+
isLoading,
|
|
18
|
+
}) => {
|
|
19
|
+
const handleDateFromClick = (e) => {
|
|
20
|
+
const input = e.target.closest('div')?.querySelector('input[type="date"]');
|
|
21
|
+
if (input) {
|
|
22
|
+
input.showPicker?.();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const handleDateToClick = (e) => {
|
|
27
|
+
const input = e.target.closest('div')?.querySelector('input[type="date"]');
|
|
28
|
+
if (input) {
|
|
29
|
+
input.showPicker?.();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return (
|
|
33
|
+
<Box marginBottom={2}>
|
|
34
|
+
<Flex gap={3} marginBottom={3} alignItems="center">
|
|
35
|
+
<TextInput
|
|
36
|
+
label="Search"
|
|
37
|
+
name="search"
|
|
38
|
+
value={filters?.search || ""}
|
|
39
|
+
onChange={(e) => onFilterChange("search", e.target.value)}
|
|
40
|
+
placeholder="Search by Status, Transaction ID, or Reference"
|
|
41
|
+
style={{ flex: 1, minWidth: "250px" }}
|
|
42
|
+
/>
|
|
43
|
+
<Select
|
|
44
|
+
label="Status"
|
|
45
|
+
name="status"
|
|
46
|
+
value={filters?.status || ""}
|
|
47
|
+
onChange={(value) => onFilterChange("status", value)}
|
|
48
|
+
placeholder="All Statuses"
|
|
49
|
+
style={{ width: "180px", minWidth: "180px" }}
|
|
50
|
+
>
|
|
51
|
+
<Option value="">All Statuses</Option>
|
|
52
|
+
<Option value="APPROVED">APPROVED</Option>
|
|
53
|
+
<Option value="PENDING">PENDING</Option>
|
|
54
|
+
<Option value="ERROR">ERROR</Option>
|
|
55
|
+
<Option value="CANCELLED">CANCELLED</Option>
|
|
56
|
+
<Option value="REDIRECT">REDIRECT</Option>
|
|
57
|
+
<Option value="CREATED">CREATED</Option>
|
|
58
|
+
</Select>
|
|
59
|
+
<Select
|
|
60
|
+
label="Request Type"
|
|
61
|
+
name="request_type"
|
|
62
|
+
value={filters?.request_type || ""}
|
|
63
|
+
onChange={(value) => onFilterChange("request_type", value)}
|
|
64
|
+
placeholder="Select request type"
|
|
65
|
+
style={{ width: "220px", minWidth: "220px" }}
|
|
66
|
+
>
|
|
67
|
+
<Option value="">All Types</Option>
|
|
68
|
+
<Option value="preauthorization">Preauthorization</Option>
|
|
69
|
+
<Option value="authorization">Authorization</Option>
|
|
70
|
+
<Option value="capture">Capture</Option>
|
|
71
|
+
<Option value="refund">Refund</Option>
|
|
72
|
+
</Select>
|
|
73
|
+
</Flex>
|
|
74
|
+
<Flex gap={3} marginBottom={3} alignItems="center">
|
|
75
|
+
<Box onClick={handleDateFromClick} style={{ cursor: "pointer" }}>
|
|
76
|
+
<TextInput
|
|
77
|
+
label="Date From"
|
|
78
|
+
name="date_from"
|
|
79
|
+
value={filters?.date_from || ""}
|
|
80
|
+
onChange={(e) => onFilterChange("date_from", e.target.value)}
|
|
81
|
+
placeholder="YYYY-MM-DD"
|
|
82
|
+
type="date"
|
|
83
|
+
style={{ minWidth: "150px" }}
|
|
84
|
+
/>
|
|
85
|
+
</Box>
|
|
86
|
+
<Box onClick={handleDateToClick} style={{ cursor: "pointer" }}>
|
|
87
|
+
<TextInput
|
|
88
|
+
label="Date To"
|
|
89
|
+
name="date_to"
|
|
90
|
+
value={filters?.date_to || ""}
|
|
91
|
+
onChange={(e) => onFilterChange("date_to", e.target.value)}
|
|
92
|
+
placeholder="YYYY-MM-DD"
|
|
93
|
+
type="date"
|
|
94
|
+
style={{ minWidth: "150px" }}
|
|
95
|
+
/>
|
|
96
|
+
</Box>
|
|
97
|
+
<Typography variant="pi" textColor="neutral600" style={{ fontSize: "12px", marginTop: "20px" }}>
|
|
98
|
+
By default, the last 30 days are shown
|
|
99
|
+
</Typography>
|
|
100
|
+
</Flex>
|
|
101
|
+
<Button
|
|
102
|
+
variant="default"
|
|
103
|
+
onClick={onFilterApply}
|
|
104
|
+
loading={isLoading}
|
|
105
|
+
startIcon={<Search />}
|
|
106
|
+
>
|
|
107
|
+
Apply Filters
|
|
108
|
+
</Button>
|
|
109
|
+
</Box>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export default TransactionHistoryTableFilters;
|
package/admin/src/pages/App/components/TransactionHistoryTable/TransactionHistoryTablePagination.jsx
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Flex,
|
|
5
|
+
Button,
|
|
6
|
+
TextInput,
|
|
7
|
+
Typography,
|
|
8
|
+
Select,
|
|
9
|
+
Option,
|
|
10
|
+
} from "@strapi/design-system";
|
|
11
|
+
import { ArrowLeft, ArrowRight, ChevronLeft, ChevronRight } from "@strapi/icons";
|
|
12
|
+
|
|
13
|
+
const TransactionHistoryTablePagination = ({
|
|
14
|
+
currentPage,
|
|
15
|
+
totalPages,
|
|
16
|
+
pageSize,
|
|
17
|
+
totalItems,
|
|
18
|
+
onPageChange,
|
|
19
|
+
onPageSizeChange,
|
|
20
|
+
isLoading,
|
|
21
|
+
}) => {
|
|
22
|
+
const [pageInput, setPageInput] = useState(currentPage.toString());
|
|
23
|
+
|
|
24
|
+
// Update input when currentPage changes externally
|
|
25
|
+
React.useEffect(() => {
|
|
26
|
+
setPageInput(currentPage.toString());
|
|
27
|
+
}, [currentPage]);
|
|
28
|
+
|
|
29
|
+
const handlePageInputChange = (e) => {
|
|
30
|
+
const value = e.target.value;
|
|
31
|
+
// Allow empty input or valid numbers
|
|
32
|
+
if (value === "" || /^\d+$/.test(value)) {
|
|
33
|
+
setPageInput(value);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handlePageInputBlur = () => {
|
|
38
|
+
const pageNum = parseInt(pageInput, 10);
|
|
39
|
+
if (pageNum && pageNum >= 1 && pageNum <= totalPages) {
|
|
40
|
+
onPageChange(pageNum);
|
|
41
|
+
} else {
|
|
42
|
+
// Reset to current page if invalid
|
|
43
|
+
setPageInput(currentPage.toString());
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const handlePageInputKeyPress = (e) => {
|
|
48
|
+
if (e.key === "Enter") {
|
|
49
|
+
handlePageInputBlur();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const goToFirstPage = () => {
|
|
54
|
+
if (currentPage > 1) {
|
|
55
|
+
onPageChange(1);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const goToLastPage = () => {
|
|
60
|
+
if (currentPage < totalPages) {
|
|
61
|
+
onPageChange(totalPages);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const goToPreviousPage = () => {
|
|
66
|
+
if (currentPage > 1) {
|
|
67
|
+
onPageChange(currentPage - 1);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const goToNextPage = () => {
|
|
72
|
+
if (currentPage < totalPages) {
|
|
73
|
+
onPageChange(currentPage + 1);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const startItem = totalItems > 0 ? (currentPage - 1) * pageSize + 1 : 0;
|
|
78
|
+
const endItem = Math.min(currentPage * pageSize, totalItems);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Box>
|
|
82
|
+
<Flex
|
|
83
|
+
justifyContent="space-between"
|
|
84
|
+
alignItems="center"
|
|
85
|
+
gap={4}
|
|
86
|
+
wrap="wrap"
|
|
87
|
+
>
|
|
88
|
+
<Flex alignItems="center" gap={2}>
|
|
89
|
+
<Typography variant="pi" textColor="neutral600">
|
|
90
|
+
{totalItems > 0
|
|
91
|
+
? `Showing ${startItem}-${endItem} of ${totalItems} transactions`
|
|
92
|
+
: `No transactions`}
|
|
93
|
+
</Typography>
|
|
94
|
+
<Flex alignItems="center" gap={2}>
|
|
95
|
+
<Typography variant="pi" textColor="neutral600">
|
|
96
|
+
Page size:
|
|
97
|
+
</Typography>
|
|
98
|
+
<Select
|
|
99
|
+
value={pageSize.toString()}
|
|
100
|
+
onChange={(value) => onPageSizeChange(parseInt(value, 10))}
|
|
101
|
+
disabled={isLoading}
|
|
102
|
+
style={{ width: "80px", minWidth: "80px" }}
|
|
103
|
+
>
|
|
104
|
+
<Option value="10">10</Option>
|
|
105
|
+
<Option value="20">20</Option>
|
|
106
|
+
<Option value="30">30</Option>
|
|
107
|
+
<Option value="50">50</Option>
|
|
108
|
+
<Option value="100">100</Option>
|
|
109
|
+
</Select>
|
|
110
|
+
</Flex>
|
|
111
|
+
</Flex>
|
|
112
|
+
|
|
113
|
+
{totalPages > 1 && (
|
|
114
|
+
<Flex alignItems="center" gap={2}>
|
|
115
|
+
<Button
|
|
116
|
+
variant="tertiary"
|
|
117
|
+
onClick={goToFirstPage}
|
|
118
|
+
disabled={currentPage === 1 || isLoading}
|
|
119
|
+
startIcon={<ChevronLeft />}
|
|
120
|
+
size="S"
|
|
121
|
+
>
|
|
122
|
+
First
|
|
123
|
+
</Button>
|
|
124
|
+
|
|
125
|
+
<Button
|
|
126
|
+
variant="tertiary"
|
|
127
|
+
onClick={goToPreviousPage}
|
|
128
|
+
disabled={currentPage === 1 || isLoading}
|
|
129
|
+
startIcon={<ArrowLeft />}
|
|
130
|
+
size="S"
|
|
131
|
+
>
|
|
132
|
+
Prev
|
|
133
|
+
</Button>
|
|
134
|
+
|
|
135
|
+
<Flex alignItems="center" gap={2}>
|
|
136
|
+
<Typography variant="pi" textColor="neutral600">
|
|
137
|
+
Page
|
|
138
|
+
</Typography>
|
|
139
|
+
<TextInput
|
|
140
|
+
value={pageInput}
|
|
141
|
+
onChange={handlePageInputChange}
|
|
142
|
+
onBlur={handlePageInputBlur}
|
|
143
|
+
onKeyPress={handlePageInputKeyPress}
|
|
144
|
+
disabled={isLoading}
|
|
145
|
+
aria-label="Page number"
|
|
146
|
+
style={{ width: "60px", textAlign: "center" }}
|
|
147
|
+
/>
|
|
148
|
+
<Typography variant="pi" textColor="neutral600">
|
|
149
|
+
of {totalPages}
|
|
150
|
+
</Typography>
|
|
151
|
+
</Flex>
|
|
152
|
+
|
|
153
|
+
<Button
|
|
154
|
+
variant="tertiary"
|
|
155
|
+
onClick={goToNextPage}
|
|
156
|
+
disabled={currentPage === totalPages || isLoading}
|
|
157
|
+
endIcon={<ArrowRight />}
|
|
158
|
+
size="S"
|
|
159
|
+
>
|
|
160
|
+
Next
|
|
161
|
+
</Button>
|
|
162
|
+
|
|
163
|
+
<Button
|
|
164
|
+
variant="tertiary"
|
|
165
|
+
onClick={goToLastPage}
|
|
166
|
+
disabled={currentPage === totalPages || isLoading}
|
|
167
|
+
endIcon={<ChevronRight />}
|
|
168
|
+
size="S"
|
|
169
|
+
>
|
|
170
|
+
Last
|
|
171
|
+
</Button>
|
|
172
|
+
</Flex>
|
|
173
|
+
)}
|
|
174
|
+
</Flex>
|
|
175
|
+
</Box>
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export default TransactionHistoryTablePagination;
|
|
180
|
+
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Box,
|
|
4
|
+
Table,
|
|
5
|
+
Thead,
|
|
6
|
+
Tbody,
|
|
7
|
+
Tr,
|
|
8
|
+
Th,
|
|
9
|
+
Td,
|
|
10
|
+
Typography,
|
|
11
|
+
Button,
|
|
12
|
+
Flex,
|
|
13
|
+
} from "@strapi/design-system";
|
|
14
|
+
import { User, File, ArrowUp, ArrowDown } from "@strapi/icons";
|
|
15
|
+
import StatusBadge from "../StatusBadge";
|
|
16
|
+
import CustomerInfoPopover from "../CustomerInfoPopover";
|
|
17
|
+
import RawDataPopover from "../RawDataPopover";
|
|
18
|
+
import TransactionHistoryTableFilters from "./TransactionHistoryTableFilters";
|
|
19
|
+
import TransactionHistoryTablePagination from "./TransactionHistoryTablePagination";
|
|
20
|
+
|
|
21
|
+
const TransactionHistoryTable = ({
|
|
22
|
+
transactions,
|
|
23
|
+
isLoading,
|
|
24
|
+
filters,
|
|
25
|
+
onFilterChange,
|
|
26
|
+
onFilterApply,
|
|
27
|
+
sorting,
|
|
28
|
+
onSort,
|
|
29
|
+
currentPage,
|
|
30
|
+
totalPages,
|
|
31
|
+
pageSize,
|
|
32
|
+
totalItems,
|
|
33
|
+
onPageChange,
|
|
34
|
+
onPageSizeChange,
|
|
35
|
+
}) => {
|
|
36
|
+
const SortableHeader = ({ column, label, sortable = true }) => {
|
|
37
|
+
const isActive = sorting?.sortBy === column;
|
|
38
|
+
const sortOrder = isActive ? sorting?.sortOrder : null;
|
|
39
|
+
|
|
40
|
+
const handleClick = () => {
|
|
41
|
+
if (sortable && onSort) {
|
|
42
|
+
onSort(column);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Th>
|
|
48
|
+
<Flex
|
|
49
|
+
alignItems="center"
|
|
50
|
+
gap={2}
|
|
51
|
+
onClick={handleClick}
|
|
52
|
+
style={{ cursor: sortable ? "pointer" : "default" }}
|
|
53
|
+
>
|
|
54
|
+
<Typography fontWeight="bold" textColor="primary500">
|
|
55
|
+
{label}
|
|
56
|
+
</Typography>
|
|
57
|
+
{sortable && (
|
|
58
|
+
<Box style={{ display: "flex", alignItems: "center" }}>
|
|
59
|
+
{sortOrder === "asc" && <ArrowUp size={8} />}
|
|
60
|
+
{sortOrder === "desc" && <ArrowDown size={8} />}
|
|
61
|
+
{!isActive && (
|
|
62
|
+
<Box style={{ opacity: 0.3 }}>
|
|
63
|
+
<ArrowUp size={8} />
|
|
64
|
+
</Box>
|
|
65
|
+
)}
|
|
66
|
+
</Box>
|
|
67
|
+
)}
|
|
68
|
+
</Flex>
|
|
69
|
+
</Th>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
const getCardTypeName = (cardtype) => {
|
|
73
|
+
switch (cardtype) {
|
|
74
|
+
case "V":
|
|
75
|
+
return "Visa";
|
|
76
|
+
case "M":
|
|
77
|
+
return "MasterCard";
|
|
78
|
+
default:
|
|
79
|
+
return cardtype || "";
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const getPaymentMethodName = (clearingtype, wallettype, cardtype) => {
|
|
84
|
+
switch (clearingtype) {
|
|
85
|
+
case "cc":
|
|
86
|
+
const cardTypeName = getCardTypeName(cardtype);
|
|
87
|
+
return cardTypeName ? `CC / ${cardTypeName}` : "Credit Card";
|
|
88
|
+
case "sb":
|
|
89
|
+
return "Online Banking";
|
|
90
|
+
case "wlt":
|
|
91
|
+
return wallettype === "PPE" ? "PayPal" : "Wallet";
|
|
92
|
+
case "elv":
|
|
93
|
+
return "Direct Debit (SEPA)";
|
|
94
|
+
default:
|
|
95
|
+
return "Unknown";
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const formatAmount = (amount, currency) => {
|
|
100
|
+
return `${(amount / 100).toFixed(2)} ${currency}`;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const formatDate = (dateString) => {
|
|
104
|
+
return new Date(dateString).toLocaleString("de-DE", {
|
|
105
|
+
year: "numeric",
|
|
106
|
+
month: "2-digit",
|
|
107
|
+
day: "2-digit",
|
|
108
|
+
hour: "2-digit",
|
|
109
|
+
minute: "2-digit",
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<Box>
|
|
115
|
+
{/* Table Filters */}
|
|
116
|
+
<TransactionHistoryTableFilters
|
|
117
|
+
filters={filters}
|
|
118
|
+
onFilterChange={onFilterChange}
|
|
119
|
+
onFilterApply={onFilterApply}
|
|
120
|
+
isLoading={isLoading}
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
<Box style={{ maxHeight: "600px", overflow: "auto", marginBottom: "1rem" }}>
|
|
125
|
+
<Table colCount={6} rowCount={transactions.length}>
|
|
126
|
+
<Thead>
|
|
127
|
+
<Tr>
|
|
128
|
+
<SortableHeader column="amount" label="Amount" />
|
|
129
|
+
<SortableHeader column="created_at" label="Created At" />
|
|
130
|
+
<SortableHeader column="status" label="Status" />
|
|
131
|
+
<SortableHeader column="reference" label="Reference" />
|
|
132
|
+
<SortableHeader column="method" label="Method" />
|
|
133
|
+
<Th>
|
|
134
|
+
<Typography fontWeight="bold" textColor="primary500">
|
|
135
|
+
Actions
|
|
136
|
+
</Typography>
|
|
137
|
+
</Th>
|
|
138
|
+
</Tr>
|
|
139
|
+
</Thead>
|
|
140
|
+
<Tbody>
|
|
141
|
+
{transactions.length === 0 && !isLoading ? (
|
|
142
|
+
<Tr>
|
|
143
|
+
<Td colSpan={6}>
|
|
144
|
+
<Box padding={6}>
|
|
145
|
+
<Typography textAlign="center" textColor="neutral600">
|
|
146
|
+
No transactions found
|
|
147
|
+
</Typography>
|
|
148
|
+
</Box>
|
|
149
|
+
</Td>
|
|
150
|
+
</Tr>
|
|
151
|
+
) : (
|
|
152
|
+
transactions.map((transaction) => (
|
|
153
|
+
<Tr key={transaction.id}>
|
|
154
|
+
<Td>
|
|
155
|
+
<Typography variant="pi" fontWeight="bold" textColor="primary600">
|
|
156
|
+
{formatAmount(transaction.amount, transaction.currency)}
|
|
157
|
+
</Typography>
|
|
158
|
+
</Td>
|
|
159
|
+
<Td>
|
|
160
|
+
<Typography variant="pi" textColor="neutral800">
|
|
161
|
+
{formatDate(transaction.created_at)}
|
|
162
|
+
</Typography>
|
|
163
|
+
</Td>
|
|
164
|
+
<Td>
|
|
165
|
+
<StatusBadge status={transaction.status} transaction={transaction} />
|
|
166
|
+
</Td>
|
|
167
|
+
<Td>
|
|
168
|
+
<Typography variant="pi" textColor="neutral800">
|
|
169
|
+
{transaction.reference}
|
|
170
|
+
</Typography>
|
|
171
|
+
</Td>
|
|
172
|
+
<Td>
|
|
173
|
+
<Typography variant="pi" textColor="neutral800">
|
|
174
|
+
{getPaymentMethodName(
|
|
175
|
+
transaction.raw_request?.clearingtype,
|
|
176
|
+
transaction.raw_request?.wallettype,
|
|
177
|
+
transaction.raw_request?.cardtype
|
|
178
|
+
)}
|
|
179
|
+
</Typography>
|
|
180
|
+
</Td>
|
|
181
|
+
<Td>
|
|
182
|
+
<Flex gap={2} justifyContent="flex-start">
|
|
183
|
+
<CustomerInfoPopover transaction={transaction}>
|
|
184
|
+
<Button
|
|
185
|
+
variant="secondary"
|
|
186
|
+
size="S"
|
|
187
|
+
startIcon={<User />}
|
|
188
|
+
>
|
|
189
|
+
View Customer
|
|
190
|
+
</Button>
|
|
191
|
+
</CustomerInfoPopover>
|
|
192
|
+
<RawDataPopover transaction={transaction}>
|
|
193
|
+
<Button
|
|
194
|
+
variant="secondary"
|
|
195
|
+
size="S"
|
|
196
|
+
startIcon={<File />}
|
|
197
|
+
>
|
|
198
|
+
View Raw Data
|
|
199
|
+
</Button>
|
|
200
|
+
</RawDataPopover>
|
|
201
|
+
</Flex>
|
|
202
|
+
</Td>
|
|
203
|
+
</Tr>
|
|
204
|
+
))
|
|
205
|
+
)}
|
|
206
|
+
</Tbody>
|
|
207
|
+
</Table>
|
|
208
|
+
</Box>
|
|
209
|
+
|
|
210
|
+
{/* Pagination */}
|
|
211
|
+
<TransactionHistoryTablePagination
|
|
212
|
+
currentPage={currentPage}
|
|
213
|
+
totalPages={totalPages}
|
|
214
|
+
pageSize={pageSize}
|
|
215
|
+
totalItems={totalItems}
|
|
216
|
+
onPageChange={onPageChange}
|
|
217
|
+
onPageSizeChange={onPageSizeChange}
|
|
218
|
+
isLoading={isLoading}
|
|
219
|
+
/>
|
|
220
|
+
</Box>
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export default TransactionHistoryTable;
|
|
225
|
+
|
|
@@ -114,6 +114,8 @@ const App = () => {
|
|
|
114
114
|
filters={transactionHistory.filters}
|
|
115
115
|
onFilterChange={transactionHistory.handleFilterChange}
|
|
116
116
|
onFilterApply={transactionHistory.handleFilterApply}
|
|
117
|
+
sorting={transactionHistory.sorting}
|
|
118
|
+
onSort={transactionHistory.handleSort}
|
|
117
119
|
isLoadingHistory={transactionHistory.isLoadingHistory}
|
|
118
120
|
transactionHistory={transactionHistory.transactionHistory}
|
|
119
121
|
paginatedTransactions={transactionHistory.paginatedTransactions}
|
|
@@ -122,6 +124,7 @@ const App = () => {
|
|
|
122
124
|
pageSize={transactionHistory.pageSize}
|
|
123
125
|
onRefresh={transactionHistory.loadTransactionHistory}
|
|
124
126
|
onPageChange={transactionHistory.handlePageChange}
|
|
127
|
+
onPageSizeChange={transactionHistory.handlePageSizeChange}
|
|
125
128
|
selectedTransaction={transactionHistory.selectedTransaction}
|
|
126
129
|
onTransactionSelect={transactionHistory.handleTransactionSelect}
|
|
127
130
|
paymentActions={paymentActions}
|
|
@@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
|
|
|
2
2
|
import { useNotification } from "@strapi/helper-plugin";
|
|
3
3
|
import payoneRequests from "../utils/api";
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const DEFAULT_PAGE_SIZE = 10;
|
|
6
6
|
|
|
7
7
|
const useTransactionHistory = () => {
|
|
8
8
|
const toggleNotification = useNotification();
|
|
@@ -10,12 +10,32 @@ const useTransactionHistory = () => {
|
|
|
10
10
|
const [isLoadingHistory, setIsLoadingHistory] = useState(false);
|
|
11
11
|
const [selectedTransaction, setSelectedTransaction] = useState(null);
|
|
12
12
|
const [currentPage, setCurrentPage] = useState(1);
|
|
13
|
+
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
|
14
|
+
// Calculate default dates
|
|
15
|
+
const getDefaultDateFrom = () => {
|
|
16
|
+
const date = new Date();
|
|
17
|
+
date.setDate(date.getDate() - 30);
|
|
18
|
+
return date.toISOString().split('T')[0];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const getDefaultDateTo = () => {
|
|
22
|
+
const date = new Date();
|
|
23
|
+
date.setDate(date.getDate() + 1); // Add 1 day to include today's transactions
|
|
24
|
+
return date.toISOString().split('T')[0];
|
|
25
|
+
};
|
|
26
|
+
|
|
13
27
|
const [filters, setFilters] = useState({
|
|
14
28
|
search: "",
|
|
15
29
|
request_type: "",
|
|
16
30
|
payment_method: "",
|
|
17
|
-
date_from:
|
|
18
|
-
date_to:
|
|
31
|
+
date_from: getDefaultDateFrom(),
|
|
32
|
+
date_to: getDefaultDateTo(),
|
|
33
|
+
status: ""
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const [sorting, setSorting] = useState({
|
|
37
|
+
sortBy: null,
|
|
38
|
+
sortOrder: null // 'asc' or 'desc'
|
|
19
39
|
});
|
|
20
40
|
|
|
21
41
|
useEffect(() => {
|
|
@@ -25,7 +45,12 @@ const useTransactionHistory = () => {
|
|
|
25
45
|
const loadTransactionHistory = async () => {
|
|
26
46
|
setIsLoadingHistory(true);
|
|
27
47
|
try {
|
|
28
|
-
const
|
|
48
|
+
const params = { ...filters };
|
|
49
|
+
if (sorting.sortBy && sorting.sortOrder) {
|
|
50
|
+
params.sort_by = sorting.sortBy;
|
|
51
|
+
params.sort_order = sorting.sortOrder;
|
|
52
|
+
}
|
|
53
|
+
const result = await payoneRequests.getTransactionHistory(params);
|
|
29
54
|
setTransactionHistory(result.data || []);
|
|
30
55
|
setCurrentPage(1);
|
|
31
56
|
} catch (error) {
|
|
@@ -46,6 +71,37 @@ const useTransactionHistory = () => {
|
|
|
46
71
|
loadTransactionHistory();
|
|
47
72
|
};
|
|
48
73
|
|
|
74
|
+
const handleSort = (column) => {
|
|
75
|
+
setSorting((prev) => {
|
|
76
|
+
// If clicking the same column, cycle through: null -> asc -> desc -> null
|
|
77
|
+
if (prev.sortBy === column) {
|
|
78
|
+
if (!prev.sortOrder) {
|
|
79
|
+
return { sortBy: column, sortOrder: "asc" };
|
|
80
|
+
} else if (prev.sortOrder === "asc") {
|
|
81
|
+
return { sortBy: column, sortOrder: "desc" };
|
|
82
|
+
} else {
|
|
83
|
+
return { sortBy: null, sortOrder: null };
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
// If clicking a different column, reset and set new column to asc
|
|
87
|
+
return { sortBy: column, sortOrder: "asc" };
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Reload when sorting changes (but not on initial mount)
|
|
93
|
+
const [isInitialMount, setIsInitialMount] = useState(true);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (isInitialMount) {
|
|
97
|
+
setIsInitialMount(false);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Only reload if sorting is actually set or cleared
|
|
101
|
+
loadTransactionHistory();
|
|
102
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
103
|
+
}, [sorting.sortBy, sorting.sortOrder]);
|
|
104
|
+
|
|
49
105
|
const handleTransactionSelect = (transaction) => {
|
|
50
106
|
if (selectedTransaction?.id === transaction?.id) {
|
|
51
107
|
setSelectedTransaction(null);
|
|
@@ -59,10 +115,16 @@ const useTransactionHistory = () => {
|
|
|
59
115
|
setSelectedTransaction(null);
|
|
60
116
|
};
|
|
61
117
|
|
|
118
|
+
const handlePageSizeChange = (newPageSize) => {
|
|
119
|
+
setPageSize(newPageSize);
|
|
120
|
+
setCurrentPage(1); // Reset to first page when page size changes
|
|
121
|
+
setSelectedTransaction(null);
|
|
122
|
+
};
|
|
123
|
+
|
|
62
124
|
// Pagination calculations
|
|
63
|
-
const totalPages = Math.ceil(transactionHistory.length /
|
|
64
|
-
const startIndex = (currentPage - 1) *
|
|
65
|
-
const endIndex = startIndex +
|
|
125
|
+
const totalPages = Math.ceil(transactionHistory.length / pageSize);
|
|
126
|
+
const startIndex = (currentPage - 1) * pageSize;
|
|
127
|
+
const endIndex = startIndex + pageSize;
|
|
66
128
|
const paginatedTransactions = transactionHistory.slice(startIndex, endIndex);
|
|
67
129
|
|
|
68
130
|
return {
|
|
@@ -71,13 +133,16 @@ const useTransactionHistory = () => {
|
|
|
71
133
|
isLoadingHistory,
|
|
72
134
|
selectedTransaction,
|
|
73
135
|
filters,
|
|
136
|
+
sorting,
|
|
74
137
|
currentPage,
|
|
75
138
|
totalPages,
|
|
76
|
-
pageSize
|
|
139
|
+
pageSize,
|
|
77
140
|
handleFilterChange,
|
|
78
141
|
handleFilterApply,
|
|
142
|
+
handleSort,
|
|
79
143
|
handleTransactionSelect,
|
|
80
144
|
handlePageChange,
|
|
145
|
+
handlePageSizeChange,
|
|
81
146
|
loadTransactionHistory
|
|
82
147
|
};
|
|
83
148
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-plugin-payone-provider",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.6.10",
|
|
4
4
|
"description": "Strapi plugin for Payone payment gateway integration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"maintainers": [
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"apple-pay-button": "^1.2.1",
|
|
14
14
|
"axios": "^1.6.3",
|
|
15
|
-
"prop-types": "^15.7.2"
|
|
15
|
+
"prop-types": "^15.7.2",
|
|
16
|
+
"@uiw/react-json-view": "^2.0.0-alpha.40"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
18
19
|
"react": "^18.2.0",
|