1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-03-15 12:29:16 +01:00

factorize front-end API params helper

This commit is contained in:
Sylvain 2022-06-28 14:59:51 +02:00
parent 433f2f876d
commit ea1883e406
33 changed files with 197 additions and 103 deletions

View File

@ -8,11 +8,12 @@ class API::ReservationsController < API::ApiController
respond_to :json
def index
if params[:reservable_id] && params[:reservable_type] && params[:user_id]
if params[:user_id]
params[:user_id] = current_user.id unless current_user.admin? || current_user.manager?
where_clause = params.permit(:reservable_id, :reservable_type).to_h
where_clause[:statistic_profile_id] = StatisticProfile.find_by!(user_id: params[:user_id])
where_clause = { statistic_profile_id: StatisticProfile.find_by!(user_id: params[:user_id]) }
where_clause[:reservable_type] = params[:reservable_type] if params[:reservable_type]
where_clause[:reservable_id] = params[:reservable_id] if params[:reservable_id]
@reservations = Reservation.where(where_clause)
elsif params[:reservable_id] && params[:reservable_type] && (current_user.admin? || current_user.manager?)

View File

@ -1,16 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { Group, GroupIndexFilter } from '../models/group';
import ApiLib from '../lib/api';
export default class GroupAPI {
static async index (filters?: GroupIndexFilter): Promise<Array<Group>> {
const res: AxiosResponse<Array<Group>> = await apiClient.get(`/api/groups${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<Group>> = await apiClient.get(`/api/groups${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
private static filtersToQuery (filters?: GroupIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -1,10 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { Machine, MachineIndexFilter } from '../models/machine';
import ApiLib from '../lib/api';
export default class MachineAPI {
static async index (filters?: MachineIndexFilter): Promise<Array<Machine>> {
const res: AxiosResponse<Array<Machine>> = await apiClient.get(`/api/machines${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<Machine>> = await apiClient.get(`/api/machines${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
@ -12,10 +13,4 @@ export default class MachineAPI {
const res: AxiosResponse<Machine> = await apiClient.get(`/api/machines/${id}`);
return res?.data;
}
private static filtersToQuery (filters?: MachineIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -1,10 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { PackIndexFilter, PrepaidPack } from '../models/prepaid-pack';
import ApiLib from '../lib/api';
export default class PrepaidPackAPI {
static async index (filters?: PackIndexFilter): Promise<Array<PrepaidPack>> {
const res: AxiosResponse<Array<PrepaidPack>> = await apiClient.get(`/api/prepaid_packs${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<PrepaidPack>> = await apiClient.get(`/api/prepaid_packs${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
@ -27,10 +28,4 @@ export default class PrepaidPackAPI {
const res: AxiosResponse<void> = await apiClient.delete(`/api/prepaid_packs/${packId}`);
return res?.data;
}
private static filtersToQuery (filters?: PackIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -2,6 +2,7 @@ import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { ShoppingCart } from '../models/payment';
import { ComputePriceResult, Price, PriceIndexFilter } from '../models/price';
import ApiLib from '../lib/api';
export default class PriceAPI {
static async compute (cart: ShoppingCart): Promise<ComputePriceResult> {
@ -10,7 +11,7 @@ export default class PriceAPI {
}
static async index (filters?: PriceIndexFilter): Promise<Array<Price>> {
const res: AxiosResponse = await apiClient.get(`/api/prices${this.filtersToQuery(filters)}`);
const res: AxiosResponse = await apiClient.get(`/api/prices${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
@ -28,10 +29,4 @@ export default class PriceAPI {
const res: AxiosResponse<void> = await apiClient.delete(`/api/prices/${priceId}`);
return res?.data;
}
private static filtersToQuery (filters?: PriceIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -1,10 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { ProofOfIdentityFile, ProofOfIdentityFileIndexFilter } from '../models/proof-of-identity-file';
import ApiLib from '../lib/api';
export default class ProofOfIdentityFileAPI {
static async index (filters?: ProofOfIdentityFileIndexFilter): Promise<Array<ProofOfIdentityFile>> {
const res: AxiosResponse<Array<ProofOfIdentityFile>> = await apiClient.get(`/api/proof_of_identity_files${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<ProofOfIdentityFile>> = await apiClient.get(`/api/proof_of_identity_files${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
@ -27,10 +28,4 @@ export default class ProofOfIdentityFileAPI {
const res: AxiosResponse<void> = await apiClient.delete(`/api/proof_of_identity_files/${proofOfIdentityFileId}`);
return res?.data;
}
private static filtersToQuery (filters?: ProofOfIdentityFileIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -1,10 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { ProofOfIdentityRefusal, ProofOfIdentityRefusalIndexFilter } from '../models/proof-of-identity-refusal';
import ApiLib from '../lib/api';
export default class ProofOfIdentityRefusalAPI {
static async index (filters?: ProofOfIdentityRefusalIndexFilter): Promise<Array<ProofOfIdentityRefusal>> {
const res: AxiosResponse<Array<ProofOfIdentityRefusal>> = await apiClient.get(`/api/proof_of_identity_refusals${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<ProofOfIdentityRefusal>> = await apiClient.get(`/api/proof_of_identity_refusals${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
@ -12,10 +13,4 @@ export default class ProofOfIdentityRefusalAPI {
const res: AxiosResponse<ProofOfIdentityRefusal> = await apiClient.post('/api/proof_of_identity_refusals', { proof_of_identity_refusal: proofOfIdentityRefusal });
return res?.data;
}
private static filtersToQuery (filters?: ProofOfIdentityRefusalIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -1,10 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { ProofOfIdentityType, ProofOfIdentityTypeIndexfilter } from '../models/proof-of-identity-type';
import ApiLib from '../lib/api';
export default class ProofOfIdentityTypeAPI {
static async index (filters?: ProofOfIdentityTypeIndexfilter): Promise<Array<ProofOfIdentityType>> {
const res: AxiosResponse<Array<ProofOfIdentityType>> = await apiClient.get(`/api/proof_of_identity_types${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<ProofOfIdentityType>> = await apiClient.get(`/api/proof_of_identity_types${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
@ -27,10 +28,4 @@ export default class ProofOfIdentityTypeAPI {
const res: AxiosResponse<void> = await apiClient.delete(`/api/proof_of_identity_types/${proofOfIdentityTypeId}`);
return res?.data;
}
private static filtersToQuery (filters?: ProofOfIdentityTypeIndexfilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -0,0 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { Reservation, ReservationIndexFilter } from '../models/reservation';
import ApiLib from '../lib/api';
export default class ReservationAPI {
static async index (filters: ReservationIndexFilter): Promise<Array<Reservation>> {
const res: AxiosResponse<Array<Reservation>> = await apiClient.get(`/api/reservations${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
}

View File

@ -1,16 +1,11 @@
import apiClient from './clients/api-client';
import { AxiosResponse } from 'axios';
import { Training, TrainingIndexFilter } from '../models/training';
import ApiLib from '../lib/api';
export default class TrainingAPI {
static async index (filters?: TrainingIndexFilter): Promise<Array<Training>> {
const res: AxiosResponse<Array<Training>> = await apiClient.get(`/api/trainings${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<Training>> = await apiClient.get(`/api/trainings${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
private static filtersToQuery (filters?: TrainingIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -1,16 +1,11 @@
import apiClient from './clients/api-client';
import { UserPack, UserPackIndexFilter } from '../models/user-pack';
import { AxiosResponse } from 'axios';
import ApiLib from '../lib/api';
export default class UserPackAPI {
static async index (filters: UserPackIndexFilter): Promise<Array<UserPack>> {
const res: AxiosResponse<Array<UserPack>> = await apiClient.get(`/api/user_packs${this.filtersToQuery(filters)}`);
const res: AxiosResponse<Array<UserPack>> = await apiClient.get(`/api/user_packs${ApiLib.filtersToQuery(filters)}`);
return res?.data;
}
private static filtersToQuery (filters?: UserPackIndexFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -0,0 +1,28 @@
import React, { useEffect, useState } from 'react';
import { FabPanel } from '../../base/fab-panel';
import { Reservation } from '../../../models/reservation';
import ReservationAPI from '../../../api/reservation';
interface MachineReservationsProps {
userId: number,
onError: (message: string) => void,
}
/**
* List all machine reservations for the given user
*/
export const MachineReservations: React.FC<MachineReservationsProps> = ({ userId, onError }) => {
const [reservations, setReservations] = useState<Array<Reservation>>([]);
useEffect(() => {
ReservationAPI.index({ user_id: userId, reservable_type: 'Machine' })
.then(res => setReservations(res))
.catch(error => onError(error));
}, []);
return (
<FabPanel className="machine-reservations">
{reservations.map(r => JSON.stringify(r))}
</FabPanel>
);
};

View File

@ -0,0 +1,26 @@
import React from 'react';
import { IApplication } from '../../../models/application';
import { react2angular } from 'react2angular';
import { MachineReservations } from './machine-reservations';
import { SpaceReservations } from './space-reservations';
declare const Application: IApplication;
interface ReservationsDashboardProps {
onError: (message: string) => void,
userId: number
}
/**
* User dashboard showing everything about his spaces/machine reservations and also remaining credits
*/
const ReservationsDashboard: React.FC<ReservationsDashboardProps> = ({ onError, userId }) => {
return (
<div className="reservations-dashboard">
<MachineReservations userId={userId} onError={onError} />
<SpaceReservations userId={userId} onError={onError} />
</div>
);
};
Application.Components.component('reservationsDashboard', react2angular(ReservationsDashboard, ['onError', 'userId']));

View File

@ -0,0 +1,28 @@
import React, { useEffect, useState } from 'react';
import { FabPanel } from '../../base/fab-panel';
import { Reservation } from '../../../models/reservation';
import ReservationAPI from '../../../api/reservation';
interface SpaceReservationsProps {
userId: number,
onError: (message: string) => void,
}
/**
* List all space reservations for the given user
*/
export const SpaceReservations: React.FC<SpaceReservationsProps> = ({ userId, onError }) => {
const [reservations, setReservations] = useState<Array<Reservation>>([]);
useEffect(() => {
ReservationAPI.index({ user_id: userId, reservable_type: 'Space' })
.then(res => setReservations(res))
.catch(error => onError(error));
}, []);
return (
<FabPanel className="space-reservations">
{reservations.map(r => JSON.stringify(r))}
</FabPanel>
);
};

View File

@ -0,0 +1,9 @@
import { ApiFilter } from '../models/api';
export default class ApiLib {
static filtersToQuery (filters?: ApiFilter): string {
if (!filters) return '';
return '?' + Object.entries(filters).map(f => `${f[0]}=${f[1]}`).join('&');
}
}

View File

@ -0,0 +1,3 @@
// ApiFilter should be extended by an interface listing all the filters allowed for a given API
// eslint-disable-next-line @typescript-eslint/ban-types
export type ApiFilter = {};

View File

@ -1,4 +1,6 @@
export interface GroupIndexFilter {
import { ApiFilter } from './api';
export interface GroupIndexFilter extends ApiFilter {
disabled?: boolean,
admins?: boolean,
}

View File

@ -1,6 +1,7 @@
import { Reservation } from './reservation';
import { ApiFilter } from './api';
export interface MachineIndexFilter {
export interface MachineIndexFilter extends ApiFilter {
disabled: boolean,
}

View File

@ -1,5 +1,6 @@
import { ApiFilter } from './api';
export interface PackIndexFilter {
export interface PackIndexFilter extends ApiFilter {
group_id?: number,
priceable_id?: number,
priceable_type?: string,

View File

@ -1,6 +1,7 @@
import { TDateISO } from '../typings/date-iso';
import { ApiFilter } from './api';
export interface PriceIndexFilter {
export interface PriceIndexFilter extends ApiFilter {
priceable_type?: string,
priceable_id?: number,
group_id?: number,

View File

@ -1,5 +1,6 @@
import { ApiFilter } from './api';
export interface ProofOfIdentityFileIndexFilter {
export interface ProofOfIdentityFileIndexFilter extends ApiFilter {
user_id: number,
}

View File

@ -1,5 +1,6 @@
import { ApiFilter } from './api';
export interface ProofOfIdentityRefusalIndexFilter {
export interface ProofOfIdentityRefusalIndexFilter extends ApiFilter {
user_id: number,
}

View File

@ -1,4 +1,6 @@
export interface ProofOfIdentityTypeIndexfilter {
import { ApiFilter } from './api';
export interface ProofOfIdentityTypeIndexfilter extends ApiFilter {
group_id?: number,
}

View File

@ -1,20 +1,37 @@
import { TDateISO } from '../typings/date-iso';
import { ApiFilter } from './api';
export type ReservableType = 'Training' | 'Event' | 'Space' | 'Machine';
export interface ReservationSlot {
id?: number,
start_at: TDateISO,
end_at: TDateISO,
availability_id: number,
offered: boolean
canceled_at?: TDateISO,
availability_id?: number,
offered?: boolean,
is_reserved?: boolean
}
export interface Reservation {
id?: number,
user_id?: number,
user_full_name?: string,
message?: string,
reservable_id: number,
reservable_type: string,
reservable_type: ReservableType,
slots_attributes: Array<ReservationSlot>,
nb_reserve_places?: number,
tickets_attributes?: {
event_price_category_id: number,
booked: boolean,
},
total_booked_seats?: number,
created_at?: TDateISO,
}
export interface ReservationIndexFilter extends ApiFilter {
reservable_id?: number,
reservable_type?: ReservableType | Array<ReservableType>,
user_id?: number
}

View File

@ -1,3 +1,5 @@
import { ApiFilter } from './api';
export interface Training {
id?: number,
name: string,
@ -11,7 +13,7 @@ export interface Training {
training_image?: string,
}
export interface TrainingIndexFilter {
export interface TrainingIndexFilter extends ApiFilter {
disabled?: boolean,
public_page?: boolean,
requested_attributes?: ['availabillities'],

View File

@ -1,6 +1,7 @@
import { TDateISO } from '../typings/date-iso';
import { ApiFilter } from './api';
export interface UserPackIndexFilter {
export interface UserPackIndexFilter extends ApiFilter {
user_id?: number,
priceable_type: string,
priceable_id: number

View File

@ -191,6 +191,15 @@ angular.module('application.router', ['ui.router'])
}
}
})
.state('app.logged.dashboard.reservations', {
url: '/reservations',
views: {
'main@': {
templateUrl: '/dashboard/reservations.html',
controller: 'DashboardController'
}
}
})
.state('app.logged.dashboard.events', {
url: '/events',
views: {

View File

@ -15,6 +15,7 @@
<li ng-if="!isAuthorized(['admin', 'manager']) && hasProofOfIdentityTypes" ui-sref-active="active"><a class="text-black" ui-sref="app.logged.dashboard.proof_of_identity_files" translate>{{ 'app.public.common.my_supporting_documents_files' }}</a></li>
<li ui-sref-active="active"><a class="text-black" ui-sref="app.logged.dashboard.projects" translate>{{ 'app.public.common.my_projects' }}</a></li>
<li ui-sref-active="active"><a class="text-black" ui-sref="app.logged.dashboard.trainings" translate>{{ 'app.public.common.my_trainings' }}</a></li>
<li ui-sref-active="active"><a class="text-black" ui-sref="app.logged.dashboard.reservations" translate>{{ 'app.public.common.my_reservations' }}</a></li>
<li ui-sref-active="active"><a class="text-black" ui-sref="app.logged.dashboard.events" translate>{{ 'app.public.common.my_events' }}</a></li>
<li ui-sref-active="active" ng-show="$root.modules.invoicing"><a class="text-black" ui-sref="app.logged.dashboard.invoices" translate>{{ 'app.public.common.my_invoices' }}</a></li>
<li ui-sref-active="active" ng-show="$root.modules.invoicing"><a class="text-black" ui-sref="app.logged.dashboard.payment_schedules" translate>{{ 'app.public.common.my_payment_schedules' }}</a></li>

View File

@ -0,0 +1,11 @@
<div>
<section class="heading">
<div class="row no-gutter">
<ng-include src="'/dashboard/nav.html'"></ng-include>
</div>
</section>
<reservations-dashboard user-id="user.id" on-error="onError" />
</div>

View File

@ -9,6 +9,7 @@ json.slots_attributes reservation.slots do |s|
json.start_at s.start_at.iso8601
json.end_at s.end_at.iso8601
json.canceled_at s.canceled_at&.iso8601
json.is_reserved true
end
json.nb_reserve_places reservation.nb_reserve_places
json.tickets reservation.tickets do |t|

View File

@ -1,4 +1,5 @@
# frozen_string_literal: true
json.array!(@reservations) do |r|
json.partial! 'api/reservations/reservation', reservation: r
end

View File

@ -1,7 +1,6 @@
# frozen_string_literal: true
json.id @reservation.id
json.user_id @reservation.statistic_profile.user_id
json.partial! 'api/reservations/reservation', reservation: @reservation
json.user do
json.id @reservation.user.id
if @reservation.user.subscribed_plan
@ -17,26 +16,7 @@ json.user do
json.hours_used mc.users_credits.find_by(user_id: @reservation.statistic_profile.user_id).hours_used
end
end
json.message @reservation.message
json.slots_attributes @reservation.slots do |s|
json.id s.id
json.start_at s.start_at.iso8601
json.end_at s.end_at.iso8601
json.is_reserved true
end
json.reservable do
json.id @reservation.reservable.id
json.name @reservation.reservable.name
end
json.nb_reserve_places @reservation.nb_reserve_places
json.tickets @reservation.tickets do |t|
json.extract! t, :booked, :created_at
json.event_price_category do
json.extract! t.event_price_category, :id, :price_category_id
json.price_category do
json.extract! t.event_price_category.price_category, :id, :name
end
end
end
json.total_booked_seats @reservation.total_booked_seats
json.created_at @reservation.created_at.iso8601

View File

@ -18,6 +18,7 @@ en:
my_supporting_documents_files: "My supporting documents"
my_projects: "My Projects"
my_trainings: "My Trainings"
my_reservations: "My reservations"
my_events: "My Events"
my_invoices: "My Invoices"
my_payment_schedules: "My payment schedules"
@ -289,7 +290,7 @@ en:
add_an_event: "Add an event"
load_the_next_events: "Load the next events..."
full_price_: "Full price:"
to_date: "to" #eg. from 01/01 to 01/05
to_date: "to" #e.g. from 01/01 to 01/05
all_themes: "All themes"
#details and booking of an event
events_show:
@ -301,8 +302,8 @@ en:
ending: "Ending:"
opening_hours: "Opening hours:"
all_day: "All day"
from_time: "From" #eg. from 18:00 to 21:00
to_time: "to" #eg. from 18:00 to 21:00
from_time: "From" #e.g. from 18:00 to 21:00
to_time: "to" #e.g. from 18:00 to 21:00
full_price_: "Full price:"
tickets_still_availables: "Tickets still available:"
sold_out: "Sold out."