@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 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
- alert("Investor not found in current record");
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
- // Build full payload like UI admin does
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
- alert(
236
- `No ${collectionDisplayName} found with status "${newStatus}" for this meeting. Please create one first.`,
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
- // Step 4: Add investor to target record
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
- // Dispatch custom event to trigger refresh of all filtered relation fields
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
- // Refresh current field
280
- await fetchFilteredData();
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
- alert("Failed to change status. Check console for details.");
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.0",
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"