1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-02-17 11:54:22 +01:00
This commit is contained in:
vincent 2021-12-21 14:37:38 +01:00
parent f8798e28b5
commit d6a4675209
6 changed files with 249 additions and 38 deletions

View File

@ -0,0 +1,75 @@
import React, { ReactNode, useState } from 'react';
import { Price } from '../../models/price';
import { useTranslation } from 'react-i18next';
import { FabPopover } from '../base/fab-popover';
import { CreateTimeslot } from './create-timeslot';
import PriceAPI from '../../api/price';
import FormatLib from '../../lib/format';
interface ConfigureTimeslotButtonProps {
prices: Array<Price>,
onError: (message: string) => void,
onSuccess: (message: string) => void,
groupId: number,
priceableId: number,
priceableType: string,
}
/**
* This component is a button that shows the list of timeslots.
* It also triggers modal dialogs to configure (add/delete/edit/remove) timeslots.
*/
export const ConfigureTimeslotButton: React.FC<ConfigureTimeslotButtonProps> = ({ prices, onError, onSuccess, groupId, priceableId, priceableType }) => {
const { t } = useTranslation('admin');
const [timeslots, setTimeslots] = useState<Array<Price>>(prices);
const [showList, setShowList] = useState<boolean>(false);
/**
* Open/closes the popover listing the existing packs
*/
const toggleShowList = (): void => {
setShowList(!showList);
};
/**
* Callback triggered when the timeslot was successfully created/deleted/updated.
* We refresh the list of timeslots.
*/
const handleSuccess = (message: string) => {
onSuccess(message);
PriceAPI.index({ group_id: groupId, priceable_id: priceableId, priceable_type: priceableType })
.then(data => setTimeslots(data))
.catch(error => onError(error));
};
/**
* Render the button used to trigger the "new pack" modal
*/
const renderAddButton = (): ReactNode => {
return <CreateTimeslot onSuccess={handleSuccess}
onError={onError}
groupId={groupId}
priceableId={priceableId}
priceableType={priceableType} />;
};
return (
<div className="configure-packs-button">
<button className="packs-button" onClick={toggleShowList}>
<i className="fas fa-box" />
</button>
{showList && <FabPopover title={t('app.admin.configure_timeslots_button.timeslots')} headerButton={renderAddButton()} className="fab-popover__right">
<ul>
{timeslots?.map(timeslot =>
<li key={timeslot.id}>
{timeslot.duration} {t('app.admin.calendar.minutes')} - {FormatLib.price(timeslot.amount)}
<span className="pack-actions">
</span>
</li>)}
</ul>
{timeslots?.length === 0 && <span>{t('app.admin.configure_timeslots_button.no_timeslots')}</span>}
</FabPopover>}
</div>
);
};

View File

