mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-02-20 14:54:15 +01:00
Convert [spaces] to React
This commit is contained in:
parent
4a8fa65e5f
commit
f8798e28b5
20
app/frontend/src/javascript/api/space.ts
Normal file
20
app/frontend/src/javascript/api/space.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import apiClient from './clients/api-client';
|
||||
import { AxiosResponse } from 'axios';
|
||||
|
||||
export default class MachineAPI {
|
||||
static async index (filters?: boolean): Promise<Array<any>> {
|
||||
const res: AxiosResponse<Array<any>> = await apiClient.get(`/api/spaces${this.filtersToQuery(filters)}`);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
static async get (id: number): Promise<any> {
|
||||
const res: AxiosResponse<any> = await apiClient.get(`/api/spaces/${id}`);
|
||||
return res?.data;
|
||||
}
|
||||
|
||||
private static filtersToQuery (filters?: boolean): string {
|
||||
if (!filters) return '';
|
||||
|
||||
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { react2angular } from 'react2angular';
|
||||
import { Loader } from '../base/loader';
|
||||
import { FabAlert } from '../base/fab-alert';
|
||||
import { HtmlTranslate } from '../base/html-translate';
|
||||
import SpaceAPI from '../../api/space';
|
||||
import GroupAPI from '../../api/group';
|
||||
import { Group } from '../../models/group';
|
||||
import { IApplication } from '../../models/application';
|
||||
import { EditablePrice } from './editable-price';
|
||||
import PriceAPI from '../../api/price';
|
||||
import { Price } from '../../models/price';
|
||||
import { useImmer } from 'use-immer';
|
||||
import FormatLib from '../../lib/format';
|
||||
|
||||
declare const Application: IApplication;
|
||||
|
||||
interface SpacesPricingProps {
|
||||
onError: (message: string) => void,
|
||||
onSuccess: (message: string) => void,
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to set and edit the prices of spaces-hours, per group
|
||||
*/
|
||||
const SpacesPricing: React.FC<SpacesPricingProps> = ({ onError, onSuccess }) => {
|
||||
const { t } = useTranslation('admin');
|
||||
|
||||
const [spaces, setSpaces] = useState<Array<any>>(null);
|
||||
const [groups, setGroups] = useState<Array<Group>>(null);
|
||||
const [prices, updatePrices] = useImmer<Array<Price>>(null);
|
||||
|
||||
// retrieve the initial data
|
||||
useEffect(() => {
|
||||
SpaceAPI.index(false)
|
||||
.then(data => setSpaces(data))
|
||||
.catch(error => onError(error));
|
||||
GroupAPI.index({ disabled: false, admins: false })
|
||||
.then(data => setGroups(data))
|
||||
.catch(error => onError(error));
|
||||
PriceAPI.index({ priceable_type: 'Space', plan_id: null })
|
||||
.then(data => updatePrices(data))
|
||||
.catch(error => onError(error));
|
||||
}, []);
|
||||
|
||||
// duration of the example slot
|
||||
const EXEMPLE_DURATION = 20;
|
||||
|
||||
/**
|
||||
* Return the exemple price, formatted
|
||||
*/
|
||||
const examplePrice = (type: 'hourly_rate' | 'final_price'): string => {
|
||||
const hourlyRate = 10;
|
||||
|
||||
if (type === 'hourly_rate') {
|
||||
return FormatLib.price(hourlyRate);
|
||||
}
|
||||
|
||||
const price = (hourlyRate / 60) * EXEMPLE_DURATION;
|
||||
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);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the given price in the internal state
|
||||
*/
|
||||
const updatePrice = (price: Price): void => {
|
||||
updatePrices(draft => {
|
||||
const index = draft.findIndex(p => p.id === price.id);
|
||||
draft[index] = price;
|
||||
return draft;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the user has confirmed to update a price
|
||||
*/
|
||||
const handleUpdatePrice = (price: Price): void => {
|
||||
PriceAPI.update(price)
|
||||
.then(() => {
|
||||
onSuccess(t('app.admin.machines_pricing.price_updated'));
|
||||
updatePrice(price);
|
||||
})
|
||||
.catch(error => onError(error));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="machines-pricing">
|
||||
<FabAlert level="warning">
|
||||
<p><HtmlTranslate trKey="app.admin.pricing.these_prices_match_space_hours_rates_html"/></p>
|
||||
<p><HtmlTranslate trKey="app.admin.pricing.prices_calculated_on_hourly_rate_html" options={{ DURATION: `${EXEMPLE_DURATION}`, RATE: examplePrice('hourly_rate'), PRICE: examplePrice('final_price') }} /></p>
|
||||
<p>{t('app.admin.pricing.you_can_override')}</p>
|
||||
</FabAlert>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('app.admin.pricing.spaces')}</th>
|
||||
{groups?.map(group => <th key={group.id} className="group-name">{group.name}</th>)}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{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} />}
|
||||
</td>)}
|
||||
</tr>)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const SpacesPricingWrapper: React.FC<SpacesPricingProps> = ({ onError, onSuccess }) => {
|
||||
return (
|
||||
<Loader>
|
||||
<SpacesPricing onError={onError} onSuccess={onSuccess} />
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
||||
Application.Components.component('spacesPricing', react2angular(SpacesPricingWrapper, ['onError', 'onSuccess']));
|
@ -1,8 +1,11 @@
|
||||
<div class="alert alert-warning m-t">
|
||||
<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>
|
||||
@ -26,4 +29,4 @@
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</table>-->
|
||||
|
Loading…
x
Reference in New Issue
Block a user