From b4781b164224be67a0f092123b7306bc1e8bbe6e Mon Sep 17 00:00:00 2001 From: Du Peng Date: Wed, 31 May 2023 18:20:14 +0200 Subject: [PATCH] (feat) edit/delete child in members list --- app/controllers/api/children_controller.rb | 2 +- .../components/family-account/child-item.tsx | 7 ++- .../components/family-account/child-modal.tsx | 4 +- .../family-account/children-dashboard.tsx | 4 +- .../components/user/members-list-item.tsx | 41 ++++++------- .../components/user/members-list.tsx | 60 +++++++++++++++++-- .../javascript/controllers/admin/members.js | 42 ++++++++++++- app/frontend/src/javascript/models/member.ts | 2 + .../templates/admin/members/members.html | 4 +- app/views/api/members/list.json.jbuilder | 12 ++++ 10 files changed, 141 insertions(+), 37 deletions(-) diff --git a/app/controllers/api/children_controller.rb b/app/controllers/api/children_controller.rb index bbd1d8e04..ac1d98b78 100644 --- a/app/controllers/api/children_controller.rb +++ b/app/controllers/api/children_controller.rb @@ -10,7 +10,7 @@ class API::ChildrenController < API::APIController authorize Child user_id = current_user.id user_id = params[:user_id] if current_user.privileged? && params[:user_id] - @children = Child.where(user_id: user_id) + @children = Child.where(user_id: user_id).includes(:supporting_document_files).order(:created_at) end def show diff --git a/app/frontend/src/javascript/components/family-account/child-item.tsx b/app/frontend/src/javascript/components/family-account/child-item.tsx index 62de02e9e..4192d2c04 100644 --- a/app/frontend/src/javascript/components/family-account/child-item.tsx +++ b/app/frontend/src/javascript/components/family-account/child-item.tsx @@ -11,7 +11,7 @@ interface ChildItemProps { child: Child; size: 'sm' | 'lg'; onEdit: (child: Child) => void; - onDelete: (error: string) => void; + onDelete: (child: Child, error: string) => void; onError: (error: string) => void; } @@ -33,8 +33,9 @@ export const ChildItem: React.FC = ({ child, size, onEdit, onDel const deleteChild = () => { ChildAPI.destroy(child.id).then(() => { toggleDeleteChildModal(); - onDelete(t('app.public.child_item.deleted')); - }).catch(() => { + onDelete(child, t('app.public.child_item.deleted')); + }).catch((e) => { + console.error(e); onError(t('app.public.child_item.unable_to_delete')); }); }; diff --git a/app/frontend/src/javascript/components/family-account/child-modal.tsx b/app/frontend/src/javascript/components/family-account/child-modal.tsx index acc75e9f4..f950e207e 100644 --- a/app/frontend/src/javascript/components/family-account/child-modal.tsx +++ b/app/frontend/src/javascript/components/family-account/child-modal.tsx @@ -13,7 +13,7 @@ interface ChildModalProps { operator: User; isOpen: boolean; toggleModal: () => void; - onSuccess: (msg: string) => void; + onSuccess: (child: Child, msg: string) => void; onError: (error: string) => void; supportingDocumentsTypes: Array; } @@ -35,7 +35,7 @@ export const ChildModal: React.FC = ({ child, isOpen, toggleMod await ChildAPI.create(data); } toggleModal(); - onSuccess(''); + onSuccess(data, ''); } catch (error) { onError(error); } diff --git a/app/frontend/src/javascript/components/family-account/children-dashboard.tsx b/app/frontend/src/javascript/components/family-account/children-dashboard.tsx index 30d950068..c9f856d58 100644 --- a/app/frontend/src/javascript/components/family-account/children-dashboard.tsx +++ b/app/frontend/src/javascript/components/family-account/children-dashboard.tsx @@ -70,7 +70,7 @@ export const ChildrenDashboard: React.FC = ({ user, oper /** * Delete a child */ - const handleDeleteChildSuccess = (msg: string) => { + const handleDeleteChildSuccess = (_child: Child, msg: string) => { ChildAPI.index({ user_id: user.id }).then(setChildren); onSuccess(msg); }; @@ -78,7 +78,7 @@ export const ChildrenDashboard: React.FC = ({ user, oper /** * Handle save child success from the API */ - const handleSaveChildSuccess = (msg: string) => { + const handleSaveChildSuccess = (_data: Child, msg: string) => { ChildAPI.index({ user_id: user.id }).then(setChildren); if (msg) { onSuccess(msg); diff --git a/app/frontend/src/javascript/components/user/members-list-item.tsx b/app/frontend/src/javascript/components/user/members-list-item.tsx index 19041b4e9..4d0bea4f0 100644 --- a/app/frontend/src/javascript/components/user/members-list-item.tsx +++ b/app/frontend/src/javascript/components/user/members-list-item.tsx @@ -1,32 +1,28 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Member } from '../../models/member'; import { Child } from '../../models/child'; -import ChildAPI from '../../api/child'; import { FabButton } from '../base/fab-button'; -import { CaretDown, User, Users } from 'phosphor-react'; +import { CaretDown, User, Users, PencilSimple, Trash } from 'phosphor-react'; import { ChildItem } from '../family-account/child-item'; -import { EditDestroyButtons } from '../base/edit-destroy-buttons'; interface MembersListItemProps { member: Member, onError: (message: string) => void, onSuccess: (message: string) => void + onEditChild: (child: Child) => void; + onDeleteChild: (child: Child, error: string) => void; + onDeleteMember: (memberId: number) => void; } /** * Members list */ -export const MembersListItem: React.FC = ({ member, onError, onSuccess }) => { +export const MembersListItem: React.FC = ({ member, onError, onEditChild, onDeleteChild, onDeleteMember }) => { const { t } = useTranslation('admin'); - const [children, setChildren] = useState>([]); const [childrenList, setChildrenList] = useState(false); - useEffect(() => { - ChildAPI.index({ user_id: member.id }).then(setChildren); - }, [member]); - /** * Redirect to the given user edition page */ @@ -38,12 +34,12 @@ export const MembersListItem: React.FC = ({ member, onErro
- {(children.length > 0) + {(member.children.length > 0) ? : }
- {(children.length > 0) && + {(member.children.length > 0) && setChildrenList(!childrenList)} className={`toggle ${childrenList ? 'open' : ''}`}> @@ -78,22 +74,21 @@ export const MembersListItem: React.FC = ({ member, onErro
-
- {/* TODO: */} - toMemberEdit(member.id)} - onDeleteSuccess={() => onSuccess} - itemId={member.id} - itemType={t('app.admin.members_list_item.item_type')} - destroy={() => new Promise(() => console.log(`Delete member ${member.id}`))} /> +
+ toMemberEdit(member.id)} className="edit-btn"> + + + onDeleteMember(member.id)} className="delete-btn"> + +
- { (children.length > 0) && + { (member.children.length > 0) &&

- {children.map(child => ( - console.log('edit child')} onDelete={() => console.log('delete child')} onError={onError} /> + {member.children.map((child: Child) => ( + ))}
} diff --git a/app/frontend/src/javascript/components/user/members-list.tsx b/app/frontend/src/javascript/components/user/members-list.tsx index 939db4632..ad911c0a0 100644 --- a/app/frontend/src/javascript/components/user/members-list.tsx +++ b/app/frontend/src/javascript/components/user/members-list.tsx @@ -1,27 +1,79 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { IApplication } from '../../models/application'; import { Loader } from '../base/loader'; import { react2angular } from 'react2angular'; import { Member } from '../../models/member'; import { MembersListItem } from './members-list-item'; +import { SupportingDocumentType } from '../../models/supporting-document-type'; +import SupportingDocumentTypeAPI from '../../api/supporting-document-type'; +import { Child } from '../../models/child'; +import { ChildModal } from '../family-account/child-modal'; +import { User } from '../../models/user'; declare const Application: IApplication; interface MembersListProps { members: Member[], + operator: User, onError: (message: string) => void, onSuccess: (message: string) => void + onDeleteMember: (memberId: number) => void; + onDeletedChild: (memberId: number, childId: number) => void; + onUpdatedChild: (memberId: number, child: Child) => void; } /** * Members list */ -export const MembersList: React.FC = ({ members, onError, onSuccess }) => { +export const MembersList: React.FC = ({ members, onError, onSuccess, operator, onDeleteMember, onDeletedChild, onUpdatedChild }) => { + const [supportingDocumentsTypes, setSupportingDocumentsTypes] = useState>([]); + const [child, setChild] = useState(); + const [isOpenChildModal, setIsOpenChildModal] = useState(false); + + useEffect(() => { + SupportingDocumentTypeAPI.index({ document_type: 'Child' }).then(tData => { + setSupportingDocumentsTypes(tData); + }); + }, []); + + /** + * Open the edit child modal + */ + const editChild = (child: Child) => { + setIsOpenChildModal(true); + setChild({ + ...child, + supporting_document_files_attributes: supportingDocumentsTypes.map(t => { + const file = child.supporting_document_files_attributes.find(f => f.supporting_document_type_id === t.id); + return file || { supporting_document_type_id: t.id }; + }) + } as Child); + }; + + /** + * Delete a child + */ + const handleDeleteChildSuccess = (c: Child, msg: string) => { + onDeletedChild(c.user_id, c.id); + onSuccess(msg); + }; + + /** + * Handle save child success from the API + */ + const handleSaveChildSuccess = (c: Child, msg: string) => { + onUpdatedChild(c.user_id, c); + if (msg) { + onSuccess(msg); + } + }; + return (
{members.map(member => ( - + ))} + setIsOpenChildModal(false)} onSuccess={handleSaveChildSuccess} onError={onError} supportingDocumentsTypes={supportingDocumentsTypes} operator={operator} />
); }; @@ -34,4 +86,4 @@ const MembersListWrapper: React.FC = (props) => { ); }; -Application.Components.component('membersList', react2angular(MembersListWrapper, ['members', 'onError', 'onSuccess'])); +Application.Components.component('membersList', react2angular(MembersListWrapper, ['members', 'onError', 'onSuccess', 'operator', 'onDeleteMember', 'onDeletedChild', 'onUpdatedChild'])); diff --git a/app/frontend/src/javascript/controllers/admin/members.js b/app/frontend/src/javascript/controllers/admin/members.js index e26a3da48..e7b388827 100644 --- a/app/frontend/src/javascript/controllers/admin/members.js +++ b/app/frontend/src/javascript/controllers/admin/members.js @@ -291,7 +291,7 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', Member.delete( { id: memberId }, function () { - $scope.members.splice(findItemIdxById($scope.members, memberId), 1); + $scope.members = _.filter($scope.members, function (m) { return m.id !== memberId; }); return growl.success(_t('app.admin.members.member_successfully_deleted')); }, function (error) { @@ -303,6 +303,32 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', ); }; + $scope.onDeletedChild = function (memberId, childId) { + $scope.members = $scope.members.map(function (member) { + if (member.id === memberId) { + member.children = _.filter(member.children, function (c) { return c.id !== childId; }); + return member; + } + return member; + }); + }; + + $scope.onUpdatedChild = function (memberId, child) { + $scope.members = $scope.members.map(function (member) { + if (member.id === memberId) { + member.children = member.children.map(function (c) { + if (c.id === child.id) { + return child; + } + return c; + }); + console.log(member.children); + return member; + } + return member; + }); + }; + /** * Ask for confirmation then delete the specified administrator * @param admins {Array} full list of administrators @@ -588,6 +614,20 @@ Application.Controllers.controller('AdminMembersController', ['$scope', '$sce', } }; + /** + * Callback triggered in case of error + */ + $scope.onError = (message) => { + growl.error(message); + }; + + /** + * Callback triggered in case of success + */ + $scope.onSuccess = (message) => { + growl.success(message); + }; + /* PRIVATE SCOPE */ /** diff --git a/app/frontend/src/javascript/models/member.ts b/app/frontend/src/javascript/models/member.ts index b163c3814..3c9e8cad2 100644 --- a/app/frontend/src/javascript/models/member.ts +++ b/app/frontend/src/javascript/models/member.ts @@ -1,4 +1,5 @@ import { TDateISO } from '../typings/date-iso'; +import { Child } from './child'; export interface Member { maxMembers: number @@ -16,6 +17,7 @@ export interface Member { } subscribed_plan?: Plan validated_at: TDateISO + children: Child[] } interface Plan { diff --git a/app/frontend/templates/admin/members/members.html b/app/frontend/templates/admin/members/members.html index 36d1e43a5..22fe77590 100644 --- a/app/frontend/templates/admin/members/members.html +++ b/app/frontend/templates/admin/members/members.html @@ -35,7 +35,9 @@ - +
+ +
diff --git a/app/views/api/members/list.json.jbuilder b/app/views/api/members/list.json.jbuilder index 38dd1b1a2..97840a4b8 100644 --- a/app/views/api/members/list.json.jbuilder +++ b/app/views/api/members/list.json.jbuilder @@ -18,4 +18,16 @@ json.array!(@members) do |member| end end json.validated_at member.validated_at + json.children member.children.order(:created_at) do |child| + json.extract! child, :id, :first_name, :last_name, :email, :birthday, :phone, :user_id, :validated_at + json.supporting_document_files_attributes child.supporting_document_files do |f| + json.id f.id + json.supportable_id f.supportable_id + json.supportable_type f.supportable_type + json.supporting_document_type_id f.supporting_document_type_id + json.attachment f.attachment.file&.filename + json.attachment_name f.attachment_identifier + json.attachment_url f.attachment_identifier ? "/api/supporting_document_files/#{f.id}/download" : nil + end + end end