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
|
authorize Child
|
||||||
user_id = current_user.id
|
user_id = current_user.id
|
||||||
user_id = params[:user_id] if current_user.privileged? && params[: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
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@ -11,7 +11,7 @@ interface ChildItemProps {
|
|||||||
child: Child;
|
child: Child;
|
||||||
size: 'sm' | 'lg';
|
size: 'sm' | 'lg';
|
||||||
onEdit: (child: Child) => void;
|
onEdit: (child: Child) => void;
|
||||||
onDelete: (error: string) => void;
|
onDelete: (child: Child, error: string) => void;
|
||||||
onError: (error: string) => void;
|
onError: (error: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,8 +33,9 @@ export const ChildItem: React.FC<ChildItemProps> = ({ child, size, onEdit, onDel
|
|||||||
const deleteChild = () => {
|
const deleteChild = () => {
|
||||||
ChildAPI.destroy(child.id).then(() => {
|
ChildAPI.destroy(child.id).then(() => {
|
||||||
toggleDeleteChildModal();
|
toggleDeleteChildModal();
|
||||||
onDelete(t('app.public.child_item.deleted'));
|
onDelete(child, t('app.public.child_item.deleted'));
|
||||||
}).catch(() => {
|
}).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
onError(t('app.public.child_item.unable_to_delete'));
|
onError(t('app.public.child_item.unable_to_delete'));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -13,7 +13,7 @@ interface ChildModalProps {
|
|||||||
operator: User;
|
operator: User;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
toggleModal: () => void;
|
toggleModal: () => void;
|
||||||
onSuccess: (msg: string) => void;
|
onSuccess: (child: Child, msg: string) => void;
|
||||||
onError: (error: string) => void;
|
onError: (error: string) => void;
|
||||||
supportingDocumentsTypes: Array<SupportingDocumentType>;
|
supportingDocumentsTypes: Array<SupportingDocumentType>;
|
||||||
}
|
}
|
||||||
@ -35,7 +35,7 @@ export const ChildModal: React.FC<ChildModalProps> = ({ child, isOpen, toggleMod
|
|||||||
await ChildAPI.create(data);
|
await ChildAPI.create(data);
|
||||||
}
|
}
|
||||||
toggleModal();
|
toggleModal();
|
||||||
onSuccess('');
|
onSuccess(data, '');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
onError(error);
|
onError(error);
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ export const ChildrenDashboard: React.FC<ChildrenDashboardProps> = ({ user, oper
|
|||||||
/**
|
/**
|
||||||
* Delete a child
|
* Delete a child
|
||||||
*/
|
*/
|
||||||
const handleDeleteChildSuccess = (msg: string) => {
|
const handleDeleteChildSuccess = (_child: Child, msg: string) => {
|
||||||
ChildAPI.index({ user_id: user.id }).then(setChildren);
|
ChildAPI.index({ user_id: user.id }).then(setChildren);
|
||||||
onSuccess(msg);
|
onSuccess(msg);
|
||||||
};
|
};
|
||||||
@ -78,7 +78,7 @@ export const ChildrenDashboard: React.FC<ChildrenDashboardProps> = ({ user, oper
|
|||||||
/**
|
/**
|
||||||
* Handle save child success from the API
|
* 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);
|
ChildAPI.index({ user_id: user.id }).then(setChildren);
|
||||||
if (msg) {
|
if (msg) {
|
||||||
onSuccess(msg);
|
onSuccess(msg);
|
||||||
|
@ -1,32 +1,28 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Member } from '../../models/member';
|
import { Member } from '../../models/member';
|
||||||
import { Child } from '../../models/child';
|
import { Child } from '../../models/child';
|
||||||
import ChildAPI from '../../api/child';
|
|
||||||
import { FabButton } from '../base/fab-button';
|
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 { ChildItem } from '../family-account/child-item';
|
||||||
import { EditDestroyButtons } from '../base/edit-destroy-buttons';
|
|
||||||
|
|
||||||
interface MembersListItemProps {
|
interface MembersListItemProps {
|
||||||
member: Member,
|
member: Member,
|
||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
onSuccess: (message: string) => void
|
onSuccess: (message: string) => void
|
||||||
|
onEditChild: (child: Child) => void;
|
||||||
|
onDeleteChild: (child: Child, error: string) => void;
|
||||||
|
onDeleteMember: (memberId: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Members list
|
* 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 { t } = useTranslation('admin');
|
||||||
|
|
||||||
const [children, setChildren] = useState<Array<Child>>([]);
|
|
||||||
const [childrenList, setChildrenList] = useState(false);
|
const [childrenList, setChildrenList] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
ChildAPI.index({ user_id: member.id }).then(setChildren);
|
|
||||||
}, [member]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect to the given user edition page
|
* 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 key={member.id} className={`members-list-item ${member.validated_at ? 'is-validated' : ''} ${member.need_completion ? 'is-incomplet' : ''}`}>
|
||||||
<div className="left-col">
|
<div className="left-col">
|
||||||
<div className='status'>
|
<div className='status'>
|
||||||
{(children.length > 0)
|
{(member.children.length > 0)
|
||||||
? <Users size={24} weight="bold" />
|
? <Users size={24} weight="bold" />
|
||||||
: <User size={24} weight="bold" />
|
: <User size={24} weight="bold" />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{(children.length > 0) &&
|
{(member.children.length > 0) &&
|
||||||
<FabButton onClick={() => setChildrenList(!childrenList)} className={`toggle ${childrenList ? 'open' : ''}`}>
|
<FabButton onClick={() => setChildrenList(!childrenList)} className={`toggle ${childrenList ? 'open' : ''}`}>
|
||||||
<CaretDown size={24} weight="bold" />
|
<CaretDown size={24} weight="bold" />
|
||||||
</FabButton>
|
</FabButton>
|
||||||
@ -78,22 +74,21 @@ export const MembersListItem: React.FC<MembersListItemProps> = ({ member, onErro
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="member-actions">
|
<div className="member-actions edit-destroy-buttons">
|
||||||
{/* TODO: <EditDestroyButtons> */}
|
<FabButton onClick={() => toMemberEdit(member.id)} className="edit-btn">
|
||||||
<EditDestroyButtons onError={onError}
|
<PencilSimple size={20} weight="fill" />
|
||||||
onEdit={() => toMemberEdit(member.id)}
|
</FabButton>
|
||||||
onDeleteSuccess={() => onSuccess}
|
<FabButton onClick={() => onDeleteMember(member.id)} className="delete-btn">
|
||||||
itemId={member.id}
|
<Trash size={20} weight="fill" />
|
||||||
itemType={t('app.admin.members_list_item.item_type')}
|
</FabButton>
|
||||||
destroy={() => new Promise(() => console.log(`Delete member ${member.id}`))} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ (children.length > 0) &&
|
{ (member.children.length > 0) &&
|
||||||
<div className={`member-children ${childrenList ? 'open' : ''}`}>
|
<div className={`member-children ${childrenList ? 'open' : ''}`}>
|
||||||
<hr />
|
<hr />
|
||||||
{children.map(child => (
|
{member.children.map((child: Child) => (
|
||||||
<ChildItem key={child.id} child={child} size='sm' onEdit={() => console.log('edit child')} onDelete={() => console.log('delete child')} onError={onError} />
|
<ChildItem key={child.id} child={child} size='sm' onEdit={onEditChild} onDelete={onDeleteChild} onError={onError} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,79 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { IApplication } from '../../models/application';
|
import { IApplication } from '../../models/application';
|
||||||
import { Loader } from '../base/loader';
|
import { Loader } from '../base/loader';
|
||||||
import { react2angular } from 'react2angular';
|
import { react2angular } from 'react2angular';
|
||||||
import { Member } from '../../models/member';
|
import { Member } from '../../models/member';
|
||||||
import { MembersListItem } from './members-list-item';
|
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;
|
declare const Application: IApplication;
|
||||||
|
|
||||||
interface MembersListProps {
|
interface MembersListProps {
|
||||||
members: Member[],
|
members: Member[],
|
||||||
|
operator: User,
|
||||||
onError: (message: string) => void,
|
onError: (message: string) => void,
|
||||||
onSuccess: (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
|
* 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 (
|
return (
|
||||||
<div className="members-list">
|
<div className="members-list">
|
||||||
{members.map(member => (
|
{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>
|
</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(
|
Member.delete(
|
||||||
{ id: memberId },
|
{ id: memberId },
|
||||||
function () {
|
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'));
|
return growl.success(_t('app.admin.members.member_successfully_deleted'));
|
||||||
},
|
},
|
||||||
function (error) {
|
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
|
* Ask for confirmation then delete the specified administrator
|
||||||
* @param admins {Array} full list of administrators
|
* @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 */
|
/* PRIVATE SCOPE */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { TDateISO } from '../typings/date-iso';
|
import { TDateISO } from '../typings/date-iso';
|
||||||
|
import { Child } from './child';
|
||||||
|
|
||||||
export interface Member {
|
export interface Member {
|
||||||
maxMembers: number
|
maxMembers: number
|
||||||
@ -16,6 +17,7 @@ export interface Member {
|
|||||||
}
|
}
|
||||||
subscribed_plan?: Plan
|
subscribed_plan?: Plan
|
||||||
validated_at: TDateISO
|
validated_at: TDateISO
|
||||||
|
children: Child[]
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Plan {
|
interface Plan {
|
||||||
|
@ -35,7 +35,9 @@
|
|||||||
<iframe name="export-frame" height="0" width="0" class="none"></iframe>
|
<iframe name="export-frame" height="0" width="0" class="none"></iframe>
|
||||||
</div>
|
</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">
|
<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>
|
<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
|
||||||
end
|
end
|
||||||
json.validated_at member.validated_at
|
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
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user