1
0
mirror of https://github.com/LaCasemate/fab-manager.git synced 2025-01-17 06:52:27 +01:00

integration of packs-summary

This commit is contained in:
Sylvain 2021-06-29 15:59:57 +02:00
parent baf41588d3
commit 10aaf0042c
13 changed files with 103 additions and 40 deletions

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
# API Controller for resources of type StatisticProfilePrepaidPack
class UserPacksController < API::ApiController
class API::UserPacksController < API::ApiController
before_action :authenticate_user!
def index

View File

@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react';
import _ from 'lodash';
import { Machine } from '../../models/machine';
import { User } from '../../models/user';
import { UserPack } from '../../models/user-pack';
@ -19,7 +20,7 @@ type PackableItem = Machine;
interface PacksSummaryProps {
item: PackableItem,
itemType: 'Machine',
customer: User,
customer?: User,
operator: User,
onError: (message: string) => void,
onSuccess: (message: string) => void,
@ -33,13 +34,18 @@ const PacksSummaryComponent: React.FC<PacksSummaryProps> = ({ item, itemType, cu
const [packsModal, setPacksModal] = useState<boolean>(false);
useEffect(() => {
UserPackAPI.index({ user_id: customer.id, priceable_type: itemType, priceable_id: item.id })
.then(data => setUserPacks(data))
.catch(error => onError(error));
SettingAPI.get(SettingName.RenewPackThreshold)
.then(data => setThreshold(parseFloat(data.value)))
.catch(error => onError(error));
}, [item, itemType, customer])
}, []);
useEffect(() => {
if (_.isEmpty(customer)) return;
UserPackAPI.index({ user_id: customer.id, priceable_type: itemType, priceable_id: item.id })
.then(data => setUserPacks(data))
.catch(error => onError(error));
}, [item, itemType, customer]);
/**
* Total of minutes used by the customer
@ -47,7 +53,7 @@ const PacksSummaryComponent: React.FC<PacksSummaryProps> = ({ item, itemType, cu
const totalUsed = (): number => {
if (!userPacks) return 0;
return userPacks.map(up => up.minutes_used).reduce((acc, curr) => acc + curr);
return userPacks.map(up => up.minutes_used).reduce((acc, curr) => acc + curr, 0);
}
/**
@ -56,25 +62,26 @@ const PacksSummaryComponent: React.FC<PacksSummaryProps> = ({ item, itemType, cu
const totalAvailable = (): number => {
if (!userPacks) return 0;
return userPacks.map(up => up.prepaid_pack.minutes).reduce((acc, curr) => acc + curr);
return userPacks.map(up => up.prepaid_pack.minutes).reduce((acc, curr) => acc + curr, 0);
}
/**
* Total prepaid hours remaining for the current customer
*/
const totalHours = (): number => {
return totalAvailable() - totalUsed() / 60;
return (totalAvailable() - totalUsed()) / 60;
}
/**
* Do we need to display the "renew pack" button?
* Do we need to display the "buy new pack" button?
*/
const shouldDisplayRenew = (): boolean => {
const shouldDisplayButton = (): boolean => {
console.log(threshold);
if (threshold < 1) {
return totalAvailable() - totalUsed() >= totalAvailable() * threshold;
return totalAvailable() - totalUsed() <= totalAvailable() * threshold;
}
return totalAvailable() - totalUsed() >= threshold;
return totalAvailable() - totalUsed() <= threshold * 60;
}
/**
@ -95,23 +102,29 @@ const PacksSummaryComponent: React.FC<PacksSummaryProps> = ({ item, itemType, cu
.catch(error => onError(error));
}
// prevent component rendering if no customer selected
if (_.isEmpty(customer)) return <div />;
return (
<div className="packs-summary">
<span className="remaining-hours">{t('app.logged.packs_summary.remaining_HOURS', { HOURS: totalHours(), ITEM: itemType })}</span>
{shouldDisplayRenew() && <div>
<FabButton className="renew-button" onClick={togglePacksModal} icon={<i className="fa fa-shopping-cart"/>}>
{t('app.logged.packs_summary.buy_a_new_pack')}
</FabButton>
<ProposePacksModal isOpen={packsModal}
toggleModal={togglePacksModal}
item={item}
itemType={itemType}
customer={customer}
operator={operator}
onError={onError}
onDecline={togglePacksModal}
onSuccess={handlePackBoughtSuccess} />
</div>}
<h3>{t('app.logged.packs_summary.prepaid_hours')}</h3>
<div className="content">
<span className="remaining-hours">{t('app.logged.packs_summary.remaining_HOURS', { HOURS: totalHours(), ITEM: itemType })}</span>
{shouldDisplayButton() && <div className="button-wrapper">
<FabButton className="buy-button" onClick={togglePacksModal} icon={<i className="fa fa-shopping-cart"/>}>
{t('app.logged.packs_summary.buy_a_new_pack')}
</FabButton>
<ProposePacksModal isOpen={packsModal}
toggleModal={togglePacksModal}
item={item}
itemType={itemType}
customer={customer}
operator={operator}
onError={onError}
onDecline={togglePacksModal}
onSuccess={handlePackBoughtSuccess} />
</div>}
</div>
</div>
);
}

View File

@ -98,8 +98,8 @@ class MachinesController {
/**
* Controller used in the public listing page, allowing everyone to see the list of machines
*/
Application.Controllers.controller('MachinesController', ['$scope', '$state', '_t', 'AuthService', 'Machine', '$uibModal', 'settingsPromise', 'Member', 'uiTourService', 'growl',
function ($scope, $state, _t, AuthService, Machine, $uibModal, settingsPromise, Member, uiTourService, growl) {
Application.Controllers.controller('MachinesController', ['$scope', '$state', '_t', 'AuthService', 'Machine', '$uibModal', 'settingsPromise', 'Member', 'uiTourService', 'machinesPromise', 'growl',
function ($scope, $state, _t, AuthService, Machine, $uibModal, settingsPromise, Member, uiTourService, machinesPromise, growl) {
/* PUBLIC SCOPE */
/**
@ -164,7 +164,7 @@ Application.Controllers.controller('MachinesController', ['$scope', '$state', '_
placement: 'bottom',
orphan: true
});
if ($scope.machines.length > 0) {
if (machinesPromise.length > 0) {
uitour.createStep({
selector: '.machines-list .show-button',
stepId: 'view',
@ -185,7 +185,7 @@ Application.Controllers.controller('MachinesController', ['$scope', '$state', '_
orphan: true
});
}
if ($scope.machines.length > 0) {
if (machinesPromise.length > 0) {
uitour.createStep({
selector: '.machines-list .reserve-button',
stepId: 'reserve',
@ -357,8 +357,8 @@ Application.Controllers.controller('ShowMachineController', ['$scope', '$state',
* This controller workflow is pretty similar to the trainings reservation controller.
*/
Application.Controllers.controller('ReserveMachineController', ['$scope', '$stateParams', '_t', 'moment', 'Auth', '$timeout', 'Member', 'Availability', 'plansPromise', 'groupsPromise', 'machinePromise', 'settingsPromise', 'uiCalendarConfig', 'CalendarConfig', 'Reservation',
function ($scope, $stateParams, _t, moment, Auth, $timeout, Member, Availability, plansPromise, groupsPromise, machinePromise, settingsPromise, uiCalendarConfig, CalendarConfig, Reservation) {
Application.Controllers.controller('ReserveMachineController', ['$scope', '$stateParams', '_t', 'moment', 'Auth', '$timeout', 'Member', 'Availability', 'plansPromise', 'groupsPromise', 'machinePromise', 'settingsPromise', 'uiCalendarConfig', 'CalendarConfig', 'Reservation', 'growl',
function ($scope, $stateParams, _t, moment, Auth, $timeout, Member, Availability, plansPromise, groupsPromise, machinePromise, settingsPromise, uiCalendarConfig, CalendarConfig, Reservation, growl) {
/* PRIVATE STATIC CONSTANTS */
// Slot free to be booked
@ -626,6 +626,20 @@ Application.Controllers.controller('ReserveMachineController', ['$scope', '$stat
*/
$scope.filterDisabledPlans = function (plan) { return !plan.disabled; };
/**
* Callback triggered by react components
*/
$scope.onSuccess = function (message) {
growl.success(message);
};
/**
* Callback triggered by react components
*/
$scope.onError = function (message) {
growl.error(message);
};
/* PRIVATE SCOPE */
/**

View File

@ -25,7 +25,6 @@ export interface Machine {
current_user_is_trained?: boolean,
current_user_next_training_reservation?: Reservation,
current_user_has_packs?: boolean,
current_user_available_for_packs_renewal?: boolean,
has_prepaid_packs_for_current_user?: boolean,
machine_projects?: Array<{
id: number,

View File

@ -324,6 +324,7 @@ angular.module('application.router', ['ui.router'])
}
},
resolve: {
machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }],
settingsPromise: ['Setting', function (Setting) { return Setting.query({ names: "['feature_tour_display']" }).$promise; }]
}
})

View File

@ -55,7 +55,6 @@
@import "modules/machines/machines-list";
@import "modules/machines/machines-filters";
@import "modules/machines/required-training-modal";
@import "modules/machines/propose-packs-modal";
@import "modules/user/avatar";
@import "modules/pricing/machines-pricing";
@import "modules/pricing/editable-price";
@ -63,5 +62,7 @@
@import "modules/pricing/pack-form";
@import "modules/pricing/delete-pack";
@import "modules/pricing/edit-pack";
@import "modules/prepaid-packs/propose-packs-modal";
@import "modules/prepaid-packs/packs-summary";
@import "app.responsive";

View File

@ -0,0 +1,27 @@
.packs-summary {
border: 1px solid #ddd;
margin: 15px;
padding: 0;
border-radius: 6px;
h3 {
border-bottom: 1px solid #ddd;
padding: 15px;
margin: 0;
line-height: 1.8rem;
font-size: 1.4rem;
font-weight: 600;
}
.content {
padding: 15px;
.button-wrapper {
text-align: center;
.buy-button {
margin-top: 5px;
}
}
}
}

View File

@ -29,6 +29,14 @@
<select-member></select-member>
</div>
<packs-summary item="machine"
item-type="'Machine'"
customer="ctrl.member"
operator="currentUser"
on-error="onError"
on-success="onSuccess">
</packs-summary>
<cart slot="selectedEvent"
slot-selection-time="selectionTime"
events="events"

View File

@ -391,7 +391,7 @@ section#cookies-modal div.cookies-consent .cookies-actions button.accept {
.machine-card {
.machine-actions {
button {
color: $secondary;
color: $primary;
}
}
}

View File

@ -15,8 +15,7 @@ if current_user
json.partial! 'api/reservations/reservation', reservation: current_user.next_training_reservation_by_machine(@machine)
end
end
json.current_user_has_packs current_user.packs?(@machine, nil)
json.current_user_available_for_packs_renewal current_user.packs?(@machine)
json.current_user_has_packs current_user.packs?(@machine)
json.has_prepaid_packs_for_current_user @machine.packs?(current_user)
end

View File

@ -3,6 +3,6 @@
json.array!(@user_packs) do |user_pack|
json.extract! user_pack, :minutes_used, :expires_at
json.prepaid_pack do
json.extract! user_pack.prepaid_pack :minutes
json.extract! user_pack.prepaid_pack, :minutes
end
end

View File

@ -192,6 +192,7 @@ en:
month: "{COUNT, plural, one{month} other{months}}"
year: "{COUNT, plural, one{year} other{years}}"
packs_summary:
prepaid_hours: "Prepaid hours"
remaining_HOURS: "You have {HOURS} prepaid hours remaining for this {ITEM, select, Machine{machine} Space{space} other{}}."
buy_a_new_pack: "Buy a new pack"
#book a training