Skip to content

Commit

Permalink
feat(fe:FSADT1-1523): Apply design styling to Search screen (#1282)
Browse files Browse the repository at this point in the history
* feat: add slot for custom item component on the Auto Complete

* feat: customize predictive search result items

* feat: allow custom size for Auto Complete

* feat: display no results message

* feat: update search-line paddings

* feat: update placeholder color

* fix: update function toTitleCase

* test: display no results message

* fix: prevent reading from undefined

* chore: update stubs

* fix: remove unnecessary css code

---------

Co-authored-by: Maria Martinez <[email protected]>
  • Loading branch information
fterra-encora and mamartinezmejia authored Oct 29, 2024
1 parent 3104e5e commit b656df7
Show file tree
Hide file tree
Showing 8 changed files with 239 additions and 120 deletions.
34 changes: 33 additions & 1 deletion frontend/cypress/e2e/pages/SearchPage.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ describe("Search Page", () => {
.should("have.length.greaterThan", 0)
.should("be.visible");

cy.get("#search-box").find(`cds-combo-box-item[data-value^="${clientNumber}"]`).click();
cy.get("#search-box").find(`cds-combo-box-item[data-id="${clientNumber}"]`).click();
});
it("navigates to the client details", () => {
const greenDomain = "green-domain.com";
Expand Down Expand Up @@ -396,4 +396,36 @@ describe("Search Page", () => {
});
});
});

describe("when the API finds no matching results", () => {
beforeEach(() => {
// The "empty" value actually triggers the empty array response
cy.fillFormEntry("#search-box", "empty");
});
describe("and user clicks the Search button", () => {
beforeEach(() => {
cy.get("#search-button").click();
});

it('displays a "No results" message that includes the submitted search value', () => {
cy.wait("@fullSearch");

cy.contains("No results for “empty”");
});

describe("and the user types something else in the search box but does not re-submit the full search", () => {
beforeEach(() => {
cy.wait("@fullSearch");

cy.fillFormEntry("#search-box", "other");
});

it('still displays the "No results" message with the value that was previously submitted', () => {
cy.wait(200);

cy.contains("No results for “empty”");
});
});
});
});
});
30 changes: 29 additions & 1 deletion frontend/src/assets/styles/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -1641,14 +1641,42 @@ div.internal-grouping-01:has(svg.warning) span.body-compact-01 {
}

#search-line {
--lg-size: 3rem;
--padding-size: 2.5rem;

display: flex;
align-items: flex-end;
align-items: flex-start;
min-height: calc(var(--lg-size) + var(--padding-size));
padding: 0 var(--padding-size);

.grouping-02 {
flex-grow: 1;
}
}

#search-box {
--row-height: 2.875rem;

cds-combo-box-item {
block-size: var(--row-height);
}

&::part(cds--combo-box), &::part(input) {
border-bottom-width: 0;
}

&::part(input)::placeholder {
color: var(--light-theme-text-text-primary);
}
}

.search-result-item {
display: flex;
gap: 1rem;
align-items: center;
margin: -0.0625rem 0;
}