@ -0,0 +1,70 @@
import React, { useState } from 'react';
import { FabModal } from '../base/fab-modal';
import { TimeslotForm } from './timeslot-form';
import { Price } from '../../models/price';
import PrepaidPackAPI from '../../api/prepaid-pack';
import { useTranslation } from 'react-i18next';
import { FabAlert } from '../base/fab-alert';
interface CreateTimeslotProps {
onSuccess: (message: string) => void,
onError: (message: string) => void,
groupId: number,
priceableId: number,
priceableType: string,
}
/**
* This component shows a button.
* When clicked, we show a modal dialog handing the process of creating a new time slot
*/
export const CreateTimeslot: React.FC<CreateTimeslotProps> = ({ onSuccess, onError, groupId, priceableId, priceableType }) => {
const { t } = useTranslation('admin');
const [isOpen, setIsOpen] = useState<boolean>(false);
/**
* Open/closes the "new pack" modal dialog
*/
const toggleModal = (): void => {
setIsOpen(!isOpen);
};
/**
* Callback triggered when the user has validated the creation of the new time slot
*/
const handleSubmit = (timeslot: Price): void => {
// set the already-known attributes of the new pack
const newTimeslot = Object.assign<Price, Price>({} as Price, timeslot);
newTimeslot.group_id = groupId;
newTimeslot.priceable_id = priceableId;
newTimeslot.priceable_type = priceableType;
// create it on the API
console.log('newTimeslot :', newTimeslot);
// PrepaidPackAPI.create(newPack)
// .then(() => {
// onSuccess(t('app.admin.create_timeslot.timeslot_successfully_created'));
// toggleModal();
// })
// .catch(error => onError(error));
};
return (
<div className="create-pack">
<button className="add-pack-button" onClick={toggleModal}><i className="fas fa-plus"/></button>
<FabModal isOpen={isOpen}
toggleModal={toggleModal}
title={t('app.admin.create_timeslot.new_timeslot')}
className="new-pack-modal"
closeButton
confirmButton={t('app.admin.create_timeslot.create_timeslot')}
onConfirmSendFormId="new-pack">
<FabAlert level="info">
{t('app.admin.create_timeslot.new_timeslot_info', { TYPE: priceableType })}
</FabAlert>
<TimeslotForm formId="new-pack" onSubmit={handleSubmit} />
</FabModal>
</div>
);
};

View File

