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

(ui) integration

This commit is contained in:
Vincent 2023-05-31 11:29:10 +02:00
parent e17c0be064
commit c019f3ad3e
27 changed files with 654 additions and 222 deletions

View File

@ -10,6 +10,7 @@ import { FileType } from '../../models/file';
import { SupportingDocumentType } from '../../models/supporting-document-type';
import { User } from '../../models/user';
import { SupportingDocumentsRefusalModal } from '../supporting-documents/supporting-documents-refusal-modal';
import { FabAlert } from '../base/fab-alert';
interface ChildFormProps {
child: Child;
@ -65,11 +66,12 @@ export const ChildForm: React.FC<ChildFormProps> = ({ child, onSubmit, supportin
return (
<div className="child-form">
{!isPrivileged() &&
<div className="info-area">
{t('app.public.child_form.child_form_info')}
</div>
<FabAlert level='info'>
<p>{t('app.public.child_form.child_form_info')}</p>
</FabAlert>
}
<form onSubmit={handleSubmit(onSubmit)}>
<div className="grp">
<FormInput id="first_name"
register={register}
rules={{ required: true }}
@ -82,6 +84,8 @@ export const ChildForm: React.FC<ChildFormProps> = ({ child, onSubmit, supportin
formState={formState}
label={t('app.public.child_form.last_name')}
/>
</div>
<div className="grp">
<FormInput id="birthday"
register={register}
rules={{ required: true, validate: (value) => moment(value).isAfter(moment().subtract(18, 'year')) }}
@ -97,28 +101,16 @@ export const ChildForm: React.FC<ChildFormProps> = ({ child, onSubmit, supportin
label={t('app.public.child_form.phone')}
type="tel"
/>
</div>
<FormInput id="email"
register={register}
formState={formState}
label={t('app.public.child_form.email')}
/>
{!isPrivileged() && <>
<h3 className="missing-file">{t('app.public.child_form.supporting_documents')}</h3>
{output.supporting_document_files_attributes.map((sf, index) => {
if (isPrivileged()) {
return (
<div key={index} className="document-type">
<div className="type-name">{getSupportingDocumentsTypeName(sf.supporting_document_type_id)}</div>
{sf.attachment_url && (
<a href={sf.attachment_url} target="_blank" rel="noreferrer">
<span className="filename">{sf.attachment}</span>
<i className="fa fa-download"></i>
</a>
)}
{!sf.attachment_url && (
<div className="missing-file">{t('app.public.child_form.to_complete')}</div>
)}
</div>
);
}
return (
<FormFileUpload key={index}
defaultFile={sf as FileType}
@ -131,14 +123,46 @@ export const ChildForm: React.FC<ChildFormProps> = ({ child, onSubmit, supportin
formState={formState} />
);
})}
</>}
<div className="actions">
<FabButton type="button" onClick={handleSubmit(onSubmit)}>
<FabButton type="button" className='is-secondary' onClick={handleSubmit(onSubmit)}>
{t('app.public.child_form.save')}
</FabButton>
{isPrivileged() &&
<div>
<FabButton className="refuse-btn" onClick={toggleRefuseModal}>{t('app.public.child_form.refuse_documents')}</FabButton>
</div>
{isPrivileged() && <>
<h3 className="missing-file">{t('app.public.child_form.supporting_documents')}</h3>
<div className="document-list">
{output.supporting_document_files_attributes.map((sf, index) => {
return (
<div key={index} className="document-list-item">
<span className="type">{getSupportingDocumentsTypeName(sf.supporting_document_type_id)}</span>
{sf.attachment_url && (
<div className='file'>
<p>{sf.attachment}</p>
<a href={sf.attachment_url} target="_blank" rel="noreferrer" className='fab-button is-black'>
<span className="fab-button--icon-only"><i className="fas fa-eye"></i></span>
</a>
</div>
)}
{!sf.attachment_url && (
<div className="missing">
<p>{t('app.public.child_form.to_complete')}</p>
</div>
)}
</div>
);
})}
</div>
</>}
{isPrivileged() && <>
<FabAlert level='info'>
<p>{t('app.public.child_form.refuse_documents_info')}</p>
</FabAlert>
<div className="actions">
<FabButton className="refuse-btn is-secondary" onClick={toggleRefuseModal}>{t('app.public.child_form.refuse_documents')}</FabButton>
<SupportingDocumentsRefusalModal
isOpen={refuseModalIsOpen}
proofOfIdentityTypes={supportingDocumentsTypes}
@ -149,8 +173,7 @@ export const ChildForm: React.FC<ChildFormProps> = ({ child, onSubmit, supportin
onError={onError}
onSuccess={onSaveRefusalSuccess} />
</div>
}
</div>
</>}
</form>
</div>
);

View File

@ -5,9 +5,11 @@ import { FabButton } from '../base/fab-button';
import FormatLib from '../../lib/format';
import { DeleteChildModal } from './delete-child-modal';
import ChildAPI from '../../api/child';
import { PencilSimple, Trash, UserSquare } from 'phosphor-react';
interface ChildItemProps {
child: Child;
size: 'sm' | 'lg';
onEdit: (child: Child) => void;
onDelete: (error: string) => void;
onError: (error: string) => void;
@ -16,7 +18,7 @@ interface ChildItemProps {
/**
* A child item.
*/
export const ChildItem: React.FC<ChildItemProps> = ({ child, onEdit, onDelete, onError }) => {
export const ChildItem: React.FC<ChildItemProps> = ({ child, size, onEdit, onDelete, onError }) => {
const { t } = useTranslation('public');
const [isOpenDeleteChildModal, setIsOpenDeleteChildModal] = React.useState<boolean>(false);
@ -38,22 +40,29 @@ export const ChildItem: React.FC<ChildItemProps> = ({ child, onEdit, onDelete, o
};
return (
<div className="child-item">
<div className="child-lastname">
<div className={`child-item ${size} ${child.validated_at ? 'is-validated' : ''}`}>
<div className='status'>
<UserSquare size={24} weight="light" />
</div>
<div>
<span>{t('app.public.child_item.last_name')}</span>
<div>{child.last_name}</div>
<p>{child.last_name}</p>
</div>
<div className="child-firstname">
<div>
<span>{t('app.public.child_item.first_name')}</span>
<div>{child.first_name}</div>
<p>{child.first_name}</p>
</div>
<div className="date">
<div>
<span>{t('app.public.child_item.birthday')}</span>
<div>{FormatLib.date(child.birthday)}</div>
<p>{FormatLib.date(child.birthday)}</p>
</div>
<div className="actions">
<FabButton icon={<i className="fa fa-edit" />} onClick={() => onEdit(child)} className="edit-button" />
<FabButton icon={<i className="fa fa-trash" />} onClick={toggleDeleteChildModal} className="delete-button" />
<div className="actions edit-destroy-buttons">
<FabButton onClick={() => onEdit(child)} className="edit-btn">
<PencilSimple size={20} weight="fill" />
</FabButton>
<FabButton onClick={toggleDeleteChildModal} className="delete-btn">
<Trash size={20} weight="fill" />
</FabButton>
<DeleteChildModal isOpen={isOpenDeleteChildModal} toggleModal={toggleDeleteChildModal} child={child} onDelete={deleteChild} />
</div>
</div>

View File

@ -14,9 +14,10 @@ import SupportingDocumentTypeAPI from '../../api/supporting-document-type';
declare const Application: IApplication;
interface ChildrenListProps {
interface ChildrenDashboardProps {
user: User;
operator: User;
adminPanel?: boolean;
onSuccess: (error: string) => void;
onError: (error: string) => void;
}
@ -24,7 +25,7 @@ interface ChildrenListProps {
/**
* A list of children belonging to the current user.
*/
export const ChildrenList: React.FC<ChildrenListProps> = ({ user, operator, onError, onSuccess }) => {
export const ChildrenDashboard: React.FC<ChildrenDashboardProps> = ({ user, operator, adminPanel, onError, onSuccess }) => {
const { t } = useTranslation('public');
const [children, setChildren] = useState<Array<Child>>([]);
@ -92,19 +93,24 @@ export const ChildrenList: React.FC<ChildrenListProps> = ({ user, operator, onEr
};
return (
<section>
<section className='children-dashboard'>
<header>
<h2>{t('app.public.children_list.heading')}</h2>
{adminPanel
? <h2>{t('app.public.children_dashboard.heading')}</h2>
: <h2>{t('app.public.children_dashboard.member_heading')}</h2>
}
{!isPrivileged() && (
<FabButton onClick={addChild}>
{t('app.public.children_list.add_child')}
<div className="grpBtn">
<FabButton className="main-action-btn" onClick={addChild}>
{t('app.public.children_dashboard.add_child')}
</FabButton>
</div>
)}
</header>
<div className="children-list">
{children.map(child => (
<ChildItem key={child.id} child={child} onEdit={editChild} onDelete={handleDeleteChildSuccess} onError={onError} />
<ChildItem key={child.id} child={child} size='lg' onEdit={editChild} onDelete={handleDeleteChildSuccess} onError={onError} />
))}
</div>
<ChildModal child={child} isOpen={isOpenChildModal} toggleModal={() => setIsOpenChildModal(false)} onSuccess={handleSaveChildSuccess} onError={onError} supportingDocumentsTypes={supportingDocumentsTypes} operator={operator} />
@ -112,12 +118,12 @@ export const ChildrenList: React.FC<ChildrenListProps> = ({ user, operator, onEr
);
};
const ChildrenListWrapper: React.FC<ChildrenListProps> = (props) => {
const ChildrenDashboardWrapper: React.FC<ChildrenDashboardProps> = (props) => {
return (
<Loader>
<ChildrenList {...props} />
<ChildrenDashboard {...props} />
</Loader>
);
};
Application.Components.component('childrenList', react2angular(ChildrenListWrapper, ['user', 'operator', 'onSuccess', 'onError']));
Application.Components.component('childrenDashboard', react2angular(ChildrenDashboardWrapper, ['user', 'operator', 'adminPanel', 'onSuccess', 'onError']));

View File

@ -75,9 +75,10 @@ export const FormFileUpload = <TFieldValues extends FieldValues>({ id, label, re
return (
<div className={`form-file-upload ${label ? 'with-label' : ''} ${classNames}`}>
{hasFile() && (
<span>{file.attachment_name}</span>
)}
{hasFile()
? <span>{file.attachment_name}</span>
: <span className='placeholder'>{t('app.shared.form_file_upload.placeholder')}</span>
}
<div className="actions">
{file?.id && file?.attachment_url && (
<a href={file.attachment_url}

View File

@ -15,6 +15,7 @@ import SupportingDocumentTypeAPI from '../../api/supporting-document-type';
import { FabPanel } from '../base/fab-panel';
import { FabAlert } from '../base/fab-alert';
import { FabButton } from '../base/fab-button';
import { PencilSimple, Trash } from 'phosphor-react';
declare const Application: IApplication;
@ -248,12 +249,12 @@ const SupportingDocumentsTypesList: React.FC<SupportingDocumentsTypesListProps>
<td>{getGroupsNames(poit.group_ids)}</td>
<td>{poit.name}</td>
<td>
<div className="buttons">
<div className="edit-destroy-buttons">
<FabButton className="edit-btn" onClick={editType(poit)}>
<i className="fa fa-edit" />
<PencilSimple size={20} weight="fill" />
</FabButton>
<FabButton className="delete-btn" onClick={destroyType(poit.id)}>
<i className="fa fa-trash" />
<Trash size={20} weight="fill" />
</FabButton>
</div>
</td>
@ -292,38 +293,26 @@ const SupportingDocumentsTypesList: React.FC<SupportingDocumentsTypesListProps>
onSuccess={onDestroySuccess}
onError={onError}/>
<table>
<thead>
<tr>
<th className="name">
<a onClick={setTypeOrder('name')}>
{t('app.admin.settings.account.supporting_documents_types_list.name')}
<i className={orderClassName('name')} />
</a>
</th>
<th className="actions"></th>
</tr>
</thead>
<tbody>
<div className="document-list">
{supportingDocumentsTypes.map(poit => {
return (
<tr key={poit.id}>
<td>{poit.name}</td>
<td>
<div className="buttons">
<div key={poit.id} className="document-list-item">
<div className='file'>
<p>{poit.name}</p>
<div className="edit-destroy-buttons">
<FabButton className="edit-btn" onClick={editType(poit)}>
<i className="fa fa-edit" />
<PencilSimple size={20} weight="fill" />
</FabButton>
<FabButton className="delete-btn" onClick={destroyType(poit.id)}>
<i className="fa fa-trash" />
<Trash size={20} weight="fill" />
</FabButton>
</div>
</td>
</tr>
</div>
</div>
);
})}
</tbody>
</table>
</div>
{!hasTypes() && (
<p className="no-types-info">
<HtmlTranslate trKey="app.admin.settings.account.supporting_documents_types_list.no_types" />

View File

@ -0,0 +1,102 @@
import React, { useEffect, 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 { 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
}
/**
* Members list
*/
export const MembersListItem: React.FC<MembersListItemProps> = ({ member, onError, onSuccess }) => {
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
*/
const toMemberEdit = (memberId: number): void => {
window.location.href = `/#!/admin/members/${memberId}/edit`;
};
return (
<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)
? <Users size={24} weight="bold" />
: <User size={24} weight="bold" />
}
</div>
{(children.length > 0) &&
<FabButton onClick={() => setChildrenList(!childrenList)} className={`toggle ${childrenList ? 'open' : ''}`}>
<CaretDown size={24} weight="bold" />
</FabButton>
}
</div>
<div className="member">
<div className="member-infos">
<div>
<span>{t('app.admin.members_list_item.surname')}</span>
<p>{member.profile.last_name}</p>
</div>
<div>
<span>{t('app.admin.members_list_item.first_name')}</span>
<p>{member.profile.first_name}</p>
</div>
<div>
<span>{t('app.admin.members_list_item.phone')}</span>
<p>{member.profile.phone || '---'}</p>
</div>
<div>
<span>{t('app.admin.members_list_item.email')}</span>
<p>{member.email}</p>
</div>
<div>
<span>{t('app.admin.members_list_item.group')}</span>
<p>{member.group.name}</p>
</div>
<div>
<span>{t('app.admin.members_list_item.subscription')}</span>
<p>{member.subscribed_plan?.name || '---'}</p>
</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>
</div>
{ (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} />
))}
</div>
}
</div>
);
};

View File

@ -0,0 +1,37 @@
import React 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';
declare const Application: IApplication;
interface MembersListProps {
members: Member[],
onError: (message: string) => void,
onSuccess: (message: string) => void
}
/**
* Members list
*/
export const MembersList: React.FC<MembersListProps> = ({ members, onError, onSuccess }) => {
return (
<div className="members-list">
{members.map(member => (
<MembersListItem key={member.id} member={member} onError={onError} onSuccess={onSuccess} />
))}
</div>
);
};
const MembersListWrapper: React.FC<MembersListProps> = (props) => {
return (
<Loader>
<MembersList {...props} />
</Loader>
);
};
Application.Components.component('membersList', react2angular(MembersListWrapper, ['members', 'onError', 'onSuccess']));

View File

@ -0,0 +1,47 @@
import { TDateISO } from '../typings/date-iso';
export interface Member {
maxMembers: number
id: number
username: string
email: string
profile: {
first_name: string
last_name: string
phone: string
}
need_completion?: boolean
group: {
name: string
}
subscribed_plan?: Plan
validated_at: TDateISO
}
interface Plan {
id: number
base_name: string
name: string
amount: number
interval: string
interval_count: number
training_credit_nb: number
training_credits: [
{
training_id: number
},
{
training_id: number
}
]
machine_credits: [
{
machine_id: number
hours: number
},
{
machine_id: number
hours: number
}
]
}

View File

@ -52,6 +52,9 @@
@import "modules/events/event-form";
@import "modules/events/update-recurrent-modal";
@import "modules/events/events-settings.scss";
@import "modules/family-account/child-form";
@import "modules/family-account/child-item";
@import "modules/family-account/children-dashboard";
@import "modules/form/abstract-form-item";
@import "modules/form/form-input";
@import "modules/form/form-multi-file-upload";
@ -181,8 +184,6 @@
@import "modules/tour";
@import "modules/wallet-info";
@import "modules/family-account/child-item";
@import "app.responsive";
@import "overrides";

View File

@ -1,6 +1,9 @@
.edit-destroy-buttons {
width: fit-content;
flex-shrink: 0;
border-radius: var(--border-radius-sm);
overflow: hidden;
button {
@include btn;
border-radius: 0;

View File

@ -30,6 +30,7 @@
animation: 0.3s ease-out slideInFromTop;
position: relative;
top: 90px;
max-width: 100vw;
margin: auto;
opacity: 1;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);

View File

@ -0,0 +1,43 @@
.child-form {
.grp {
display: flex;
flex-direction: column;
@media (min-width: 640px) {flex-direction: row; }
.form-item:first-child { margin-right: 2.4rem; }
}
hr { width: 100%; }
.actions {
align-self: flex-end;
}
.document-list {
margin-bottom: 1.6rem;
display: flex;
flex-direction: column;
gap: 1.6rem;
&-item {
display: flex;
flex-direction: column;
gap: 0.8rem;
.type {
@include text-sm;
}
.file,
.missing {
padding: 0.8rem 0.8rem 0.8rem 1.6rem;
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid var(--gray-soft-dark);
border-radius: var(--border-radius);
p { margin: 0; }
}
.missing {
background-color: var(--gray-soft-light);
}
}
}
}

View File

@ -1,12 +1,62 @@
.child-item {
width: 100%;
display: grid;
grid-template-rows: repeat(3, min-content);
grid-template-columns: 1fr 1fr;
grid-template-columns: min-content 1fr;
align-items: flex-start;
gap: 1.6rem 2.4rem;
align-items: center;
background-color: var(--gray-soft-lightest);
&.lg {
padding: 1.6rem;
border: 1px solid var(--gray-soft-dark);
border-radius: var(--border-radius);
background-color: var(--gray-soft-lightest);
}
&.sm {
.actions button {
height: 3rem !important;
min-height: auto;
}
}
& > div:not(.actions) {
display: flex;
flex-direction: column;
span {
@include text-xs;
color: var(--gray-hard-light);
}
}
p {
margin: 0;
@include text-base(600);
}
&.sm p {
@include text-sm(500);
}
.status {
grid-row: 1/5;
align-self: stretch;
display: flex;
align-items: center;
}
&.is-validated .status svg {
color: var(--success-dark);
}
.actions {
align-self: center;
justify-self: flex-end;
}
@media (min-width: 768px) {
grid-template-columns: min-content repeat(3, 1fr);
.status { grid-row: auto; }
.actions {
grid-column-end: -1;
display: flex;
}
}
@media (min-width: 1024px) {
grid-template-columns: min-content repeat(3, 1fr) max-content;
}
}

View File

@ -0,0 +1,20 @@
.children-dashboard {
max-width: 1600px;
margin: 0 auto;
padding-bottom: 6rem;
@include grid-col(12);
gap: 3.2rem;
align-items: flex-start;
header {
@include header();
padding-bottom: 0;
grid-column: 2 / -2;
}
.children-list {
grid-column: 2 / -2;
display: flex;
flex-direction: column;
gap: 1.6rem;
}
}

View File

@ -13,6 +13,8 @@
margin-bottom: 1.6rem;
}
.placeholder { color: var(--gray-soft-darkest); }
.actions {
margin-left: auto;
display: flex;

View File

@ -2,3 +2,108 @@
width: 16px;
height: 21px;
}
.members-list {
width: 100%;
margin: 2.4rem 0;
display: flex;
flex-direction: column;
gap: 2.4rem;
&-item {
width: 100%;
padding: 1.6rem;
display: grid;
grid-template-columns: 48px 1fr;
gap: 1.6rem 2.4rem;
border: 1px solid var(--gray-soft-dark);
border-radius: var(--border-radius);
background-color: var(--gray-soft-lightest);
&.is-validated .left-col .status svg { color: var(--success-dark); }
&.is-incomplet .left-col .status svg { color: var(--alert); }
.left-col {
grid-row: span 2;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
.status {
display: flex;
align-items: center;
}
.toggle {
height: fit-content;
background-color: var(--gray-soft);
border: none;
svg { transition: transform 0.5s ease-in-out; }
&.open svg { transform: rotate(-180deg); }
}
}
.member {
display: flex;
flex-direction: column;
gap: 2.4rem;
&-infos {
flex: 1;
display: grid;
gap: 1.6rem;
& > div:not(.actions) {
display: flex;
flex-direction: column;
span {
@include text-xs;
color: var(--gray-hard-light);
}
}
p {
margin: 0;
@include text-base(600);
line-height: 1.5;
}
}
&-actions {
align-self: flex-end;
}
}
.member-children {
max-height: 0;
display: flex;
flex-direction: column;
gap: 1.6rem;
overflow-y: hidden;
transition: max-height 0.5s ease-in-out;
&.open {
max-height: 17rem;
overflow-y: auto;
}
hr { margin: 0; }
.child-item:last-of-type { padding-bottom: 0; }
}
@media (min-width: 768px) {
.member-infos {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.member {
flex-direction: row;
&-actions {
align-self: center;
}
}
}
@media (min-width: 1220px) {
.member-infos {
grid-template-columns: repeat(3, 1fr);
}
}
}
}

View File

@ -37,6 +37,7 @@
}
.title {
margin-bottom: 1.6rem;
display: flex;
flex-direction: row;
justify-content: space-between;
@ -54,22 +55,26 @@
}
}
table {
thead > tr {
th.group-name,
th.name {
width: 40%
}
th.actions {
width: 20%;
}
}
.document-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min-content, 50rem));
gap: 1.6rem;
tbody {
.buttons {
.edit-btn {
margin-right: 5px;
&-item {
display: flex;
flex-direction: column;
gap: 0.8rem;
.type {
@include text-sm;
}
.file {
padding: 0.8rem 0.8rem 0.8rem 1.6rem;
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid var(--gray-soft-dark);
border-radius: var(--border-radius);
p { margin: 0; }
}
}
}

View File

@ -9,3 +9,7 @@
vertical-align: middle;
}
}
.child-validation {
margin: 0 0 2rem;
text-align: center;
}

View File

@ -63,7 +63,7 @@
</uib-tab>
<uib-tab heading="{{ 'app.shared.user_admin.children' | translate }}" ng-if="$root.settings.familyAccount">
<children-list user="user" operator="currentUser" on-success="onSuccess" on-error="onError" />
<children-dashboard user="user" operator="currentUser" admin-panel="true" on-success="onSuccess" on-error="onError" />
</uib-tab>
<uib-tab heading="{{ 'app.admin.members_edit.supporting_documents' | translate }}" ng-show="hasProofOfIdentityTypes">

View File

@ -17,11 +17,12 @@
</div>
</div>
</div>
<div class="col-md-12">
<button type="button" class="btn btn-warning m-t m-b" ui-sref="app.admin.members_new" translate>
<button type="button" class="btn btn-warning m-b" ui-sref="app.admin.members_new" translate>
{{ 'app.admin.members.add_a_new_member' }}
</button>
<div class="pull-right exports-buttons" ng-show="isAuthorized('admin')">
<div class="pull-right exports-buttons m-b" ng-show="isAuthorized('admin')">
<a class="btn btn-default" ng-href="api/members/export_members.xlsx" target="export-frame" ng-click="alertExport('members')">
<i class="fa fa-file-excel-o"></i> {{ 'app.admin.members.members' | translate }}
</a>
@ -34,46 +35,8 @@
<iframe name="export-frame" height="0" width="0" class="none"></iframe>
</div>
<table class="table members-list">
<thead>
<tr>
<th style="width:4%" class="hidden-xs" ng-if="enableUserValidationRequired"></th>
<th style="width:8%" ng-show="displayUsername"><a ng-click="setOrderMember('username')">{{ 'app.admin.members.username' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='username', 'fa fa-sort-alpha-desc': member.order=='-username', 'fa fa-arrows-v': member.order }"></i></a></th>
<th style="width:14%"><a ng-click="setOrderMember('last_name')">{{ 'app.admin.members.surname' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='last_name', 'fa fa-sort-alpha-desc': member.order=='-last_name', 'fa fa-arrows-v': member.order }"></i></a></th>
<th style="width:14%"><a ng-click="setOrderMember('first_name')">{{ 'app.admin.members.first_name' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='first_name', 'fa fa-sort-alpha-desc': member.order=='-first_name', 'fa fa-arrows-v': member.order }"></i></a></th>
<th style="width:14%" class="hidden-xs"><a ng-click="setOrderMember('email')">{{ 'app.admin.members.email' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='email', 'fa fa-sort-alpha-desc': member.order=='-email', 'fa fa-arrows-v': member.order }"></i></a></th>
<th style="width:8%" class="hidden-xs hidden-sm hidden-md"><a ng-click="setOrderMember('phone')">{{ 'app.admin.members.phone' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-numeric-asc': member.order=='phone', 'fa fa-sort-numeric-desc': member.order=='-phone', 'fa fa-arrows-v': member.order }"></i></a></th>
<th style="width:13%" class="hidden-xs hidden-sm"><a ng-click="setOrderMember('group')">{{ 'app.admin.members.user_type' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='group', 'fa fa-sort-alpha-desc': member.order=='-group', 'fa fa-arrows-v': member.order }"></i></a></th>
<th style="width:13%" class="hidden-xs hidden-sm hidden-md"><a ng-click="setOrderMember('plan')">{{ 'app.admin.members.subscription' | translate }} <i class="fa fa-arrows-v" ng-class="{'fa fa-sort-alpha-asc': member.order=='plan', 'fa fa-sort-alpha-desc': member.order=='-plan', 'fa fa-arrows-v': member.order }"></i></a></th>
<th style="width:12%" class="buttons-col"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="m in members">
<td class="text-center" ng-if="enableUserValidationRequired">
<span ng-class="{ 'text-success': !!m.validated_at }"><i class="fa fa-user-check"></i></span>
</td>
<td class="text-c" ng-show="displayUsername">{{ m.username }}</td>
<td class="text-c">{{ m.profile.last_name }}</td>
<td class="text-c">{{ m.profile.first_name }}</td>
<td class="hidden-xs">{{ m.email }}</td>
<td class="hidden-xs hidden-sm hidden-md">{{ m.profile.phone }}</td>
<td class="text-u-c text-sm hidden-xs hidden-sm">{{ m.group.name }}</td>
<td class="hidden-xs hidden-sm hidden-md">{{ m.subscribed_plan | humanReadablePlanName }}</td>
<td>
<div class="buttons">
<button class="btn btn-default edit-member" ui-sref="app.admin.members_edit({id: m.id})">
<i class="fa fa-edit"></i>
</button>
<button class="btn btn-danger delete-member" ng-click="deleteMember(m.id)" ng-show="isAuthorized('admin')">
<i class="fa fa-trash"></i>
</button>
<span class="label label-danger text-white" ng-show="m.need_completion" translate>{{ 'app.shared.user_admin.incomplete_profile' }}</span>
</div>
</td>
</tr>
</tbody>
</table>
<members-list members="members" on-success="onSuccess" onError="onError" />
<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>
</div>

View File

@ -7,5 +7,5 @@
</section>
<children-list user="currentUser" operator="currentUser" on-success="onSuccess" on-error="onError" />
<children-dashboard user="currentUser" operator="currentUser" on-success="onSuccess" on-error="onError" />
</div>

View File

@ -49,7 +49,7 @@
<div class="col-sm-12 col-md-12 col-lg-4">
<section class="widget panel b-a m" ng-if="event.event_files_attributes">
<section class="widget panel b-a m" ng-if="event.event_files_attributes.length">
<div class="panel-heading b-b">
<span class="badge bg-warning pull-right">{{event.event_files_attributes.length}}</span>
<h3 translate>{{ 'app.public.events_show.downloadable_documents' }}</h3>
@ -72,8 +72,11 @@
</div>
<div class="panel-content wrapper">
<div>
<span ng-if="event.event_type === 'nominative'" class="v-middle badge text-base bg-event" translate="">{{ 'app.public.events_show.event_type.nominative' }}</span>
<span ng-if="event.event_type === 'family'" class="v-middle badge text-base bg-event" translate="">{{ 'app.public.events_show.event_type.family' }}</span>
</div>
<h5>{{event.category.name}}</h5>
<dl class="text-sm">
<dt ng-repeat="theme in event.event_themes">
<i class="fa fa-tags" aria-hidden="true"></i> {{theme.name}}
@ -136,17 +139,17 @@
class="form-control">
<option value=""></option>
</select>
<uib-alert type="danger" ng-if="enableChildValidationRequired && user.booked && user.booked.type === 'Child' && !user.booked.validatedAt">
<p class="text-sm">
<uib-alert type="danger" ng-if="enableChildValidationRequired && user.booked && user.booked.type === 'Child' && !user.booked.validatedAt" style="margin-bottom: 0.8rem;">
<span class="text-sm">
<i class="fa fa-warning"></i>
<span translate>{{ 'app.shared.cart.child_validation_required_alert' }}</span>
</p>
</span>
</uib-alert>
<uib-alert type="danger" ng-if="user.booked && user.booked.type === 'Child' && !isUnder18YearsAgo(user.booked.birthday)">
<p class="text-sm">
<uib-alert type="danger" ng-if="user.booked && user.booked.type === 'Child' && !isUnder18YearsAgo(user.booked.birthday)" style="margin-bottom: 0.8rem;">
<span class="text-sm">
<i class="fa fa-warning"></i>
<span translate>{{ 'app.shared.cart.child_birthday_must_be_under_18_years_ago_alert' }}</span>
</p>
</span>
</uib-alert>
</div>
</div>

View File

@ -12,8 +12,11 @@
</ui-select-choices>
</ui-select>
{{member}}
<div class="alert alert-danger m-t" style="margin-bottom: 0 !important;" ng-if="enableUserValidationRequired && ctrl.member.id && !ctrl.member.validated_at">
<uib-alert type="danger" ng-if="enableUserValidationRequired && ctrl.member.id && !ctrl.member.validated_at" style="margin-bottom: 0;">
<span class="text-sm">
<i class="fa fa-warning"></i>
<span translate>{{ 'app.shared.member_select.member_not_validated' }}</span>
</div>
</span>
</uib-alert>
</div>
</div>

View File

@ -1189,6 +1189,14 @@ en:
member_filter_all: "All"
member_filter_not_confirmed: "Unconfirmed"
member_filter_inactive_for_3_years: "Inactive for 3 years"
members_list_item:
item_type: "member"
surname: "Surname"
first_name: "First name"
phone: "Phone"
email: "Email"
group: "Group"
subscription: "Subscription"
#add a member
members_new:
add_a_member: "Add a member"
@ -1885,7 +1893,7 @@ en:
no_groups_info: "Supporting documents are necessarily applied to groups.<br>If you do not have any group yet, you can create one from the \"Users/Groups\" page (button on the right)."
create_groups: "Create groups"
supporting_documents_type_title: "Supporting documents requests"
add_type: "New supporting documents request"
add_type: "Add new document"
group_name: "Group"
name: "Supporting documents"
no_types: "You do not have any supporting documents requests.<br>Make sure you have created at least one group in order to add a request."

View File

@ -312,6 +312,9 @@ en:
event_description: "Event description"
downloadable_documents: "Downloadable documents"
information_and_booking: "Information and booking"
event_type:
family: "Family event"
nominative: "Nominative event"
dates: "Dates"
beginning: "Beginning:"
ending: "Ending:"
@ -477,25 +480,28 @@ en:
member_select:
select_a_member: "Select a member"
start_typing: "Start typing..."
children_list:
heading: "My children"
children_dashboard:
heading: "Children"
member_heading: "My Children"
add_child: "Add a child"
child_modal:
edit_child: "Edit child"
new_child: "New child"
child_form:
child_form_info: "Note that you can only add your children under 18 years old. Supporting documents are requested by your administrator, they will be useful to validate your child's account and authorize the reservation of events."
child_form_info: "Please note that you can only add a child under the age of 18. Supporting documents are requested by your administrator, they will be useful to validate your child's account and authorize the reservation of events."
first_name: "First name"
last_name: "Last name"
birthday: "Birthday"
email: "Email"
phone: "Phone"
save: "Save"
supporting_documents: "Supporting documents"
to_complete: "To complete"
refuse_documents: "Refusing the documents"
refuse_documents_info: "You can refuse a selection of documents by clicking on the following button."
refuse_documents: "Refuse documents"
child_item:
first_name: "First name of the child"
last_name: "Last name of the child"
first_name: "Child first name"
last_name: "Child last name"
birthday: "Birthday"
deleted: "The child has been deleted."
unable_to_delete: "Unable to delete the child."

View File

@ -477,7 +477,7 @@ fr:
member_select:
select_a_member: "Sélectionnez un membre"
start_typing: "Commencez à écrire..."
children_list:
children_dashboard:
heading: "Mes enfants"
add_child: "Ajouter un enfant"
child_modal:

View File

@ -165,7 +165,7 @@ en:
member_select:
select_a_member: "Select a member"
start_typing: "Start typing..."
member_not_validated: "Warning:<br> The member was not validated."
member_not_validated: "This member has not yet been validated."
#payment modal
abstract_payment_modal:
online_payment: "Online payment"
@ -368,9 +368,9 @@ en:
slot_tags: "Slot tags"
user_tags: "User tags"
no_tags: "No tags"
user_validation_required_alert: "Warning!<br>Your administrator must validate your account. Then, you'll then be able to access all the booking features."
child_validation_required_alert: "Warning!<br>Your administrator must validate your child account. Then, you'll then be able to book the event."
child_birthday_must_be_under_18_years_ago_alert: "Warning!<br>Your child must be under 18 years ago. Then, you'll then be able to book the event."
user_validation_required_alert: "Your administrator must validate your account. Then, you'll then be able to access all the booking features."
child_validation_required_alert: "Your administrator must validate your child account. Then, you'll then be able to book the event."
child_birthday_must_be_under_18_years_ago_alert: "Your child must be under 18. Then, you'll then be able to book the event."
# feature-tour modal
tour:
previous: "Previous"
@ -443,6 +443,7 @@ en:
select_all: "Select all"
unselect_all: "Unselect all"
form_file_upload:
placeholder: "Add a file"
browse: "Browse"
edit: "Edit"
form_image_upload: