Skip to content

Commit

Permalink
feat(members): add comment field on tickets change
Browse files Browse the repository at this point in the history
  • Loading branch information
mtthp committed Jun 21, 2024
1 parent 5327a1f commit a307ffa
Show file tree
Hide file tree
Showing 18 changed files with 450 additions and 82 deletions.
1 change: 1 addition & 0 deletions src/components/form/AppTextField.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
['pl-10']: slots.prepend || prependIcon,
['border-red-300 text-red-900 placeholder:text-red-300 focus:border-red-500 focus:ring-red-500']:
isInvalid,
['text-right']: type === 'number',
}"
:disabled="disabled"
:max="max"
Expand Down
165 changes: 165 additions & 0 deletions src/components/form/AppTextareaField.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<template>
<div class="flex flex-col gap-1">
<label v-if="label" class="block font-medium text-gray-900 sm:text-sm" :for="id">
{{ label }}
</label>
<div class="flex flex-row rounded-md shadow-sm">
<slot name="before" />
<div class="relative flex grow flex-row items-stretch gap-3">
<slot name="prepend">
<SvgIcon
v-if="prependIcon"
aria-hidden="true"
:class="{
['pointer-events-none absolute inset-y-0 left-0 z-20 ml-3 h-full w-5 text-gray-400']: true,
['text-red-500']: isInvalid,
}"
:path="prependIcon"
type="mdi" />
</slot>
<textarea
:id="id"
:aria-invalid="isInvalid"
:class="{
['block min-h-24 w-full rounded-md border-gray-300 focus:z-10 focus:border-indigo-500 focus:ring-indigo-500 disabled:cursor-not-allowed disabled:border-gray-200 disabled:bg-gray-50 disabled:text-gray-500 sm:text-sm']: true,
['rounded-none']: slots.after || slots.before,
['rounded-l-md']: !slots.before,
['rounded-r-md']: !slots.after,
['pl-10']: slots.prepend || prependIcon,
['border-red-300 text-red-900 placeholder:text-red-300 focus:border-red-500 focus:ring-red-500']:
isInvalid,
}"
:disabled="disabled"
:maxlength="maxLength"
:name="name"
:pattern="pattern"
:placeholder="placeholder"
:readonly="readonly"
:required="required"
:rows="rows"
:tabindex="tabindex"
:type="type"
:value="modelValue"
@blur="(event) => $emit('blur', event)"
@input="
(event) => $emit('update:modelValue', (event.currentTarget as HTMLInputElement).value)
" />
<slot name="append">
<SvgIcon
v-if="appendIcon"
aria-hidden="true"
class="pointer-events-none absolute inset-y-0 right-0 z-20 mr-3 h-full w-5 text-gray-400"
:path="appendIcon"
type="mdi" />
</slot>
</div>
<slot name="after" />
</div>

<ul v-if="!hideDetails" class="min-h-[1.4rem] px-3 text-xs">
<li v-if="!errors.length && hint">{{ hint }}</li>
<li v-for="error in errors" :key="`error-${error}`" class="text-red-600">
{{ error }}
</li>
</ul>
</div>
</template>

<script setup lang="ts">
import { useSlots, PropType, computed } from 'vue';
defineEmits(['blur', 'update:modelValue']);
const props = defineProps({
modelValue: {
type: String as PropType<string | null>,
default: null,
},
label: {
type: String,
default: null,
},
id: {
type: String,
default: null,
},
autocomplete: {
type: String,
default: null,
},
required: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
name: {
type: String,
default: null,
},
type: {
type: String,
default: 'text',
},
placeholder: {
type: String,
default: null,
},
errors: {
type: Array as PropType<String[]>,
default: () => [],
},
prependIcon: {
type: String,
default: '',
},
appendIcon: {
type: String,
default: '',
},
hideDetails: {
type: Boolean,
default: false,
},
hint: {
type: String,
default: '',
},
horizontal: {
type: Boolean,
default: false,
},
tabindex: {
type: String,
default: null,
},
readonly: {
type: Boolean,
default: false,
},
maxLength: {
type: [String, Number],
default: null,
},
pattern: {
type: String,
default: null,
},
rows: {
type: [String, Number],
default: 3,
},
});
const slots = useSlots();
const isInvalid = computed(() => props.errors && props.errors.length > 0);
</script>

