1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-22 11:52:21 +01:00

(feat) edit/delete child in members list

This commit is contained in:
Du Peng 2023-05-31 18:20:14 +02:00
parent bc8521fc15
commit b4781b1642
10 changed files with 141 additions and 37 deletions

View File

@ -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

View File

@ -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<ChildItemProps> = ({ 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'));
});
};

View File

@ -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<SupportingDocumentType>;
}
@ -35,7 +35,7 @@ export const ChildModal: React.FC<ChildModalProps> = ({ child, isOpen, toggleMod
await ChildAPI.create(data);
}
toggleModal();
onSuccess('');
onSuccess(data, '');
} catch (error) {
onError(error);
}

View File

@ -70,7 +70,7 @@ export const ChildrenDashboard: React.FC<ChildrenDashboardProps> = ({ 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<ChildrenDashboardProps> = ({ 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);

View File

@ -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<MembersListItemProps> = ({ member, onError, onSuccess }) => {
export const MembersListItem: React.FC<MembersListItemProps> = ({ member, onError, onEditChild, onDeleteChild, onDeleteMember }) => {
const { t } = useTranslation('admin');
const [children, setChildren] = useState<Array<Child>>([]);
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<MembersListItemProps> = ({ member, onErro
<div key={member.id} className={`members-list-item ${member.validated_at ? 'is-validated' : ''} ${member.need_completion ? 'is-incomplet' : ''}`}>
<div className="left-col">
<div className='status'>
{(children.length > 0)
{(member.children.length > 0)
? <Users size={24} weight="bold" />
: <User size={24} weight="bold" />
}
</div>
{(children.length > 0) &&
{(member.children.length > 0) &&
<FabButton onClick={() => setChildrenList(!childrenList)} className={`toggle ${childrenList ? 'open' : ''}`}>
<CaretDown size={24} weight="bold" />
</FabButton>
@ -78,22 +74,21 @@ export const MembersListItem: React.FC<MembersListItemProps> = ({ member, onErro
</div>
</div>
<div className="member-actions">
{/* TODO: <EditDestroyButtons> */}
<EditDestroyButtons onError={onError}
onEdit={() => 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}`))} />
<div className="member-actions edit-destroy-buttons">
<FabButton onClick={() => toMemberEdit(member.id)} className="edit-btn">
<PencilSimple size={20} weight="fill" />
</FabButton>
<FabButton onClick={() => onDeleteMember(member.id)} className="delete-btn">
<Trash size={20} weight="fill" />
</FabButton>
</div>
</div>
{ (children.length > 0) &&
{ (member.children.length > 0) &&
<div className={`member-children ${childrenList ? 'open' : ''}`}>
<hr />
{children.map(child => (
<ChildItem key={child.id} child={child} size='sm' onEdit={() => console.log('edit child')} onDelete={() => console.log('delete child')} onError={onError} />
{member.children.map((child: Child) => (
<ChildItem key={child.id} child={child} size='sm' onEdit={onEditChild} onDelete={onDeleteChild} onError={onError} />
))}
</div>
}

View File

@ -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<MembersListProps> = ({ members, onError, onSuccess }) => {
export const MembersList: React.FC<MembersListProps> = ({ members, onError, onSuccess, operator, onDeleteMember, onDeletedChild, onUpdatedChild }) => {
const [supportingDocumentsTypes, setSupportingDocumentsTypes] = useState<Array<SupportingDocumentType>>([]);
const [child, setChild] = useState<Child>();
const [isOpenChildModal, setIsOpenChildModal] = useState<boolean>(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 (
<div className="members-list">
{members.map(member => (
<MembersListItem key={member.id} member={member} onError={onError} onSuccess={onSuccess} />
<MembersListItem key={member.id} member={member} onError={onError} onSuccess={onSuccess} onDeleteMember={onDeleteMember} onEditChild={editChild} onDeleteChild={handleDeleteChildSuccess} />
))}
<ChildModal child={child} isOpen={isOpenChildModal} toggleModal={() => setIsOpenChildModal(false)} onSuccess={handleSaveChildSuccess} onError={onError} supportingDocumentsTypes={supportingDocumentsTypes} operator={operator} />
</div>
);
};
@ -34,4 +86,4 @@ const MembersListWrapper: React.FC<MembersListProps> = (props) => {
);
};
Application.Components.component('membersList', react2angular(MembersListWrapper, ['members', 'onError', 'onSuccess']));
Application.Components.component('membersList', react2angular(MembersListWrapper, ['members', 'onError', 'onSuccess', 'operator', 'onDeleteMember', 'onDeletedChild', 'onUpdatedChild']));

View File

@ -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 */
/**

View File

@ -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 {

View File

@ -35,7 +35,9 @@
<iframe name="export-frame" height="0" width="0" class="none"></iframe>
</div>
<members-list members="members" on-success="onSuccess" onError="onError" />
<div>
<members-list members="members" on-success="onSuccess" on-error="onError" operator="currentUser" on-delete-member="deleteMember" on-deleted-child="onDeletedChild" on-updated-child="onUpdatedChild" />
</div>
<div class="text-center">
<button class="btn btn-warning show-more" ng-click="showNextMembers()" ng-hide="member.noMore"><i class="fa fa-search-plus" aria-hidden="true"></i> {{ 'app.admin.members.display_more_users' | translate }}</button>

View File

@ -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