@ -9,6 +9,7 @@ import GroupAPI from '../../api/group';
import { Group } from '../../models/group';
import { IApplication } from '../../models/application';
import { EditablePrice } from './editable-price';
import { ConfigureTimeslotButton } from './configure-timeslot-button';
import PriceAPI from '../../api/price';
import { Price } from '../../models/price';
import { useImmer } from 'use-immer';
@ -61,11 +62,8 @@ const SpacesPricing: React.FC<SpacesPricingProps> = ({ onError, onSuccess }) =>
return FormatLib.price(price);
};
/**
* Find the price matching the given criterion
*/
const findPriceBy = (spaceId, groupId): Price => {
return prices.find(price => price.priceable_id === spaceId && price.group_id === groupId);
const findPricesBy = (spaceId, groupId): Array<Price> => {
return prices.filter(price => price.priceable_id === spaceId && price.group_id === groupId);
};
/**
@ -109,7 +107,14 @@ const SpacesPricing: React.FC<SpacesPricingProps> = ({ onError, onSuccess }) =>
{spaces?.map(space => <tr key={space.id}>
<td>{space.name}</td>
{groups?.map(group => <td key={group.id}>
{prices && <EditablePrice price={findPriceBy(space.id, group.id)} onSave={handleUpdatePrice} />}
{prices && <EditablePrice price={findPricesBy(space.id, group.id)[0]} onSave={handleUpdatePrice} />}
<ConfigureTimeslotButton
prices={findPricesBy(space.id, group.id)}
onError={onError}
onSuccess={onSuccess}
groupId={group.id}
priceableId={space.id}
priceableType='Space' />
</td>)}
</tr>)}
</tbody>

View File

@ -0,0 +1,74 @@
import React, { BaseSyntheticEvent } from 'react';
import { Price } from '../../models/price';
import { useTranslation } from 'react-i18next';
import { useImmer } from 'use-immer';
import { FabInput } from '../base/fab-input';
import { IFablab } from '../../models/fablab';
declare let Fablab: IFablab;
interface PackFormProps {
formId: string,
onSubmit: (pack: Price) => void,
price?: Price,
}
/**
* A form component to create/edit a time slot.
* The form validation must be created elsewhere, using the attribute form={formId}.
*/
export const TimeslotForm: React.FC<PackFormProps> = ({ formId, onSubmit, price }) => {
const [timeslotData, updateTimeslotData] = useImmer<Price>(price || {} as Price);
const { t } = useTranslation('admin');
/**
* Callback triggered when the user sends the form.
*/
const handleSubmit = (event: BaseSyntheticEvent): void => {
event.preventDefault();
onSubmit(timeslotData);
};
/**
* Callback triggered when the user inputs an amount for the current time slot.
*/
const handleUpdateAmount = (amount: string) => {
updateTimeslotData(draft => {
draft.amount = parseFloat(amount);
});
};
/**
* Callback triggered when the user inputs a number of minutes for the current time slot.
*/
const handleUpdateHours = (minutes: string) => {
updateTimeslotData(draft => {
draft.duration = parseInt(minutes, 10);
});
};
return (
<form id={formId} onSubmit={handleSubmit} className="pack-form">
<label htmlFor="duration">{t('app.admin.calendar.minutes')} *</label>
<FabInput id="duration"
type="number"
defaultValue={timeslotData?.duration || ''}
onChange={handleUpdateHours}
step={1}
min={1}
icon={<i className="fas fa-clock" />}
required />
<label htmlFor="amount">{t('app.admin.pack_form.amount')} *</label>
<FabInput id="amount"
type="number"
step={0.01}
min={0}
defaultValue={timeslotData?.amount || ''}
onChange={handleUpdateAmount}
icon={<i className="fas fa-money-bill" />}
addOn={Fablab.intl_currency}
required />
</form>
);
};

View File

@ -1,32 +1 @@
<spaces-pricing on-success="onSuccess" on-error="onError"></spaces-pricing>
<!--<div class="alert alert-warning m-t">
<p ng-bind-html="'app.admin.pricing.these_prices_match_space_hours_rates_html' | translate"></p>
<p ng-bind-html="'app.admin.pricing.prices_calculated_on_hourly_rate_html' | translate:{ DURATION:slotDuration, RATE: examplePrice('hourly_rate'), PRICE: examplePrice('final_price') }"></p>
<p translate>{{ 'app.admin.pricing.you_can_override' }}</p>
</div>
<table class="table">
<thead>
<tr>
<th style="width:20%" translate>{{ 'app.admin.pricing.spaces' }}</th>
<th style="width:20%" ng-repeat="group in enabledGroups">
<span class="text-u-c text-sm">{{group.name}}</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="space in enabledSpaces">
<td>
{{ space.name }}
</td>
<td ng-repeat="group in enabledGroups">
<span editable-number="findPriceBy(spacesPrices, space.id, group.id).amount"
e-step="any"
onbeforesave="updatePrice($data, findPriceBy(spacesPrices, space.id, group.id))">
{{ findPriceBy(spacesPrices, space.id, group.id).amount | currency}}
</span>
</td>
</tr>
</tbody>
</table>-->
<spaces-pricing on-success="onSuccess" on-error="onError">

View File

@ -378,6 +378,9 @@ en:
packs: "Prepaid packs"
no_packs: "No packs for now"
pack_DURATION: "{DURATION} hours"
configure_timeslots_button:
timeslots: "Time slots"
no_timeslots: "No time slot for now"
pack_form:
hours: "Hours"
amount: "Price"
@ -404,6 +407,21 @@ en:
edit_pack: "Edit the pack"
confirm_changes: "Confirm changes"
pack_successfully_updated: "The prepaid pack was successfully updated."
create_timeslot:
new_timeslot: "New time slot"
new_timeslot_info: "..."
create_timeslot: "Create this time slot"
timeslot_successfully_created: "The new time slot was successfully created."
delete_timeslot:
timeslot_deleted: "The time slot was successfully deleted."
unable_to_delete: "Unable to delete the time slot: "
delete_timeslot: "Delete the time slot"
confirm_delete: "Delete"
delete_confirmation: "Are you sure you want to delete this time slot? This won't be possible if it was already bought by users."
edit_timeslot:
edit_timeslot: "Edit the time slot"
confirm_changes: "Confirm changes"
timeslot_successfully_updated: "The time slot was successfully updated."
#ajouter un code promotionnel
coupons_new:
add_a_coupon: "Add a coupon"