vasuzex 2.3.0 → 2.3.2
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.
|
@@ -144,8 +144,11 @@ export async function createDatabase() {
|
|
|
144
144
|
console.log(`📦 Creating database: ${dbName}...`);
|
|
145
145
|
|
|
146
146
|
// Try to create database using psql
|
|
147
|
+
// CRITICAL FIX: Connect to 'postgres' database (not default user database) when checking/creating target database
|
|
148
|
+
// Without -d postgres, psql tries to connect to database matching username (e.g., "neasto_user" database)
|
|
149
|
+
// This causes "FATAL: database 'neasto_user' does not exist" when POSTGRES_USER=neasto_user
|
|
147
150
|
execSync(
|
|
148
|
-
`PGPASSWORD="${process.env.POSTGRES_PASSWORD}" psql -h ${dbHost} -p ${dbPort} -U ${dbUser} -tc "SELECT 1 FROM pg_database WHERE datname = '${dbName}'" | grep -q 1 || PGPASSWORD="${process.env.POSTGRES_PASSWORD}" psql -h ${dbHost} -p ${dbPort} -U ${dbUser} -c "CREATE DATABASE ${dbName}"`,
|
|
151
|
+
`PGPASSWORD="${process.env.POSTGRES_PASSWORD}" psql -h ${dbHost} -p ${dbPort} -U ${dbUser} -d postgres -tc "SELECT 1 FROM pg_database WHERE datname = '${dbName}'" | grep -q 1 || PGPASSWORD="${process.env.POSTGRES_PASSWORD}" psql -h ${dbHost} -p ${dbPort} -U ${dbUser} -d postgres -c "CREATE DATABASE ${dbName}"`,
|
|
149
152
|
{ stdio: 'inherit' }
|
|
150
153
|
);
|
|
151
154
|
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* - Rows per page selector
|
|
11
11
|
* - Action buttons (edit/view/delete/switch)
|
|
12
12
|
* - Loading and empty states
|
|
13
|
+
* - State persistence (restores page/filters when navigating back)
|
|
13
14
|
*
|
|
14
15
|
* @module components/DataTable
|
|
15
16
|
*/
|
|
@@ -38,6 +39,8 @@ import { Pagination } from "./Pagination.jsx";
|
|
|
38
39
|
* @param {string} props.initialStatusFilter - Initial status filter (all/true/false)
|
|
39
40
|
* @param {number} props.initialLimit - Initial rows per page
|
|
40
41
|
* @param {string} props.emptyText - Text to show when no data
|
|
42
|
+
* @param {boolean} props.persistState - Enable state persistence (default: true)
|
|
43
|
+
* @param {string} props.stateKey - Custom key for state storage (default: derived from apiUrl)
|
|
41
44
|
*/
|
|
42
45
|
export function DataTable(props) {
|
|
43
46
|
// Internal refresh key for self-refresh
|
|
@@ -60,6 +63,8 @@ export function DataTable(props) {
|
|
|
60
63
|
onDelete,
|
|
61
64
|
onToggle,
|
|
62
65
|
api, // API client instance passed as prop
|
|
66
|
+
persistState = true, // Enable state persistence by default
|
|
67
|
+
stateKey, // Optional custom state key
|
|
63
68
|
} = props;
|
|
64
69
|
|
|
65
70
|
// Validate that api client is provided
|
|
@@ -67,6 +72,66 @@ export function DataTable(props) {
|
|
|
67
72
|
throw new Error('DataTable requires "api" prop - pass your API client instance');
|
|
68
73
|
}
|
|
69
74
|
|
|
75
|
+
// Generate unique storage key based on apiUrl or custom stateKey
|
|
76
|
+
const storageKey = React.useMemo(() => {
|
|
77
|
+
if (stateKey) return `datatable_${stateKey}`;
|
|
78
|
+
// Use apiUrl as key (remove query params for consistency)
|
|
79
|
+
const cleanUrl = apiUrl.split('?')[0];
|
|
80
|
+
return `datatable_${cleanUrl.replace(/[^a-zA-Z0-9]/g, '_')}`;
|
|
81
|
+
}, [apiUrl, stateKey]);
|
|
82
|
+
|
|
83
|
+
// Helper to load persisted state
|
|
84
|
+
const loadPersistedState = React.useCallback(() => {
|
|
85
|
+
if (!persistState) return null;
|
|
86
|
+
try {
|
|
87
|
+
const stored = sessionStorage.getItem(storageKey);
|
|
88
|
+
return stored ? JSON.parse(stored) : null;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}, [persistState, storageKey]);
|
|
93
|
+
|
|
94
|
+
// Helper to save state
|
|
95
|
+
const saveState = React.useCallback((state) => {
|
|
96
|
+
if (!persistState) return;
|
|
97
|
+
try {
|
|
98
|
+
sessionStorage.setItem(storageKey, JSON.stringify(state));
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// Silently fail if sessionStorage is not available
|
|
101
|
+
}
|
|
102
|
+
}, [persistState, storageKey]);
|
|
103
|
+
|
|
104
|
+
// Initialize state from persisted data or props
|
|
105
|
+
const persistedState = loadPersistedState();
|
|
106
|
+
|
|
107
|
+
const [page, setPage] = React.useState(persistedState?.page || initialPage);
|
|
108
|
+
const [sortBy, setSortBy] = React.useState(
|
|
109
|
+
persistedState?.sortBy || initialSortBy || (columns.find((c) => c.sortable)?.field) || "",
|
|
110
|
+
);
|
|
111
|
+
const [sortOrder, setSortOrder] = React.useState(persistedState?.sortOrder || initialSortOrder);
|
|
112
|
+
const [search, setSearch] = React.useState(persistedState?.search || initialSearch || "");
|
|
113
|
+
const [statusFilter, setStatusFilter] = React.useState(persistedState?.statusFilter || initialStatusFilter || "all");
|
|
114
|
+
const [limit, setLimit] = React.useState(persistedState?.limit || initialLimit || 10);
|
|
115
|
+
const [data, setData] = React.useState([]);
|
|
116
|
+
const [loading, setLoading] = React.useState(false);
|
|
117
|
+
const [totalPages, setTotalPages] = React.useState(1);
|
|
118
|
+
const [totalItems, setTotalItems] = React.useState(0);
|
|
119
|
+
// Column search state
|
|
120
|
+
const [columnSearch, setColumnSearch] = React.useState(persistedState?.columnSearch || {});
|
|
121
|
+
|
|
122
|
+
// Save state whenever it changes
|
|
123
|
+
React.useEffect(() => {
|
|
124
|
+
saveState({
|
|
125
|
+
page,
|
|
126
|
+
sortBy,
|
|
127
|
+
sortOrder,
|
|
128
|
+
search,
|
|
129
|
+
statusFilter,
|
|
130
|
+
limit,
|
|
131
|
+
columnSearch,
|
|
132
|
+
});
|
|
133
|
+
}, [page, sortBy, sortOrder, search, statusFilter, limit, columnSearch, saveState]);
|
|
134
|
+
|
|
70
135
|
const handleStatusToggle = async (row) => {
|
|
71
136
|
if (!toggleLink) return;
|
|
72
137
|
try {
|
|
@@ -81,21 +146,6 @@ export function DataTable(props) {
|
|
|
81
146
|
toast.error(error.message || "Failed to update status");
|
|
82
147
|
}
|
|
83
148
|
};
|
|
84
|
-
|
|
85
|
-
const [page, setPage] = React.useState(initialPage);
|
|
86
|
-
const [sortBy, setSortBy] = React.useState(
|
|
87
|
-
initialSortBy || (columns.find((c) => c.sortable)?.field) || "",
|
|
88
|
-
);
|
|
89
|
-
const [sortOrder, setSortOrder] = React.useState(initialSortOrder);
|
|
90
|
-
const [search, setSearch] = React.useState(initialSearch || "");
|
|
91
|
-
const [statusFilter, setStatusFilter] = React.useState(initialStatusFilter || "all");
|
|
92
|
-
const [limit, setLimit] = React.useState(initialLimit || 10);
|
|
93
|
-
const [data, setData] = React.useState([]);
|
|
94
|
-
const [loading, setLoading] = React.useState(false);
|
|
95
|
-
const [totalPages, setTotalPages] = React.useState(1);
|
|
96
|
-
const [totalItems, setTotalItems] = React.useState(0);
|
|
97
|
-
// Column search state
|
|
98
|
-
const [columnSearch, setColumnSearch] = React.useState({});
|
|
99
149
|
|
|
100
150
|
// Reset page to 1 when columnSearch changes
|
|
101
151
|
React.useEffect(() => {
|