#search-button {
width: 7.875rem;
}
Expand Down
19 changes: 12 additions & 7 deletions frontend/src/components/forms/AutoCompleteInputComponent.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts" generic="T">
import { ref, computed, watch, nextTick } from "vue";
// Carbon
import "@carbon/web-components/es/components/combo-box/index";
Expand All @@ -7,8 +7,9 @@ import type { CDSComboBox } from "@carbon/web-components";
// Composables
import { useEventBus } from "@vueuse/core";
// Types
import type { BusinessSearchResult, CodeNameType } from "@/dto/CommonTypesDto";
import type { BusinessSearchResult, CodeNameType, CodeNameValue } from "@/dto/CommonTypesDto";
import { isEmpty, type ValidationMessageType } from "@/dto/CommonTypesDto";
import type { DROPDOWN_SIZE } from "@carbon/web-components/es/components/dropdown/defs";
//Define the input properties for this component
const props = withDefaults(
Expand All @@ -18,8 +19,9 @@ const props = withDefaults(
ariaLabel?: string;
tip?: string;
placeholder?: string;
size?: `${DROPDOWN_SIZE}`;
modelValue: string;
contents: Array<BusinessSearchResult>;
contents: Array<CodeNameValue<T>>;
validations: Array<Function>;
errorMessage?: string;
loading?: boolean;
Expand Down Expand Up @@ -82,15 +84,15 @@ watch(
);
// This is to make the input list contains the selected value to show when component render
const inputList = computed<Array<BusinessSearchResult>>(() => {
const inputList = computed<Array<CodeNameValue<T>>>(() => {
if (props.contents?.length > 0) {
return props.contents.filter((entry) => entry.name);
} else if (props.modelValue !== userValue.value) {
// Needed when the component mounts with a pre-filled value.
return [{ name: props.modelValue, code: "", status: "", legalType: "" }];
return [{ name: props.modelValue, code: "", value: undefined }];
} else if (props.modelValue && showLoading.value) {
// Just to give a "loading" feedback.
return [{ name: loadingName, code: "", status: "", legalType: "" }];
return [{ name: loadingName, code: "", value: undefined }];
}
return [];
});
Expand Down Expand Up @@ -313,6 +315,7 @@ const safeHelperText = computed(() => props.tip || " ");
<cds-combo-box
ref="cdsComboBoxRef"
:id="id"
:size="size"
:class="warning ? 'warning' : ''"
:autocomplete="autocomplete"
:title-text="label"
Expand Down Expand Up @@ -359,7 +362,9 @@ const safeHelperText = computed(() => props.tip || " ");
<cds-inline-loading />
</template>
<template v-else>
{{ item.name }}
<slot :value="item.value">
{{ item.name }}
</slot>
</template>
</cds-combo-box-item>
</cds-combo-box>
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/dto/CommonTypesDto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export interface CodeNameType {
name: string;
}

export interface CodeNameValue<T> extends CodeNameType {
value: T;
}

export interface BusinessSearchResult {
code: string;
name: string;
Expand Down
54 changes: 42 additions & 12 deletions frontend/src/pages/SearchPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import "@carbon/web-components/es/components/tag/index";
import { useFetchTo } from "@/composables/useFetch";
import { useEventBus } from "@vueuse/core";
import type { ClientSearchResult, CodeNameType } from "@/dto/CommonTypesDto";
import type { ClientSearchResult, CodeNameValue } from "@/dto/CommonTypesDto";
import { adminEmail, getObfuscatedEmailLink, toTitleCase } from "@/services/ForestClientService";
import summit from "@carbon/pictograms/es/summit";
import userSearch from "@carbon/pictograms/es/user--search";
import useSvg from "@/composables/useSvg";
// @ts-ignore
Expand All @@ -30,6 +31,7 @@ import {
const revalidateBus = useEventBus<string[] | undefined>("revalidate-bus");
const summitSvg = useSvg(summit);
const userSearchSvg = useSvg(userSearch);
const userhasAuthority = ["CLIENT_VIEWER", "CLIENT_EDITOR", "CLIENT_ADMIN", "CLIENT_SUSPEND"].some(authority => ForestClientUserSession.authorities.includes(authority));
Expand All @@ -42,6 +44,7 @@ const totalItems = ref(0);
const pageSize = ref(10);
const searchKeyword = ref("");
const lastSearchKeyword = ref("");
// empty is valid
const valid = ref(true);
Expand Down Expand Up @@ -75,6 +78,8 @@ const search = (skipResetPage = false) => {
if (!skipResetPage) {
pageNumber.value = 1;
}
lastSearchKeyword.value = searchKeyword.value;
fetchSearch();
};
Expand Down Expand Up @@ -116,18 +121,29 @@ const paginate = (event: any) => {
};
/**
* Converts a client search result to a code/name representation.
* Converts a client search result to a code/name/value representation.
* @param searchResult The client search result
*/
const searchResultToCodeName = (searchResult: ClientSearchResult): CodeNameType => {
const { clientNumber, clientFullName, clientType, city, clientStatus } = searchResult;
const searchResultToCodeNameValue = (
searchResult: ClientSearchResult,
): CodeNameValue<ClientSearchResult> => {
const { clientNumber, clientFullName } = searchResult;
const result = {
code: clientNumber,
name: `${toTitleCase(`${clientNumber}, ${clientFullName}, ${clientType}, ${city}`)} (${clientStatus})`,
name: clientFullName,
value: searchResult,
};
return result;
};
const searchResultToCodeNameValueList = (list: ClientSearchResult[]) => list.map(searchResultToCodeNameValue);
const searchResultToText = (searchResult: ClientSearchResult): string => {
const { clientNumber, clientFullName, clientType, city } = searchResult;
const result = toTitleCase(`${clientNumber}, ${clientFullName}, ${clientType}, ${city}`);
return result;
};
const openClientDetails = (clientCode: string) => {
if (clientCode) {
const url = `https://${greenDomain}/int/client/client02MaintenanceAction.do?bean.clientNumber=${clientCode}`;
Expand Down Expand Up @@ -160,7 +176,6 @@ onMounted(() => {

<template>
<div id="screen" class="table-list">

<div id="title" v-if="userhasAuthority">
<div>
<div class="form-header-title mg-sd-25">
Expand Down Expand Up @@ -202,8 +217,9 @@ onMounted(() => {
autocomplete="off"
tip=""
placeholder="Search by client number, name or acronym"
size="lg"
v-model="searchKeyword"
:contents="content?.map(searchResultToCodeName)"
:contents="searchResultToCodeNameValueList(content)"
:validations="validations"
:validations-on-change="validationsOnChange"
:loading="loading"
Expand All @@ -212,7 +228,15 @@ onMounted(() => {
@update:model-value="valid = false"
@error="valid = !$event"
@press:enter="search()"
/>
#="{ value }"
>
<div class="search-result-item" v-if="value">
{{ searchResultToText(value) }}
<cds-tag :type="tagColor(value.clientStatus)" title="">
<span>{{ value.clientStatus }}</span>
</cds-tag>
</div>
</AutoCompleteInputComponent>
</data-fetcher>
<cds-button kind="primary" @click.prevent="search()" id="search-button">
<span>Search</span>
Expand Down Expand Up @@ -265,16 +289,22 @@ onMounted(() => {
/>
</div>

<div
class="empty-table-list"
v-if="(!tableData || totalItems == 0) && userhasAuthority && !loadingSearch"
>
<div class="empty-table-list" v-if="!tableData && userhasAuthority && !loadingSearch">
<summit-svg alt="Summit pictogram" class="standard-svg" />
<p class="heading-02">Nothing to show yet!</p>
<p class="body-compact-01">Enter at least one criteria to start the search.</p>
<p class="body-compact-01">The list will display here.</p>
</div>

<div
class="empty-table-list"
v-if="tableData && totalItems == 0 && userhasAuthority && !loadingSearch"
>
<user-search-svg alt="User search pictogram" class="standard-svg" />
<p class="heading-02">No results for “{{ lastSearchKeyword }}”</p>
<p class="body-compact-01">Consider adjusting your search term(s) and try again.</p>
</div>

<div class="paginator" v-if="totalItems && userhasAuthority">
<cds-pagination
items-per-page-text="Clients per page"
Expand Down
17 changes: 12 additions & 5 deletions frontend/src/services/ForestClientService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,18 @@ export const getContactDescription = (contact: Contact, index: number): string =

export const toTitleCase = (inputString: string): string => {
if (inputString === undefined) return "";
return inputString
.toLowerCase()
.split(" ")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");

const splitMapJoin = (currentString: string, separator: string) =>
currentString
.split(separator)
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(separator);

let result = inputString.toLowerCase();
result = splitMapJoin(result, " ");
result = splitMapJoin(result, "(");
result = splitMapJoin(result, ".");
return result;
};

export const toSentenceCase = (inputString: string): string => {
Expand Down
4 changes: 2 additions & 2 deletions frontend/stub/__files/response-client-search-page0.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
{
"clientNumber": "00191081",
"clientAcronym": null,
"clientFullName": "DFGDGDFG",
"clientFullName": "D.F.G. DGDFG",
"clientType": "Ministry of Forests and Range",
"city": "CALGARY",
"clientStatus": "Active",
Expand All @@ -74,7 +74,7 @@
{
"clientNumber": "00191080",
"clientAcronym": null,
"clientFullName": "EDMOND KAAUA",
"clientFullName": "EDMOND KAAUA (EDMOND'S PAINTING)",
"clientType": "Individual",
"city": "VICTORIA",
"clientStatus": "Active",
Expand Down
Loading

0 comments on commit b656df7

Please sign in to comment.