@tunghtml/strapi-plugin-filtered-relation 1.1.0 → 1.1.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.
- package/CHANGELOG.md +50 -0
- package/admin/src/components/FilteredRelationInput.jsx +63 -43
- package/package.json +1 -1
- package/push-to-github.sh +52 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,55 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.2] - 2024-12-02
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Critical Bug - Data Loss Prevention**: Fixed critical bug where disconnect operation happened before validating target record existence
|
|
8
|
+
- Previously: Disconnected investor first, then checked if target exists → data loss if target missing
|
|
9
|
+
- Now: Validates ALL conditions first (target exists, data valid), then performs disconnect/connect
|
|
10
|
+
- Prevents "Investor not found in current record" error on subsequent operations
|
|
11
|
+
- Users can now safely perform multiple status changes without page refresh
|
|
12
|
+
- **Improved Error Notifications**: Changed error notification type from "warning" to "danger"
|
|
13
|
+
- Makes errors more visible and distinguishable from warnings
|
|
14
|
+
- Helps users quickly identify critical issues that need attention
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Reordered `handleStatusChange` logic flow:
|
|
19
|
+
1. Get current record data
|
|
20
|
+
2. Find investor to move
|
|
21
|
+
3. **Validate target record exists** (NEW - moved before disconnect)
|
|
22
|
+
4. **Validate target data is valid** (NEW)
|
|
23
|
+
5. Disconnect from current record (only after validation passes)
|
|
24
|
+
6. Connect to target record
|
|
25
|
+
7. Refresh UI
|
|
26
|
+
- All error notifications now use `type: "danger"` instead of `type: "warning"`
|
|
27
|
+
|
|
28
|
+
### Technical Details
|
|
29
|
+
|
|
30
|
+
- Added validation check for target record data integrity before any mutations
|
|
31
|
+
- Prevents partial state updates that could leave data in inconsistent state
|
|
32
|
+
- Follows transaction-like pattern: validate everything, then commit changes
|
|
33
|
+
|
|
34
|
+
## [1.1.1] - 2024-12-02
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **State Refresh Issue**: Fixed "Investor not found in current record" error that occurred after status changes
|
|
39
|
+
- Now refreshes local state immediately after disconnect/connect operations
|
|
40
|
+
- Prevents stale data issues when performing multiple status changes without page reload
|
|
41
|
+
- Reordered refresh logic: local field first, then dispatch event to other fields
|
|
42
|
+
- **User Experience**: Replaced browser `alert()` with Strapi's native notification system
|
|
43
|
+
- Success notifications for successful status changes
|
|
44
|
+
- Warning notifications for missing records or data issues
|
|
45
|
+
- Danger notifications for errors
|
|
46
|
+
- All notifications use `useNotification` hook from `@strapi/strapi/admin`
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- Improved notification messages to be more user-friendly
|
|
51
|
+
- Enhanced error handling with proper notification types
|
|
52
|
+
|
|
3
53
|
## [1.1.0] - 2024-12-02
|
|
4
54
|
|
|
5
55
|
### Added
|
|
@@ -8,12 +8,13 @@ import {
|
|
|
8
8
|
SingleSelect,
|
|
9
9
|
SingleSelectOption,
|
|
10
10
|
} from "@strapi/design-system";
|
|
11
|
-
import { useFetchClient } from "@strapi/strapi/admin";
|
|
11
|
+
import { useFetchClient, useNotification } from "@strapi/strapi/admin";
|
|
12
12
|
import { useLocation, useNavigate } from "react-router-dom";
|
|
13
13
|
|
|
14
14
|
const FilteredRelationInput = (props) => {
|
|
15
15
|
const { formatMessage } = useIntl();
|
|
16
16
|
const { get, put } = useFetchClient();
|
|
17
|
+
const { toggleNotification } = useNotification();
|
|
17
18
|
const location = useLocation();
|
|
18
19
|
const navigate = useNavigate();
|
|
19
20
|
const [items, setItems] = React.useState([]);
|
|
@@ -96,7 +97,7 @@ const FilteredRelationInput = (props) => {
|
|
|
96
97
|
data: {
|
|
97
98
|
[name]: newValue,
|
|
98
99
|
},
|
|
99
|
-
}
|
|
100
|
+
}
|
|
100
101
|
);
|
|
101
102
|
} catch (err) {
|
|
102
103
|
console.error("Error auto-saving filtered relation:", err);
|
|
@@ -116,7 +117,7 @@ const FilteredRelationInput = (props) => {
|
|
|
116
117
|
// Try 1: Get from content-type-builder (most reliable for schema)
|
|
117
118
|
try {
|
|
118
119
|
const builderResponse = await get(
|
|
119
|
-
`/content-type-builder/content-types/${targetModel}
|
|
120
|
+
`/content-type-builder/content-types/${targetModel}`
|
|
120
121
|
);
|
|
121
122
|
const builderSchema = builderResponse?.data?.data?.schema;
|
|
122
123
|
enumValues = builderSchema?.attributes?.[statusField]?.enum;
|
|
@@ -168,34 +169,17 @@ const FilteredRelationInput = (props) => {
|
|
|
168
169
|
|
|
169
170
|
if (!investorToRemove) {
|
|
170
171
|
console.error(
|
|
171
|
-
"[FilteredRelation] Investor not found in current record"
|
|
172
|
+
"[FilteredRelation] Investor not found in current record"
|
|
172
173
|
);
|
|
173
|
-
|
|
174
|
+
toggleNotification({
|
|
175
|
+
type: "danger",
|
|
176
|
+
message:
|
|
177
|
+
"Investor not found in current record. Please refresh the page.",
|
|
178
|
+
});
|
|
174
179
|
return;
|
|
175
180
|
}
|
|
176
181
|
|
|
177
|
-
//
|
|
178
|
-
const updatePayload = {
|
|
179
|
-
documentId: currentData.documentId,
|
|
180
|
-
[displayField]: {
|
|
181
|
-
connect: [],
|
|
182
|
-
disconnect: [
|
|
183
|
-
{
|
|
184
|
-
id: investorToRemove.id,
|
|
185
|
-
documentId: investorToRemove.documentId,
|
|
186
|
-
},
|
|
187
|
-
],
|
|
188
|
-
},
|
|
189
|
-
participantStatus: currentData.participantStatus,
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
// Use disconnect with full payload
|
|
193
|
-
const removeResponse = await put(
|
|
194
|
-
`/content-manager/collection-types/${targetModel}/${itemId}`,
|
|
195
|
-
updatePayload,
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
// Step 3: Find Meeting Participation Status with new status (same meeting)
|
|
182
|
+
// Step 3: VALIDATE target record EXISTS BEFORE disconnecting (critical fix)
|
|
199
183
|
const newFilterParams = {};
|
|
200
184
|
if (filterField1) {
|
|
201
185
|
newFilterParams[filterField1] = newStatus;
|
|
@@ -224,30 +208,58 @@ const FilteredRelationInput = (props) => {
|
|
|
224
208
|
searchResponse?.data ||
|
|
225
209
|
[];
|
|
226
210
|
|
|
227
|
-
let targetRecordId;
|
|
228
|
-
let targetInvestors = [];
|
|
229
|
-
|
|
230
211
|
if (targetRecords.length === 0) {
|
|
231
212
|
console.error(
|
|
232
|
-
"[FilteredRelation] No target record found with new status for this meeting."
|
|
213
|
+
"[FilteredRelation] No target record found with new status for this meeting."
|
|
233
214
|
);
|
|
234
215
|
const collectionDisplayName = targetModelInput || "record";
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
216
|
+
toggleNotification({
|
|
217
|
+
type: "danger",
|
|
218
|
+
message: `No ${collectionDisplayName} found with status "${newStatus}" for this meeting. Please create one first.`,
|
|
219
|
+
});
|
|
238
220
|
return;
|
|
239
221
|
}
|
|
240
222
|
|
|
241
223
|
// Get full target record data
|
|
242
224
|
const targetRecord = targetRecords[0];
|
|
243
|
-
targetRecordId = targetRecord.documentId || targetRecord.id;
|
|
225
|
+
const targetRecordId = targetRecord.documentId || targetRecord.id;
|
|
244
226
|
|
|
245
227
|
// Get target record with all fields
|
|
246
228
|
const targetRecordUrl = `/content-manager/collection-types/${targetModel}/${targetRecordId}?populate=*`;
|
|
247
229
|
const targetRecordFull = await get(targetRecordUrl);
|
|
248
230
|
const targetData = targetRecordFull?.data?.data || {};
|
|
249
231
|
|
|
250
|
-
|
|
232
|
+
if (!targetData || !targetData.documentId) {
|
|
233
|
+
console.error("[FilteredRelation] Target record data is invalid");
|
|
234
|
+
toggleNotification({
|
|
235
|
+
type: "danger",
|
|
236
|
+
message: "Target record not found. Please refresh and try again.",
|
|
237
|
+
});
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Step 4: NOW disconnect from current record (after validation passed)
|
|
242
|
+
const updatePayload = {
|
|
243
|
+
documentId: currentData.documentId,
|
|
244
|
+
[displayField]: {
|
|
245
|
+
connect: [],
|
|
246
|
+
disconnect: [
|
|
247
|
+
{
|
|
248
|
+
id: investorToRemove.id,
|
|
249
|
+
documentId: investorToRemove.documentId,
|
|
250
|
+
},
|
|
251
|
+
],
|
|
252
|
+
},
|
|
253
|
+
participantStatus: currentData.participantStatus,
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// Use disconnect with full payload
|
|
257
|
+
const removeResponse = await put(
|
|
258
|
+
`/content-manager/collection-types/${targetModel}/${itemId}`,
|
|
259
|
+
updatePayload
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
// Step 5: Add investor to target record
|
|
251
263
|
// Build full payload like UI admin does
|
|
252
264
|
const addPayload = {
|
|
253
265
|
documentId: targetData.documentId,
|
|
@@ -266,21 +278,29 @@ const FilteredRelationInput = (props) => {
|
|
|
266
278
|
// Use connect with full payload
|
|
267
279
|
const addResponse = await put(
|
|
268
280
|
`/content-manager/collection-types/${targetModel}/${targetRecordId}`,
|
|
269
|
-
addPayload
|
|
281
|
+
addPayload
|
|
270
282
|
);
|
|
271
283
|
|
|
272
|
-
//
|
|
284
|
+
// Refresh current field first to update local state
|
|
285
|
+
await fetchFilteredData();
|
|
286
|
+
|
|
287
|
+
// Then dispatch custom event to trigger refresh of all other filtered relation fields
|
|
273
288
|
window.dispatchEvent(
|
|
274
289
|
new CustomEvent("filtered-relation-updated", {
|
|
275
290
|
detail: { documentId, targetModel },
|
|
276
|
-
})
|
|
291
|
+
})
|
|
277
292
|
);
|
|
278
293
|
|
|
279
|
-
|
|
280
|
-
|
|
294
|
+
toggleNotification({
|
|
295
|
+
type: "success",
|
|
296
|
+
message: "Status changed successfully",
|
|
297
|
+
});
|
|
281
298
|
} catch (err) {
|
|
282
299
|
console.error("[FilteredRelation] Error moving investor:", err);
|
|
283
|
-
|
|
300
|
+
toggleNotification({
|
|
301
|
+
type: "danger",
|
|
302
|
+
message: "Failed to change status. Please try again.",
|
|
303
|
+
});
|
|
284
304
|
} finally {
|
|
285
305
|
setUpdatingStatus((prev) => ({ ...prev, [itemId]: false }));
|
|
286
306
|
}
|
|
@@ -387,7 +407,7 @@ const FilteredRelationInput = (props) => {
|
|
|
387
407
|
|
|
388
408
|
// Auto-save to database
|
|
389
409
|
const documentIds = formattedItems.map(
|
|
390
|
-
(item) => item.relatedDocumentId || item.id
|
|
410
|
+
(item) => item.relatedDocumentId || item.id
|
|
391
411
|
);
|
|
392
412
|
const newValue = JSON.stringify(documentIds);
|
|
393
413
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tunghtml/strapi-plugin-filtered-relation",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "A Strapi custom field plugin for displaying filtered relations with dynamic placeholder support and status change capability",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Script to push plugin to GitHub
|
|
4
|
+
# Usage: ./push-to-github.sh
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
echo "🚀 Pushing strapi-plugin-filtered-relation to GitHub..."
|
|
9
|
+
|
|
10
|
+
# Check if we're in the right directory
|
|
11
|
+
if [ ! -f "package.json" ]; then
|
|
12
|
+
echo "❌ Error: package.json not found. Are you in the plugin directory?"
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# Check if git is initialized
|
|
17
|
+
if [ ! -d ".git" ]; then
|
|
18
|
+
echo "📦 Initializing git repository..."
|
|
19
|
+
git init
|
|
20
|
+
git remote add origin https://github.com/finnwasabi/strapi-plugin-filtered-relation.git
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Add all files
|
|
24
|
+
echo "📝 Adding files..."
|
|
25
|
+
git add .
|
|
26
|
+
|
|
27
|
+
# Commit
|
|
28
|
+
echo "💾 Committing changes..."
|
|
29
|
+
git commit -m "Release v1.1.0 - Add status change dropdown feature
|
|
30
|
+
|
|
31
|
+
- Add status change dropdown at end of each item
|
|
32
|
+
- Dynamically fetch enum options from target model schema
|
|
33
|
+
- Move items between records using connect/disconnect API
|
|
34
|
+
- Dispatch custom events to refresh all fields without page reload
|
|
35
|
+
- Remove all console.log statements
|
|
36
|
+
- Update README with detailed options table and descriptions
|
|
37
|
+
- Update CHANGELOG with technical details
|
|
38
|
+
- Fully dynamic and reusable for any use case"
|
|
39
|
+
|
|
40
|
+
# Tag the release
|
|
41
|
+
echo "🏷️ Creating tag v1.1.0..."
|
|
42
|
+
git tag -a v1.1.0 -m "Version 1.1.0"
|
|
43
|
+
|
|
44
|
+
# Push to GitHub
|
|
45
|
+
echo "⬆️ Pushing to GitHub..."
|
|
46
|
+
git push origin main
|
|
47
|
+
git push origin v1.1.0
|
|
48
|
+
|
|
49
|
+
echo "✅ Successfully pushed to GitHub!"
|
|
50
|
+
echo "📦 Package: @tunghtml/strapi-plugin-filtered-relation@1.1.0"
|
|
51
|
+
echo "🔗 GitHub: https://github.com/finnwasabi/strapi-plugin-filtered-relation"
|
|
52
|
+
echo "🔗 NPM: https://www.npmjs.com/package/@tunghtml/strapi-plugin-filtered-relation"
|