Skip to content

Commit

Permalink
Implemented client-side validation. (#139)
Browse files Browse the repository at this point in the history
* Created the AppInput client-validated component.

* Refactored SignInView.

* Refactored CountSelect.

* Refactored CountSelect.

* Fixed flag search parameter.

* Implemented client-side validation & refactored number inputs.

* Completed GtinInput validation.

* CodeReview.

* CodeReview.

* Refactored IssuedOnInput.

* Upgraded Logitar Vue3 UI.

* Refactored textarea components.

* Reset department modal on cancel.

* Fixed postal code RegEx.

* Refactored ArticleSelect and BannerSelect.

* CodeReview.

* Refactored inputs.

* Refactored multiple selects.

* TODO

* Refactored StoreSelect.

* CodeReview.

* Completed TarSelect refactor.

* TODO

* Implemented a DateTimeInput.

* Initial values.

* Revert CountSelect and SortSelect.

* Implementing client-server validation.

* TODOs.

* Refactored rules handling.

* Completed client-validation.
  • Loading branch information
Utar94 authored Apr 19, 2024
1 parent c5833eb commit 4fb6258
Show file tree
Hide file tree
Showing 59 changed files with 1,062 additions and 514 deletions.
8 changes: 4 additions & 4 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"@vee-validate/i18n": "~4.9.6",
"@vee-validate/rules": "~4.9.6",
"bootstrap": "^5.3.3",
"logitar-vue3-ui": "^2.0.5",
"logitar-vue3-ui": "^2.4.0",
"nanoid": "^5.0.7",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
Expand Down
64 changes: 35 additions & 29 deletions frontend/src/components/articles/ArticleSelect.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
<script setup lang="ts">
import { TarSelect, type SelectOption, type SelectOptions } from "logitar-vue3-ui";
import { computed, inject, onMounted, ref } from "vue";
import { useI18n } from "vue-i18n";
import type { SelectOption } from "logitar-vue3-ui";
import { computed, onMounted, ref } from "vue";
import AppSelect from "@/components/shared/AppSelect.vue";
import type { Article } from "@/types/articles";
import { handleErrorKey } from "@/inject/App";
import { searchArticles } from "@/api/articles";
const handleError = inject(handleErrorKey) as (e: unknown) => void;
const { t } = useI18n();
defineProps<{
disabled?: boolean | string;
modelValue?: string;
required?: boolean | string;
}>();
const articles = ref<Article[]>([]);
const options = computed<SelectOption[]>(() => articles.value.map(({ id, displayName }) => ({ value: id, text: displayName })));
const props = withDefaults(defineProps<SelectOptions>(), {
floating: true,
id: "article",
label: "articles.select.label",
placeholder: "articles.select.placeholder",
});
const emit = defineEmits<{
(e: "error", value: unknown): void;
(e: "selected", value?: Article): void;
(e: "update:model-value", value?: string): void;
}>();
function onModelValueUpdate(id?: string): void {
emit("update:model-value", id);
if (id) {
const article = articles.value.find((article) => article.id === id);
emit("selected", article);
} else {
emit("selected");
}
}
onMounted(async () => {
try {
Expand All @@ -40,26 +51,21 @@ onMounted(async () => {
});
articles.value = results.items;
} catch (e: unknown) {
handleError(e);
emit("error", e);
}
});
const emit = defineEmits<{
(e: "selected", value?: Article): void;
(e: "update:model-value", value?: string): void;
}>();
function onModelValueUpdate(id?: string): void {
emit("update:model-value", id);
if (id) {
const article = articles.value.find((article) => article.id === id);
emit("selected", article);
} else {
emit("selected");
}
}
</script>

<template>
<TarSelect v-bind="props" :label="t(label)" :options="options" :placeholder="t(placeholder)" @update:model-value="onModelValueUpdate" />
<AppSelect
:disabled="disabled"
floating
id="article"
label="articles.select.label"
:model-value="modelValue"
:options="options"
placeholder="articles.select.placeholder"
:required="required"
@update:model-value="onModelValueUpdate"
/>
</template>
26 changes: 14 additions & 12 deletions frontend/src/components/articles/GtinInput.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
<script setup lang="ts">
import { TarInput, type InputOptions } from "logitar-vue3-ui";
import { useI18n } from "vue-i18n";
import AppInput from "@/components/shared/AppInput.vue";
const { t } = useI18n();
const props = withDefaults(defineProps<InputOptions>(), {
floating: true,
id: "gtin",
label: "articles.gtin.label",
max: 14,
placeholder: "articles.gtin.label",
});
defineProps<{
modelValue?: string;
}>();
defineEmits<{
(e: "update:model-value", value?: string): void;
}>();
</script>

<template>
<TarInput v-bind="props" :label="t(label)" :placeholder="t(placeholder)" @update:model-value="$emit('update:model-value', $event)" />
<AppInput
floating
id="gtin"
label="articles.gtin.label"
max="14"
:model-value="modelValue"
placeholder="articles.gtin.label"
:rules="{ allowed_characters: '0123456789' }"
@update:model-value="$emit('update:model-value', $event)"
/>
</template>
62 changes: 33 additions & 29 deletions frontend/src/components/banners/BannerSelect.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
<script setup lang="ts">
import { TarSelect, type SelectOption, type SelectOptions } from "logitar-vue3-ui";
import { computed, inject, onMounted, ref } from "vue";
import { useI18n } from "vue-i18n";
import type { SelectOption } from "logitar-vue3-ui";
import { computed, onMounted, ref } from "vue";
import AppSelect from "@/components/shared/AppSelect.vue";
import type { Banner } from "@/types/banners";
import { handleErrorKey } from "@/inject/App";
import { searchBanners } from "@/api/banners";
const handleError = inject(handleErrorKey) as (e: unknown) => void;
const { t } = useI18n();
defineProps<{
modelValue?: string;
noStatus?: boolean | string;
}>();
const banners = ref<Banner[]>([]);
const options = computed<SelectOption[]>(() => banners.value.map(({ id, displayName }) => ({ value: id, text: displayName })));
const props = withDefaults(defineProps<SelectOptions>(), {
floating: true,
id: "banner",
label: "banners.select.label",
placeholder: "banners.select.placeholder",
});
const emit = defineEmits<{
(e: "error", value: unknown): void;
(e: "selected", value?: Banner): void;
(e: "update:model-value", value?: string): void;
}>();
function onModelValueUpdate(id?: string): void {
emit("update:model-value", id);
if (id) {
const banner = banners.value.find((banner) => banner.id === id);
emit("selected", banner);
} else {
emit("selected");
}
}
onMounted(async () => {
try {
Expand All @@ -40,26 +50,20 @@ onMounted(async () => {
});
banners.value = results.items;
} catch (e: unknown) {
handleError(e);
emit("error", e);
}
});
const emit = defineEmits<{
(e: "selected", value?: Banner): void;
(e: "update:model-value", value?: string): void;
}>();
function onModelValueUpdate(id?: string): void {
emit("update:model-value", id);
if (id) {
const banner = banners.value.find((banner) => banner.id === id);
emit("selected", banner);
} else {
emit("selected");
}
}
</script>

<template>
<TarSelect v-bind="props" :label="t(label)" :options="options" :placeholder="t(placeholder)" @update:model-value="onModelValueUpdate" />
<AppSelect
floating
id="banner"
label="banners.select.label"
:model-value="modelValue"
:no-status="noStatus"
:options="options"
placeholder="banners.select.placeholder"
@update:model-value="onModelValueUpdate"
/>
</template>
17 changes: 13 additions & 4 deletions frontend/src/components/departments/DepartmentEdit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,22 @@ function hide(): void {
modalRef.value?.hide();
}
function setModel(department?: Department) {
number.value = department?.number ?? "";
displayName.value = department?.displayName ?? "";
description.value = department?.description ?? "";
}
const emit = defineEmits<{
(e: "error", value: unknown): void;
(e: "saved", value: Department): void;
}>();
function onCancel(): void {
setModel(props.department);
hide();
}
const { handleSubmit, isSubmitting } = useForm();
const onSubmit = handleSubmit(async () => {
try {
Expand All @@ -61,9 +72,7 @@ const onSubmit = handleSubmit(async () => {
watchEffect(() => {
const department = props.department;
number.value = department?.number ?? "";
displayName.value = department?.displayName ?? "";
description.value = department?.description ?? "";
setModel(department);
});
</script>

Expand All @@ -83,7 +92,7 @@ watchEffect(() => {
<DescriptionTextarea v-model="description" />
</form>
<template #footer>
<TarButton icon="fas fa-ban" :text="t('actions.cancel')" variant="secondary" @click="hide" />
<TarButton icon="fas fa-ban" :text="t('actions.cancel')" variant="secondary" @click="onCancel" />
<TarButton
:disabled="isSubmitting || !hasChanges"
:icon="department ? 'fas fa-save' : 'fas fa-plus'"
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/departments/DepartmentList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ watch(payload, (payload) => refresh(payload), { deep: true, immediate: true });
<div class="row">
<SearchInput class="col-lg-4" v-model="search" />
<SortSelect class="col-lg-4" :descending="isDescending" :options="sortOptions" v-model="sort" @descending="isDescending = $event" />
<CountSelect class="col-lg-4" :model-value="count.toString()" @update:model-value="count = $event ?? 0" />
<CountSelect class="col-lg-4" :model-value="count" @update:model-value="count = $event ?? 0" />
</div>
<template v-if="departments.length">
<table class="table table-striped">
Expand Down
36 changes: 17 additions & 19 deletions frontend/src/components/departments/DepartmentSelect.vue
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
<script setup lang="ts">
import { TarSelect, type SelectOption, type SelectOptions } from "logitar-vue3-ui";
import type { SelectOption } from "logitar-vue3-ui";
import { computed } from "vue";
import { useI18n } from "vue-i18n";
import AppSelect from "@/components/shared/AppSelect.vue";
import type { Department } from "@/types/departments";
import type { Store } from "@/types/stores";
import { orderBy } from "@/helpers/arrayUtils";
const { t } = useI18n();
const props = withDefaults(
defineProps<
SelectOptions & {
store: Store;
}
>(),
{
floating: true,
id: "department",
label: "departments.select.label",
placeholder: "departments.select.placeholder",
},
);
const props = defineProps<{
modelValue?: string;
noStatus?: boolean | string;
store: Store;
}>();
const options = computed<SelectOption[]>(() =>
orderBy(
props.store.departments.map(({ displayName, number }) => ({ text: displayName, value: number })),
Expand All @@ -47,5 +36,14 @@ function onModelValueUpdate(number?: string): void {
</script>

<template>
<TarSelect v-bind="props" :label="t(label)" :options="options" :placeholder="t(placeholder)" @update:model-value="onModelValueUpdate" />
<AppSelect
floating
id="department"
label="departments.select.label"
:model-value="modelValue"
:no-status="noStatus"
:options="options"
placeholder="departments.select.placeholder"
@update:model-value="onModelValueUpdate"
/>
</template>
25 changes: 13 additions & 12 deletions frontend/src/components/products/SkuInput.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
<script setup lang="ts">
import { TarInput, type InputOptions } from "logitar-vue3-ui";
import { useI18n } from "vue-i18n";
import AppInput from "@/components/shared/AppInput.vue";
const { t } = useI18n();
const props = withDefaults(defineProps<InputOptions>(), {
floating: true,
id: "sku",
label: "products.sku.label",
max: 32,
placeholder: "products.sku.label",
});
defineProps<{
modelValue?: string;
}>();
defineEmits<{
(e: "update:model-value", value?: string): void;
}>();
</script>

<template>
<TarInput v-bind="props" :label="t(label)" :placeholder="t(placeholder)" @update:model-value="$emit('update:model-value', $event)" />
<AppInput
floating
id="sku"
label="products.sku.label"
max="32"
:model-value="modelValue"
placeholder="products.sku.label"
@update:model-value="$emit('update:model-value', $event)"
/>
</template>
Loading

0 comments on commit 4fb6258

Please sign in to comment.