Skip to content

Commit

Permalink
Adapte les fiches de sorties aux listes des événements (#338)
Browse files Browse the repository at this point in the history
* Adapte les fiches de sorties aux listes des événements (#290)

* Corrige selon review

* Corrige le dernier point (vu ensemble)

* Corrige selon review

* Utilise le component `Button` dans `ButtonDropdown`

* Corrige un test

* Corrige un snapshot de PDF

* Trie les listes par ID (et pas par nom) dans les fiches de sortie

* Dernière petites corrections

* Apply suggestions from code review

* Corrige selon review

* Corrige l'ordre du matériel dans les fiches de sorties

---------

Co-authored-by: Donovan <donovan@pulsanova.com>
  • Loading branch information
polosson and Donov4n authored Jul 28, 2023
1 parent 482a352 commit 94e6c1e
Show file tree
Hide file tree
Showing 44 changed files with 2,290 additions and 1,963 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Ce projet adhère au principe du [Semantic Versioning](https://semver.org/spec/v
- Améliore grandement les performances de calcul des disponibilités du matériel. Cela se traduit
par des temps de chargement divisés par 5 (donc un gain de 500% !) dans le calendrier, mais aussi
à l'étape 4 de l'édition d'événement, et dans l'onglet "périodes de réservation" du matériel (Premium #321).
- Ajoute la possibilité de télécharger la fiche de sortie avec une page par parc de matériel (Premium #290).

## 0.21.2-premium (2023-05-15)

Expand Down
27 changes: 0 additions & 27 deletions client/src/hooks/useNow.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@use '~@/themes/default/style/globals';

/// Padding vertical des boutons.
/// @type Number
$padding-y: 0.396rem !default;

/// Padding horizontal des boutons.
/// @type Number
$padding-x: 0.626rem !default;

/// Marge entre l'icône et le texte des boutons (si icône présente)
/// @type Number
$icon-margin: 0.385rem !default;

/// Largeur de la bordure des boutons.
/// @type Number
$border-width: 2px !default;

/// Border radius des boutons.
/// @type Number
$border-radius: 4px !default;
75 changes: 75 additions & 0 deletions client/src/themes/default/components/ButtonDropdown/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
@use '~@/themes/default/components/Button/variables' as button;
@use '~@/themes/default/style/globals';
@use 'sass:color';

.ButtonDropdown {
$block: &;

position: relative;
display: flex;

//
// - Bouton principal
//

&__main-button {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}

//
// - Bouton toggle
//

&__toggle {
// - Important, pour surcharger la définition de .Button + .Button
// stylelint-disable-next-line declaration-no-important
margin-left: -(button.$border-width) !important;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}

&__action-button {
width: 100%;
}

//
// - Menu
//

&__menu {
position: absolute;
z-index: 1;
top: 100%;
right: 0;
margin: 2px 0 0;
padding: 0;
background: globals.$bg-color-dropdown-menu;
box-shadow: -2px 6px 6px rgba(0, 0, 0, 0.25);
transform-origin: 50% 0%;
transform: scaleY(0);
transition: transform 150ms ease-in-out;
white-space: nowrap;

&__item {
flex: 0 0 auto;
margin: 0;
white-space: nowrap;
list-style: none;

& + & {
margin-top: 2px;
}
}
}

//
// - Open
//

&--open {
#{$block}__menu {
transform: none;
}
}
}
249 changes: 249 additions & 0 deletions client/src/themes/default/components/ButtonDropdown/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
import './index.scss';
import { defineComponent } from '@vue/composition-api';
import ClickOutside from 'vue-click-outside';
import Icon from '@/themes/default/components/Icon';
import Button from '@/themes/default/components/Button';

import type { PropType } from '@vue/composition-api';
import type { Props as IconProps } from '@/themes/default/components/Icon';

type Action = {
/** Le contenu à afficher dans le bouton d'action secondaire. */
label: string,

/** Le type de bouton d'action secondaire à utiliser. */
type?: string,

/**
* Si l'action secondaire est un lien, la cible du lien sous forme de chaîne,
* ou d'objet `Location` compatible avec Vue-Router.
*
* Si non définie, un élément HTML `<button>` sera utilisé et
* vous devriez écouter l'événement `onClick` pour réagir au click.
*/
target?: string | Location,

/**
* Si l'action secondaire est un lien, permet d'indiquer que c'est un lien externe.
*
* Si c'est le cas, le fonctionnement sera le suivant :
* - Le routing interne ("Vue Router"), ne sera pas utilisé.
* (Il ne faut donc pas passer d'objet à `target` mais bien une chaîne)
* - Si c'est une URL absolue, celle-ci s'ouvrira dans une autre fenêtre / onglet.
*/
external?: boolean,

/**
* L'éventuel icône à utiliser avant le texte de l'action secondaire.
*
* Doit contenir une chaîne de caractère avec les composantes suivantes séparées par `:` :
* - Le nom de l'icône sous forme de chaîne (e.g. `plus`, `wrench`)
* Pour une liste exhaustive des codes, voir: https://fontawesome.com/v5.15/icons?m=free
* - La variante à utiliser de l'icône à utiliser (`solid`, `regular`, ...).
*
* @example
* - `wrench`
* - `wrench:solid`
*/
icon?: string | `${string}:${Required<IconProps>['variant']}`,

/**
* Fonction à utiliser lors d'un clic sur le bouton d'action secondaire.
*
* N'est utile que quand l'action secondaire n'est pas un lien.
*/
onClick?(e: MouseEvent): void,
};

type Props = {
/** Le contenu à afficher dans le bouton principal. */
label: string,

/**
* Si le bouton principal est un lien, la cible du lien sous forme de chaîne,
* ou d'objet `Location` compatible avec Vue-Router.
*
* Si non définie, un élément HTML `<button>` sera utilisé et
* vous devriez écouter l'événement `onClick` pour réagir au click.
*/
to?: string | Location,

/**
* Si le bouton principal est un lien, permet d'indiquer que c'est un lien externe.
*
* Si c'est le cas, le component fonctionnera comme suit:
* - Le routing interne ("Vue Router"), ne sera pas utilisé.
* (Il ne faut donc pas passer d'objet à `to` mais bien une chaîne)
* - Si c'est une URL absolue, celle-ci s'ouvrira dans une autre fenêtre / onglet.
*/
external?: boolean,

/**
* L'éventuel icône à utiliser avant le texte du bouton principal.
*
* Doit contenir une chaîne de caractère avec les composantes suivantes séparées par `:`:
* - Le nom de l'icône sous forme de chaîne (e.g. `plus`, `wrench`)
* Pour une liste exhaustive des codes, voir: https://fontawesome.com/v5.15/icons?m=free
* - La variante à utiliser de l'icône à utiliser (`solid`, `regular`, ...).
*
* @example
* - `wrench`
* - `wrench:solid`
*/
icon?: string | `${string}:${Required<IconProps>['variant']}`,

/**
* Permet d'indiquer si le bouton principal et les actions secondaires sont désactivés.
*
* Si c'est le cas (true), le bouton principal et toutes les actions secondaires seront
* affichés grisés et ne seront pas cliquables.
*/
disabled?: boolean,

/**
* Un tableau d'objets décrivant toutes les actions présentes dans le dropdown.
*
* Voir le type {@link Action} pour plus de détails.
*/
actions: Action[],
};

type Data = {
isOpen: boolean,
};

/**
* ButtonDropdown
*
* Affiche un bouton qui permet de déclencher une action principale, et un menu
* déroulant contenant des actions secondaires.
*
* Le bouton principal peut être soit un lien (externe ou non), soit un button.
* Dans le premier cas, il faut passer les props `to` et éventuellement `external`,
* et dans le second cas il suffit d'utiliser l'événement `onClick`.
*
* Pour les actions secondaires, chaque item de la liste `actions` doit être du
* type `Action` (voir documentation).
*/
const ButtonDropdown = defineComponent({
name: 'ButtonDropdown',
directives: { ClickOutside },
props: {
label: {
type: String as PropType<Props['label']>,
required: true,
},
to: {
type: [String, Object] as PropType<Props['to']>,
default: undefined,
},
external: {
type: Boolean as PropType<Required<Props>['external']>,
default: false,
},
icon: {
type: String as PropType<Props['icon']>,
default: undefined,
},
disabled: {
type: Boolean as PropType<Required<Props>['disabled']>,
default: false,
},
actions: {
type: Array as PropType<Props['actions']>,
required: true,
validator: (values: unknown) => (
Array.isArray(values) && values.length > 0
),
},
},
emits: ['click'],
data: (): Data => ({
isOpen: false,
}),
methods: {
// ------------------------------------------------------
// -
// - Handlers
// -
// ------------------------------------------------------

handleClose() {
this.isOpen = false;
},

handleToggle() {
this.isOpen = !this.isOpen;
},

handleClick(e: MouseEvent) {
if (this.disabled) {
return;
}

this.isOpen = false;
this.$emit('click', e);
},
},
render() {
const {
isOpen,
disabled,
handleClose,
icon,
label,
to,
external,
handleClick,
handleToggle,
actions,
} = this;

const classNames = ['ButtonDropdown', {
'ButtonDropdown--open': isOpen,
}];

return (
<div class={classNames} v-clickOutside={handleClose}>
<Button
type="secondary"
to={to}
icon={icon}
external={external}
onClick={handleClick}
disabled={disabled}
class="ButtonDropdown__main-button"
>
{label}
</Button>
<Button
type="secondary"
onClick={handleToggle}
class="ButtonDropdown__toggle"
disabled={disabled}
>
<Icon name="ellipsis-h" />
</Button>
<ul class="ButtonDropdown__menu">
{actions.map((action: Action) => (
<li class="ButtonDropdown__menu__item" key={action.label}>
<Button
type={action.type}
to={action.target}
icon={action.icon}
external={action.external}
onClick={action.onClick ?? (() => {})}
disabled={disabled}
class="ButtonDropdown__action-button"
>
{action.label}
</Button>
</li>
))}
</ul>
</div>
);
},
});

export default ButtonDropdown;
Loading

0 comments on commit 94e6c1e

Please sign in to comment.