<style scoped>
textarea {
field-sizing: content;
height: max-content;
}
</style>
8 changes: 4 additions & 4 deletions src/i18n/locales/en-GB/activity.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"detail": {
"comment": {
"label": "Reason",
"placeholder": "Explain the reason for this change"
},
"description": "",
"duration": {
"label": "Duration",
Expand All @@ -25,10 +29,6 @@
"fail": "Unable to update attendance on {date}",
"success": "Attendance on {date} has been updated"
},
"reason": {
"label": "Reason",
"placeholder": "Explain the reason for this change"
},
"title": "Attendance on {date}",
"type": {
"label": "Consumption Type",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/en-GB/subscriptions.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"delete": {
"comment": {
"label": "Reason",
"placeholder": "Explain the reason for this removal"
},
"description": "Are you sure you want to delete this subscription? You won't be able to undo this action.",
"onDelete": {
"fail": "Unable to delete this subscription",
Expand Down
8 changes: 8 additions & 0 deletions src/i18n/locales/en-GB/tickets.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"delete": {
"comment": {
"label": "Reason",
"placeholder": "Explain the reason for this removal"
},
"description": "Are you sure you want to delete this ticket order? You won't be able to undo this action.",
"onDelete": {
"fail": "Unable to delete this ticket order",
Expand All @@ -8,6 +12,10 @@
"title": "Deleting ticket order"
},
"detail": {
"comment": {
"label": "Reason",
"placeholder": "Explain the reason for this change"
},
"count": {
"label": "Number of tickets",
"unit": "ticket | ticket | tickets"
Expand Down
8 changes: 4 additions & 4 deletions src/i18n/locales/fr-FR/activity.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"detail": {
"comment": {
"label": "Motif",
"placeholder": "Expliquez la raison de ce changement"
},
"description": "",
"duration": {
"label": "Durée",
Expand All @@ -25,10 +29,6 @@
"fail": "Impossible de mettre à jour la présence du {date}",
"success": "Présence du {date} modifiée"
},
"reason": {
"label": "Motif",
"placeholder": "Expliquez la raison de ce changement"
},
"title": "Présence du {date}",
"type": {
"label": "Type de consommation",
Expand Down
4 changes: 4 additions & 0 deletions src/i18n/locales/fr-FR/subscriptions.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"delete": {
"comment": {
"label": "Motif",
"placeholder": "Expliquez la raison de cette suppression"
},
"description": "Êtes-vous sûr de vouloir supprimer cet abonnement ? Vous ne pourrez pas revenir en arrière.",
"onDelete": {
"fail": "Impossible de supprimer cet abonnement",
Expand Down
8 changes: 8 additions & 0 deletions src/i18n/locales/fr-FR/tickets.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"delete": {
"comment": {
"label": "Motif",
"placeholder": "Expliquez la raison de cette suppression"
},
"description": "Êtes-vous sûr de vouloir supprimer cette commande de tickets ? Vous ne pourrez pas revenir en arrière.",
"onDelete": {
"fail": "Impossible de supprimer cette commande de tickets",
Expand All @@ -8,6 +12,10 @@
"title": "Suppression de la commande de tickets"
},
"detail": {
"comment": {
"label": "Motif",
"placeholder": "Expliquez la raison de ce changement"
},
"count": {
"label": "Nombre de tickets",
"unit": "ticket | ticket | tickets"
Expand Down
2 changes: 1 addition & 1 deletion src/services/api/members.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const getMemberActivity = (id: string): Promise<Attendance[]> => {
export const updateMemberActivity = (
memberId: string,
activityId: string,
activity: Attendance & { reason: string },
activity: Attendance & { comment: string },
): Promise<Attendance> => {
return HTTP.put(`/api/members/${memberId}/activity/${activityId}`, activity).then(
({ data }) => data,
Expand Down
15 changes: 9 additions & 6 deletions src/services/api/subscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ export const getMemberSubscription = (
);
};

export type SubscriptionChange = Pick<Subscription, 'started'> & {
comment: string; // comment is mandatory to audit what happened
};

export const addMemberSubscription = (
memberId: string,
subscription: Subscription,
subscription: SubscriptionChange,
): Promise<Subscription> => {
return HTTP.post(`/api/members/${memberId}/subscriptions`, subscription).then(({ data }) => data);
};

export type SubscriptionChange = Pick<Subscription, 'started'> & {
comment: string; // comment is mandatory to audit what happened
};

export const updateMemberSubscription = (
memberId: string,
subscriptionId: string,
Expand All @@ -45,6 +45,9 @@ export const updateMemberSubscription = (
export const deleteMemberSubscription = (
memberId: string,
subscriptionId: string,
comment: string,
): Promise<void> => {
return HTTP.delete(`/api/members/${memberId}/subscriptions/${subscriptionId}`);
return HTTP.delete(`/api/members/${memberId}/subscriptions/${subscriptionId}`, {
data: { comment },
});
};
16 changes: 12 additions & 4 deletions src/services/api/tickets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,26 @@ export const getMemberTicket = (memberId: string, ticketId: string): Promise<Tic
return HTTP.get(`/api/members/${memberId}/tickets/${ticketId}`).then(({ data }) => data);
};

export const addMemberTicket = (memberId: string, ticket: Ticket): Promise<Ticket> => {
export type TicketChange = Pick<Ticket, 'count'> & {
comment: string; // comment is mandatory to audit what happened
};

export const addMemberTicket = (memberId: string, ticket: TicketChange): Promise<Ticket> => {
return HTTP.post(`/api/members/${memberId}/tickets`, ticket).then(({ data }) => data);
};

export const updateMemberTicket = (
memberId: string,
ticketId: string,
ticket: Ticket,
ticket: TicketChange,
): Promise<Ticket> => {
return HTTP.put(`/api/members/${memberId}/tickets/${ticketId}`, ticket).then(({ data }) => data);
};

export const deleteMemberTicket = (memberId: string, ticketId: string): Promise<void> => {
return HTTP.delete(`/api/members/${memberId}/tickets/${ticketId}`);
export const deleteMemberTicket = (
memberId: string,
ticketId: string,
comment: string,
): Promise<void> => {
return HTTP.delete(`/api/members/${memberId}/tickets/${ticketId}`, { data: { comment } });
};
Loading

0 comments on commit a307ffa

Please sign in to comment.