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:
parent
bc8521fc15
commit
b4781b1642
@ -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
|
||||
|
@ -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'));
|
||||
});
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
}
|
||||
|
@ -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']));
|
||||
|
@ -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 */
|
||||
|
||||
/**
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user