日本語版
最新ニュース
企業

商品一覧ページ – グラフの使用

Optimizely Graph では、GraphQL を使用して高度な方法でデータをクエリできます。ファセットと検索フレーズを使用してデータをクエリすることは、コマース サイトの「製品リスト ページ」を構築するときに非常に役立ちます。NextJs を使用して、Customizable Commerce の製品リスト ページを作成する方法について説明します。 このチュートリアルでは、Foundationのサンプルサイトを使用します。Foundationの設定方法の詳細については、 https://github.com/episerver/Foundation ブランド、サイズ、色、価格で製品をフィルタリングできる NextJs アプリを作成します。また、「セマンティック検索」と通常の関連性検索を使用して検索することもできます。 注: この例の「製品リスト ページ」のテンプレートとスタイル設定は、スタイル設定に関する知識がほとんどない人 (私) によって作成されました。これは、テンプレートを作成する方法を示す単なる例です。 Foundationサイトの設定から始め、サイトが正常に動作していることを確認します。また、編集/管理者モードでログインできることを確認してください。Foundationサイトの詳細については、以下を参照してください。 https://github.com/episerver/Foundation Foundation サイトから Graph にコンテンツを送信するには、Graph アカウントが必要です。ただし、Graph アカウントを持っていなくても心配はいりません。Foundation コンテンツを含む Graph アカウントを用意していますので、アカウントを使用してアプリケーションの作成をテストできます。 注: Graph アカウントをお持ちでない場合は、この部分 (「Foundation サイトで Graph を有効にする」)…

商品一覧ページ – グラフの使用

1720224538
2024-07-05 12:21:40

Optimizely Graph では、GraphQL を使用して高度な方法でデータをクエリできます。ファセットと検索フレーズを使用してデータをクエリすることは、コマース サイトの「製品リスト ページ」を構築するときに非常に役立ちます。NextJs を使用して、Customizable Commerce の製品リスト ページを作成する方法について説明します。

このチュートリアルでは、Foundationのサンプルサイトを使用します。Foundationの設定方法の詳細については、 https://github.com/episerver/Foundation

ブランド、サイズ、色、価格で製品をフィルタリングできる NextJs アプリを作成します。また、「セマンティック検索」と通常の関連性検索を使用して検索することもできます。

注: この例の「製品リスト ページ」のテンプレートとスタイル設定は、スタイル設定に関する知識がほとんどない人 (私) によって作成されました。これは、テンプレートを作成する方法を示す単なる例です。

Foundationサイトの設定から始め、サイトが正常に動作していることを確認します。また、編集/管理者モードでログインできることを確認してください。Foundationサイトの詳細については、以下を参照してください。 https://github.com/episerver/Foundation

Foundation サイトから Graph にコンテンツを送信するには、Graph アカウントが必要です。ただし、Graph アカウントを持っていなくても心配はいりません。Foundation コンテンツを含む Graph アカウントを用意していますので、アカウントを使用してアプリケーションの作成をテストできます。

注: Graph アカウントをお持ちでない場合は、この部分 (「Foundation サイトで Graph を有効にする」) をスキップできます。代わりに、次のセクション「GraphQL クエリの作成」に進み、次の singleKey を使用できます: 5m4F2pBpXPWehc3QGqFfvohgNtgYxHQOxfmKsnqhRYDpZTBU

Graphにコンテンツをプッシュするためのパッケージをインストールする

次の NuGet パッケージをインストールします: Optimizely.ContentGraph.Cms

Optimizely.ContentGraph.Cms

Graphアカウントを設定する

appSettings.json に次のセクションを追加します。

  "Optimizely": {
    "ContentGraph": {
      "GatewayAddress": "https://cg.optimizely.com",
      "AppKey": "{your-app-key}",
      "Secret": "{your-secret}",
      "SingleKey": "{your-single-key}",
      "AllowSendingLog": "true"
    }
  }

Startup.cs の “ConfigureServices” メソッドに次のコードを追加します。

            services.AddContentGraph(x =>
            {
                x.IncludeInheritanceInContentType = true;
                x.PreventFieldCollision = true;
            });

「GraphCommerceIntegration」プロジェクトへの参照を追加します

1. リポジトリGraphCommerceIntegrationをクローンする

https://github.com/jonasbergqvist/GraphCommerceIntegration.git

2. プロジェクトを追加します。GraphCommerceIntegration.csproj「」の下にあります。「GraphCommerceIntegration」フォルダーを Foundation ソリューションに追加します。

3. Foundation サイトのプロジェクト参照を「GraphCommerceIntegration」に追加します。

4. Foundationサイトをコンパイルして起動する

注: リポジトリには

  • スケジュールされたジョブにカタログ コンテンツを含めるクラス (カタログ コンテンツのイベント同期はプロジェクトを参照せずに機能します)
  • カタログ コンテンツに構成されているすべての言語を含むクラス。
  • 価格が変更されたときにイベント同期をトリガーするクラス。
  • 価格ベースクラス。製品やバリエーションをグラフにプッシュするときに価格情報を簡単に含めることができます。
  • アセットベースクラス。Graphにプッシュされたときに、製品やバリエーションのアセット情報を簡単に追加できます。
  • バリエーションから関連製品へのデータを簡単に集計できる集計基本クラス。

注: 詳細については、READMEを参照してください。 https://github.com/jonasbergqvist/GraphCommerceIntegration/tree/main

商品とバリエーションにデフォルト価格を追加する

製品とバリエーションのデフォルト マーケットにデフォルト価格を含めるには、次のクラスを Foundation サイトに追加します。

    [ServiceConfiguration(typeof(IContentApiModelProperty), Lifecycle = ServiceInstanceScope.Singleton)]
    public class DefaultPriceContentApiModel : ContentApiModelPriceBase
    {
        private readonly ICurrentMarket _currentMarketService;

        public DefaultPriceContentApiModel(
            ContentTypeModelRepository contentTypeModelRepository,
            IContentLoader contentLoader,
            IPriceService priceService,
            ICurrentMarket currentMarketService)
            : base(contentTypeModelRepository, contentLoader, priceService)
        {
            _currentMarketService = currentMarketService;
        }

        public override string Name => "DefaultMarketPrice";

        protected override IMarket GetMarket()
        {
            return _currentMarketService.GetCurrentMarket();
        }
    }

注: 製品は、関連するバリエーションから最も低い価格を取得します。

バリエーションコンテンツから製品コンテンツへのデータの集計

コンテンツがグラフにプッシュされるときに色とサイズを含めるには、次の 2 つのクラスを追加します。

    [ServiceConfiguration(typeof(IContentApiModelProperty), Lifecycle = ServiceInstanceScope.Singleton)]
    public class ColorContentApiModel : ProductAggregationContentApiModelBase
    {
        public ColorContentApiModel(ContentTypeModelRepository contentTypeModelRepository, IContentLoader contentLoader)
            : base(contentTypeModelRepository, contentLoader)
        {
        }

        public override string Name => "Colors";

        protected override Expression> VariationProperty => (x) => x.Color;
    }

サイズ

    [ServiceConfiguration(typeof(IContentApiModelProperty), Lifecycle = ServiceInstanceScope.Singleton)]
    public class ColorContentApiModel : ProductAggregationContentApiModelBase
    {
        public ColorContentApiModel(ContentTypeModelRepository contentTypeModelRepository, IContentLoader contentLoader)
            : base(contentTypeModelRepository, contentLoader)
        {
        }

        public override string Name => "Colors";

        protected override Expression> VariationProperty => (x) => x.Color;
    }

デフォルトの製品アセットをグラフにプッシュする

グラフにプッシュされたときに、デフォルトの製品アセットのURLを製品に含めるために、次のクラスを追加します。

    [ServiceConfiguration(typeof(IContentApiModelProperty), Lifecycle = ServiceInstanceScope.Singleton)]
    public class DefaultImageUrlContentApiModel : CommerceAssetApiModelBase
    {
        public DefaultImageUrlContentApiModel(ContentTypeModelRepository contentTypeModelRepository, IContentLoader contentLoader, IUrlResolver urlResolver)
            : base(contentTypeModelRepository, contentLoader, urlResolver)
        {
        }

        public override string Name => "DefaultImageUrl";

        public override string NoValue => string.Empty;

        protected override string GetAssets(IEnumerable commerceMediaItems)
        {
            foreach(CommerceMedia media in commerceMediaItems.OrderBy(x => x.SortOrder))
            {
                if (ContentLoader.TryGet(media.AssetLink, out var contentMedia))
                {
                    return GetUrl(media);
                }
            }

            return NoValue;
        }
    }

Foundationサイトのデータを使用してGraphQLクエリをいくつか作成します。URLを使用してオンラインIDEを使用できます。 https://cg.optimizely.com/app/graphiql?auth={あなたの単一キー}独自の Graph アカウントをお持ちでない場合は、サンプル アカウントを使用できます。 https://cg.optimizely.com/app/graphiql?auth=5m4F2pBpXPWehc3QGqFfvohgNtgYxHQOxfmKsnqhRYDpZTBU

商品リストページを作成する

製品リストページを処理するには、次のクエリ (フラグメントを含む) を作成します。

fragment GenericProductTeaser on GenericProduct {
  Name
  Code
  DefaultMarketPrice
  Brand
  DefaultImageUrl
}

query ProductListing(
  $languages: [Locales] = en
  $searchText: String,
  $brands: [String!],
  $sizes: [String!],
  $colors: [String!],
  $minPrice: Float,
  $maxPrice: Float,
  $skip: Int = 0,
  $limit: Int = 10,
  $order: GenericProductOrderByInput = { 
    _ranking: SEMANTIC,
  }) {
    GenericProduct(
      locale: $languages
      where:{
        _or:[
        {
          _fulltext: {
              match: $searchText
          }
        },
        {
          Name: {
            match: $searchText
            boost: 5
          }
        }
        ]
        DefaultMarketPrice: {
            gte: $minPrice
            lte: $maxPrice
        }
      }
      skip: $skip,
      limit: $limit
      orderBy: $order
  	) {
      total
      items {
          ...GenericProductTeaser
      }
      facets {
          Brand(filters: $brands) {
              name
              count
          }
          Sizes(filters:$sizes) {
              name
              count
          }
          Colors(filters:$colors) {
              name
              count
          }
          DefaultMarketPrice(
            ranges: [
              { to: 50 },
              { from: 51, to: 100 },
              { from: 101, to: 150 },
              { from: 151, to: 200 },
              { from: 201, to: 250 },
              { from: 251, to: 300 },
              { from: 301, to: 350 },
              { from: 351, to: 400 },
              { from: 401, to: 450 },
              { from: 451, to: 500 },
              { from: 501 },
            ]) {
              name
              count
           }
      	}
    }
}

クエリを一つずつ見ていきましょう

ジェネリック製品ティーザー

フラグメントは、再利用可能な部分クエリです。ブロックとして考えることができます。ここでは、「GenericProductTeaser」(好きなように呼び出せます) というフラグメントを作成し、これが「GenericProduct」タイプを処理します。「GenericProduct」は Foundation サイトのコンテンツ タイプで、Graph にプッシュされています。これで、「GenericProduct」タイプから返すフィールド (プロパティ) を選択できるようになりました。Ctrl キーを押しながらスペース キー (Ctrl + スペース) をクリックすると、インテリジェンスが利用できます。

fragment GenericProductTeaser on GenericProduct {
  Name
  Code
  DefaultMarketPrice
  Brand
  DefaultImageUrl
}

クエリ名と変数

クエリに「ProductListing」という名前を付けましたが、任意の名前を付けることもできます。また、クエリにいくつかの変数を追加したので、さまざまなオプションを使用してクエリを実行できます。GraphQL クエリは、お気に入りのプログラミング言語のメソッドと考えることができます。クエリに名前を付け、好きな変数を追加します。その後、クエリ内で変数を使用できます。

  • $languages: コンテンツを取得する言語。複数の言語を指定できます。「Locales」の値は、コマース サイトで設定した言語です。
  • $searchText: 通常検索またはセマンティック検索を実行するための検索フレーズ
  • $brands: 選択したブランド
  • $sizes: 選択したサイズ
  • $minPrice: 製品を取得するための最低デフォルト価格
  • $hightPrice: 製品を取得するためのデフォルトの最高価格
  • $skip: 結果の先頭から何項目スキップするか
  • $limit: 取得する結果項目の数
  • $order: コンテンツを受信する順序。
query ProductListing(
  $languages: [Locales] = en
  $searchText: String,
  $brands: [String!],
  $sizes: [String!],
  $colors: [String!],
  $minPrice: Float,
  $maxPrice: Float,
  $skip: Int = 0,
  $limit: Int = 10,
  $order: GenericProductOrderByInput = { 
    _ranking: SEMANTIC,
  })

ジェネリック製品

コンテンツ タイプ「GenericProduct」を使用して、すべての「GenericProduct」アイテムをクエリします。これにより、タイプが「GenericProduct」であるか、「GenericProduct」から継承されているすべてのコンテンツがクエリされます。

GenericProduct(

ロケール

クエリする言語には、変数 $languages を使用します。クエリでは、$languages のデフォルト値は「en」に設定されています。

locale: $languages

どこ

コンテンツのフィルタリング。フィルタリングは値を持つ変数に対してのみ行われます。

「_or」ステートメントを使用して、変数「$searchText」をすべての検索可能なプロパティ (_fullText) および「Name」と照合します。「Name」に一致する結果を 5 倍にします。

また、$minPrice 変数と $maxPrice 変数に基づいて、「デフォルトの市場価格」をフィルタリングします。

      where:{
        _or:[
        {
          _fulltext: {
              match: $searchText
          }
        },
        {
          Name: {
            match: $searchText
            boost: 5
          }
        }
        ]
        DefaultMarketPrice: {
            gte: $minPrice
            lte: $maxPrice
        }
      }

スキップと制限

上から $skip 個のアイテムをスキップし、結果に $limit 個のアイテムを含めます。

      skip: $skip,
      limit: $limit

注文方法

受信した $order 変数に基づいて結果を並べ替えます。デフォルトでは、「セマンティック検索」ランキング (クエリのデフォルトの変数値) に基づいて結果を並べ替えます。

orderBy: $order

合計

クエリの結果の合計数

total

アイテム

アイテムは選択されたフィールド(プロパティ)を取得するために使用されています。フラグメント「GenericProductTeaser」を参照して、フラグメントで指定されたフィールド(プロパティ)を取得します。

items {
          ...GenericProductTeaser
      }

ファセット

ファセットは特定のフィールド (プロパティ) のデータを集約し、一意の値とその一意の値の「カウント」を提供します。

「ブランド」、「サイズ」、「色」、および「DefaultMarketPrice」のファセットを作成しています。最初の 3 つのファセットは単純なファセットで、各ファセットの「フィルター」パラメータを使用して、ユーザーが選択した値を指定します。最後のファセットは範囲ファセットで、さまざまな価格間隔の数を指定します。

      facets {
          Brand(filters: $brands) {
              name
              count
          }
          Sizes(filters:$sizes) {
              name
              count
          }
          Colors(filters:$colors) {
              name
              count
          }
          DefaultMarketPrice(
            ranges: [
              { to: 50 },
              { from: 51, to: 100 },
              { from: 101, to: 150 },
              { from: 151, to: 200 },
              { from: 201, to: 250 },
              { from: 251, to: 300 },
              { from: 301, to: 350 },
              { from: 351, to: 400 },
              { from: 401, to: 450 },
              { from: 451, to: 500 },
              { from: 501 },
            ]) {
              name
              count
          	}
      	}

製品詳細ページを作成する

また、「コード」を使用して商品を取得する商品詳細ページも作成します。

query ProductDetail(
  $locale: Locales = en
  $code: String!
) {
  GenericProduct(
    locale: [$locale]
    where:{
      Code: { eq: $code }
    }
    limit:1
  ) {
    items {
      Name
      Code
      DefaultImageUrl
      DefaultMarketPrice
      Brand
      LongDescription
    }
  }
}

NextJs アプリをゼロから作成します。最終結果がどうなるかを確認したい場合は、次のサイトをご覧ください。 https://github.com/jonasbergqvist/GraphCommerceIntegration/tree/main/graph-commerce-example-app

新しいNextJsアプリを作成する

npx create-next-app@latest
  • タイプスクリプト: はい
  • ESLint: はい
  • Tailwind CSS: はい
  • src/ディレクトリ: はい
  • アプリルーター: いいえ
  • defaultimport エイリアスをカスタマイズ: いいえ

依存関係を追加する

package.jsonを開いて以下を追加します

devDependencies内

    "@graphql-codegen/cli": "^5.0.2",
    "@graphql-codegen/client-preset": "^4.2.6",
    "@parcel/watcher": "^2.4.1",

依存関係

    "@apollo/client": "^3.10.4",
    "graphql": "^16.8.1",
    "html-react-parser": "^5.1.10",
    "next-range-slider": "^1.0.5",

スクリプトでは

"codegen": "graphql-codegen --watch",

すべての依存関係をインストールする

次のコマンドを実行して、すべての依存関係をインストールします。

npm install

GraphQL Codegen を構成する

GraphQL codegenは、強く型付けされたクエリとクエリ結果を提供する優れたツールです。アプリケーションでGraphのアカウントを使用するようにCodegenを構成する必要があります。

codegen.tsを作成する

アプリケーションのルートフォルダの下に「codegen.ts」という名前で新しいファイルを作成し、次の内容を追加します。

import { CodegenConfig  } from '@graphql-codegen/cli'

const config : CodegenConfig = {
    schema: "https://cg.optimizely.com/content/v2?auth={your-single-key}",
    documents: ["src/**/*.{ts,tsx}"],
    ignoreNoDocuments: true,
    generates: {
        './src/graphql/': {
            preset: 'client',
            plugins: [],
        }
    }
}

export default config

{your-single-key}をあなたのシングルキーに変更してください。Graphアカウントをお持ちでない場合は、

5m4F2pBpXPWehc3QGqFfvohgNtgYxHQOxfmKsnqhRYDpZTBU

コード生成ウォッチャーを起動する

次のコマンドを実行して、GraphQL codegen がプロジェクト内の GraphQL クエリを継続的にチェックできるようにします。

yarn codegen

1ページ編集をサポートするApollo Clientを使用する

Apollo Client は、GraphQL サポートが組み込まれた多くのクライアントの 1 つです。この例では Apollo Client を使用しますが、独自のプロジェクトでは任意のクライアントを使用できます。

apolloClient.tsx を追加

「src」フォルダの下に「apolloClient.tsx」という名前の新しいファイルを追加します。

import { ApolloClient, createHttpLink, InMemoryCache } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';

let client: ApolloClient | undefined = undefined;

if (typeof window !== "undefined" && window.location !== undefined) {
    const queryString = window?.location?.search;
    const urlParams = new URLSearchParams(queryString);
    const preview_token = urlParams.get('preview_token') ?? undefined;

    if (preview_token) {
        const httpLink = createHttpLink({
            uri: 'https://cg.optimizely.com/content/v2',
        });

        const authLink = setContext((_, { headers }) => {
            return {
                headers: {
                    ...headers,
                    authorization: `Bearer ${preview_token}`
                }
            };
        });

        client = new ApolloClient({
            link: authLink.concat(httpLink),
            cache: new InMemoryCache()
        });

        const communicationScript = document.createElement('script');
        communicationScript.src = `{url-to-your-foundation-site}/Util/javascript/communicationinjector.js`;
        communicationScript.setAttribute('data-nscript', 'afterInteractive')
        document.body.appendChild(communicationScript);
    }
}

if (client === undefined) {
    const httpLink = createHttpLink({
        uri: 'https://cg.optimizely.com/content/v2?auth={your-single-key}',
    });

    const authLink = setContext((_, { headers }) => {
        return {
            headers: {
                ...headers
            }
        };
    });

    client = new ApolloClient({
        link: authLink.concat(httpLink),
        cache: new InMemoryCache()
    });
}

export default client;

このファイルでは次の 2 つの点を変更する必要があります。

  • {url-to-your-foundation-site}をFoundationサイトを実行するURLに変更します。例: https://localhost:44397
  • {your-single-key} を Graph アカウントのシングルキーに置き換えます。Graph アカウントがない場合は 5m4F2pBpXPWehc3QGqFfvohgNtgYxHQOxfmKsnqhRYDpZTBU を使用してください。

_app.tsxを更新してApolloクライアントを使用する

src/pages の下の_app.tsx を次のように更新します。

import "@/styles/globals.css";
import { ApolloProvider } from "@apollo/client";
import type { AppProps } from "next/app";
import client from '../apolloClient';

export default function App({ Component, pageProps }: AppProps) {
  return (
    
      
    
  );
}

GenericProductTeaserComponent.tsx を作成する

「src」の下に「components」という名前のフォルダーを追加し、「components」フォルダーに「GenericProductTeaserComponent.tsx」という名前のファイルを追加します。

import { FragmentType, graphql, useFragment } from "@/graphql"
import { Dispatch, FC, SetStateAction, useState } from "react"; 

export const GenericProductTeaserFragment = graphql(/* GraphQL */ `
    fragment GenericProductTeaser on GenericProduct {
        Name
        Code
        DefaultMarketPrice
        Brand
        DefaultImageUrl
    }
`)

interface GenericProductTeaserProps {
    GenericProductTeaser: FragmentType
    setSelectedCode: Dispatch>;
    setShowModal: Dispatch>;
}
 
const GenericProductTeaserComponent: FC = ({ GenericProductTeaser, setSelectedCode, setShowModal}) => {
    const setSelected = (event: any) => {
        if(event?.target?.id) {
            setSelectedCode(event?.target?.id)
            setShowModal(true)
        }
      }

    const item = useFragment(GenericProductTeaserFragment, GenericProductTeaser)
    const imageUrl="https://localhost:44397" + item.DefaultImageUrl
        return (
            
{項目名

${item.DefaultMarketPrice}

) } export default GenericProductTeaserComponent

GraphQL フラグメント GenericProductTeaser がファイルの先頭に追加されます。このクエリは、その後、「GenericProductTeaserComponent」で使用されます。フラグメントの GenericProduct に存在するフィールド (プロパティ) を追加することができます。フィールド/プロパティを追加してファイルを保存すると、数秒後に GenericProductTeaserComponent で使用できるようになります。

_scoreを追加してファイルを保存するテスト

    fragment GenericProductTeaser on GenericProduct {
        Name
        Code
        DefaultMarketPrice
        Brand
        DefaultImageUrl
        _score
    }

ファイルを保存してから数秒後に、GenericProductTeaserComponent 内の「item」で _score が利用できるようになります。

サイトを構築してみる

次のコマンドを実行して、すべてが機能していることを確認します。

npm run build

ProductListingComponent..tsx を作成する

商品リストページのGraphQLクエリを使用して、「コンポーネント」の下にProductListingComponent.tsxを作成します。

import React, { FC, useState } from 'react'
import { useQuery } from '@apollo/client'

import { graphql } from '@/graphql'
import GenericProductTeaserComponent from './GenericProductTeaserComponent'
 
export const ProductListing = graphql(/* GraphQL */ `
    query ProductListing(
        $languages: [Locales] = en
        $searchText: String,
        $brands: [String!],
        $sizes: [String!],
        $colors: [String!],
        $minPrice: Float,
        $maxPrice: Float,
        $skip: Int = 0,
        $limit: Int = 10,
        $order: GenericProductOrderByInput = {       
            _ranking: SEMANTIC,
            DefaultMarketPrice: ASC 
        }
        )
        {
        GenericProduct(
            locale: $languages
            where:{
                _or:[
                    {
					    _fulltext: {
                            match: $searchText
                        }
                    },
                    {
						Name: {
                            match: $searchText
                            boost: 20
                        }
                    }
                ]
                DefaultMarketPrice: {
                    gte: $minPrice
                    lte: $maxPrice
                }
            }
            skip: $skip,
            limit: $limit
            orderBy: $order
        ) {
            total
            items {
                ...GenericProductTeaser
            }
            facets {
                Brand(filters: $brands) {
                    name
                    count
                }
                Sizes(filters:$sizes) {
                    name
                    count
                }
                Colors(filters:$colors) {
                    name
                    count
                }
                DefaultMarketPrice(ranges: [
                    { to: 50 },
                    { from: 51, to: 100 },
                    { from: 101, to: 150 },
                    { from: 151, to: 200 },
                    { from: 201, to: 250 },
                    { from: 251, to: 300 },
                    { from: 301, to: 350 },
                    { from: 351, to: 400 },
                    { from: 401, to: 450 },
                    { from: 451, to: 500 },
                    { from: 501 },
                ]) {
                    name
                    count
                }
            }
        }
    }
`)
 
const ProductListingComponent: FC = () => {

    const [showModal, setShowModal] = useState(false);
    const [selectedCode, setSelectedCode] = useState(() => '');

    const { data } = useQuery(ProductListing, { 
        variables: { 

        } 
    })

    return (
        

Hits: { data?.GenericProduct?.total }

    { data?.GenericProduct?.items?.map((item, index) => { return })}
) } export default ProductListingComponent

index.tsx を更新

src/pagesフォルダのindex.tsxを以下のように更新します。

import ProductListingComponent from "@/components/ProductListingComponent";

export default function Home() {
  return (
      
  );
}

画像を機能させるために財団のウェブサイトを立ち上げる

Foundation Web サイトがまだ実行されていない場合は起動します。NextJs アプリで画像を動作させるには、これを実行する必要があります。その理由は、Graph は Commerce システム内にある実際の画像へのリンクのみを保存するためです。Foundation サイトを起動しない場合は、画像以外 (壊れた画像が表示されます) はすべて動作します。

NextJsアプリを起動する

npm run dev

閲覧時にいくつかの商品が表示されるようになりました

http://localhost:3000/

ProductDetailComponent.tsx を作成する

「components」の下に ProductDetailComponent.tsx という名前のファイルを作成します。

import React, { Dispatch, FC, SetStateAction, useEffect, useState } from 'react'
import { useQuery } from '@apollo/client'
import { graphql } from '@/graphql'
import parse from 'html-react-parser';


export const ProductDetail = graphql(/* GraphQL */ `
    query ProductDetail(
    $locale: Locales = en
    $code: String!
    ) {
    GenericProduct(
        locale: [$locale]
        where:{
        Code: { eq: $code }
        }
        limit:1
    ) {
        items {
        Name
        Code
        DefaultImageUrl
        DefaultMarketPrice
        Brand
        LongDescription
        }
    }
    }
`)


interface ProductDetailProps {
    code: string
    setOpen: Dispatch>;
}
 
const ProductDetailComponent: FC = ({code, setOpen}) => {


    const { data } = useQuery(ProductDetail, {
        variables: {
            code
        }
    })


    const item = data?.GenericProduct?.items![0]
    const imageUrl="{url-to-your-foundation-site}" + item?.DefaultImageUrl
    return (
        
{/*content*/}
{/*header*/}

{ item?.Name }

{/*body*/}
{項目名

{ parse(item?.LongDescription ?? '')}

From: {item?.Brand}

{/*footer*/}
) } export default ProductDetailComponent

{url-to-your-foundation-site}をFoundationサイトを実行するURLに変更します。例: https://localhost:44397

必要に応じて製品の詳細を読み込むために ProductListingComponent を更新します

ProductListingComponent.tsx を更新して、画像または名前をクリックしたときにモーダルで製品詳細コンポーネントを開くようにします。

前に以下を追加

            { 
                showModal ? (
                    
                ) : null
            }

ProductDetailComponentをインポートします

import ProductDetailComponent from './ProductDetailComponent'

ProductListingComponentは次のようになります。

import React, { FC, useState } from 'react'
import { useQuery } from '@apollo/client'

import { graphql } from '@/graphql'
import GenericProductTeaserComponent from './GenericProductTeaserComponent'
import ProductDetailComponent from './ProductDetailComponent'
 
export const ProductListing = graphql(/* GraphQL */ `
    query ProductListing(
        $languages: [Locales] = en
        $searchText: String,
        $brands: [String!],
        $sizes: [String!],
        $colors: [String!],
        $minPrice: Float,
        $maxPrice: Float,
        $skip: Int = 0,
        $limit: Int = 10,
        $order: GenericProductOrderByInput = {       
            _ranking: SEMANTIC,
            DefaultMarketPrice: ASC 
        }
        )
        {
        GenericProduct(
            locale: $languages
            where:{
                _or:[
                    {
					    _fulltext: {
                            match: $searchText
                        }
                    },
                    {
						Name: {
                            match: $searchText
                            boost: 20
                        }
                    }
                ]
                DefaultMarketPrice: {
                    gte: $minPrice
                    lte: $maxPrice
                }
            }
            skip: $skip,
            limit: $limit
            orderBy: $order
        ) {
            total
            items {
                ...GenericProductTeaser
            }
            facets {
                Brand(filters: $brands) {
                    name
                    count
                }
                Sizes(filters:$sizes) {
                    name
                    count
                }
                Colors(filters:$colors) {
                    name
                    count
                }
                DefaultMarketPrice(ranges: [
                    { to: 50 },
                    { from: 51, to: 100 },
                    { from: 101, to: 150 },
                    { from: 151, to: 200 },
                    { from: 201, to: 250 },
                    { from: 251, to: 300 },
                    { from: 301, to: 350 },
                    { from: 351, to: 400 },
                    { from: 401, to: 450 },
                    { from: 451, to: 500 },
                    { from: 501 },
                ]) {
                    name
                    count
                }
            }
        }
    }
`)
 
const ProductListingComponent: FC = () => {

    const [showModal, setShowModal] = useState(false);
    const [selectedCode, setSelectedCode] = useState(() => '');

    const { data } = useQuery(ProductListing, { 
        variables: { 

        } 
    })

    return (
        

Hits: { data?.GenericProduct?.total }

    { data?.GenericProduct?.items?.map((item, index) => { return })}
{ showModal ? ( ) : null }
) } export default ProductListingComponent

アプリをテストする

画像をクリックして製品の詳細を読み込んでください

TermFacetComponent.tsx を作成する

次のコードを使用して、「components」フォルダに TermFacetComponent.tsx を作成します。

import { StringFacet } from "@/graphql/graphql"
import { Dispatch, FC, SetStateAction } from "react"

interface TermFacetProps {
    headingText: string
    values: string[]
    facet: StringFacet[] | null
    setValues: Dispatch>;
}

const TermFacetComponent: FC = ({ headingText, values, facet, setValues }) => {

    const handleSelection = (event: React.ChangeEvent) => {
        let localValues = Array.from(values)
        if(event.target.checked) {
            localValues.push(event.target.id);
        }
        else {
            localValues = localValues.filter(x => x !== event.target.id);
        }
        setValues(localValues);
    };

    return (
      
    )
}

export default TermFacetComponent

用語ファセットを使用するために ProductListingComponent.tsx を更新します

ProductListingComponent.tsxを次のように更新します。

import React, { FC, useEffect, useState } from 'react'
import { useQuery } from '@apollo/client'

import { graphql } from '@/graphql'
import GenericProductTeaserComponent from './GenericProductTeaserComponent'
import ProductDetailComponent from './ProductDetailComponent'
import TermFacetComponent from './TermFacetComponent'
import { StringFacet } from '@/graphql/graphql'
 
export const ProductListing = graphql(/* GraphQL */ `
    query ProductListing(
        $languages: [Locales] = en
        $searchText: String,
        $brands: [String!],
        $sizes: [String!],
        $colors: [String!],
        $minPrice: Float,
        $maxPrice: Float,
        $skip: Int = 0,
        $limit: Int = 10,
        $order: GenericProductOrderByInput = {       
            _ranking: SEMANTIC,
            DefaultMarketPrice: ASC 
        }
        )
        {
        GenericProduct(
            locale: $languages
            where:{
                _or:[
                    {
					    _fulltext: {
                            match: $searchText
                        }
                    },
                    {
						Name: {
                            match: $searchText
                            boost: 20
                        }
                    }
                ]
                DefaultMarketPrice: {
                    gte: $minPrice
                    lte: $maxPrice
                }
            }
            skip: $skip,
            limit: $limit
            orderBy: $order
        ) {
            total
            items {
                ...GenericProductTeaser
            }
            facets {
                Brand(filters: $brands) {
                    name
                    count
                }
                Sizes(filters:$sizes) {
                    name
                    count
                }
                Colors(filters:$colors) {
                    name
                    count
                }
                DefaultMarketPrice(ranges: [
                    { to: 50 },
                    { from: 51, to: 100 },
                    { from: 101, to: 150 },
                    { from: 151, to: 200 },
                    { from: 201, to: 250 },
                    { from: 251, to: 300 },
                    { from: 301, to: 350 },
                    { from: 351, to: 400 },
                    { from: 401, to: 450 },
                    { from: 451, to: 500 },
                    { from: 501 },
                ]) {
                    name
                    count
                }
            }
        }
    }
`)
 
const ProductListingComponent: FC = () => {

    const [brands, setBrands] = useState(() => new Array());
    const [brandFacet, setBrandFacet] = useState(() => new Array())

    const [colors, setColors] = useState(() => new Array());
    const [colorFacet, setColorFacet] = useState(() => new Array())

    const [sizes, setSizes] = useState(() => new Array());
    const [sizeFacet, setSizeFacet] = useState(() => new Array())

    const [showModal, setShowModal] = useState(false);
    const [selectedCode, setSelectedCode] = useState(() => '');

    const { data } = useQuery(ProductListing, { 
        variables: { 
            brands, 
            colors, 
            sizes,
        } 
    })
    
    function facetOptionChanged(fasetQueryResult: StringFacet[], faset: StringFacet[]): boolean {
        if(fasetQueryResult.length != faset.length) {
            return true
        }

        for (let i = 0; i  {
        if(data?.GenericProduct?.facets?.Brand != undefined && data?.GenericProduct?.facets?.Brand) {
          if(facetOptionChanged(data?.GenericProduct?.facets?.Brand as StringFacet[], brandFacet)) {
            setBrandFacet(data.GenericProduct.facets?.Brand as StringFacet[])
          }
        }

        if(data?.GenericProduct?.facets?.Colors != undefined && data?.GenericProduct?.facets?.Colors) {
            if(facetOptionChanged(data?.GenericProduct?.facets?.Colors as StringFacet[], colorFacet)) {
              setColorFacet(data.GenericProduct.facets?.Colors as StringFacet[])
            }
        }

        if(data?.GenericProduct?.facets?.Sizes != undefined && data?.GenericProduct?.facets?.Sizes) {
            if(facetOptionChanged(data?.GenericProduct?.facets?.Sizes as StringFacet[], sizeFacet)) {
              setSizeFacet(data.GenericProduct.facets?.Sizes as StringFacet[])
            }
        }
      }, [brandFacet, colorFacet, sizeFacet, data?.GenericProduct?.facets]);

    return (
        

Hits: { data?.GenericProduct?.total }

    { data?.GenericProduct?.items?.map((item, index) => { return })}
{ showModal ? ( ) : null }
) } export default ProductListingComponent

アプリ内のファセットをテストする

さまざまなファセット値をクリックしてテストし、選択に基づいて正しい製品が表示されることを確認します。

RangeFacetComponent.tsx を作成する

次のコードを使用して、「コンポーネント」の下に RangeFacetComponent.tsx を作成します。

import { NumberFacet } from "@/graphql/graphql"
import React, { Dispatch, FC, SetStateAction, useEffect, useState } from "react";
import { RangeSlider } from 'next-range-slider';
import 'next-range-slider/dist/main.css';

interface RangeFacetProps {
    headingText: string
    minValue: number
    maxValue: number
    currentLowValue: number
    currentHighValue: number
    facet: NumberFacet[] | null
    setLowValue: Dispatch>;
    setHighValue: Dispatch>;
}

const RangeFacetComponent: FC = ({ headingText, minValue, maxValue, currentLowValue, currentHighValue, facet, setLowValue, setHighValue }) => {

    const [localLowValue, setLocalLowValue] = useState(() => minValue);
    const [localHighValue, setHighLocalValue] = useState(() => maxValue)

    useEffect(() => {
        const delayDebounceFn = setTimeout(() => {
          setLowValue(localLowValue)
          setHighValue(localHighValue)
        }, 500)
    
        return () => clearTimeout(delayDebounceFn)
      }, [localLowValue, localHighValue, setLowValue, setHighValue])

      const facetValues = Array.from(facet?.values() ?? []).map((x) => x.count!)
      const highestFacetCount = Math.max(...facetValues)
    return (
        
{ facet?.map((x, index) => { let hValue = Math.round((x.count! / highestFacetCount) * 12) if(Math.abs(hValue % 2) == 1) { hValue = hValue - 1 } const className = "bg-indigo-200 relative flex justify-center w-full h-" + hValue return ( ) }) }

setLocalLowValue(Number(e.target.value)), }, rightInputProps: { value: currentHighValue, onChange: (e) => setHighLocalValue(Number(e.target.value)), }, } } />

{ headingText }: {currentLowValue} - {currentHighValue}

); } export default RangeFacetComponent

ProductListingComponent を更新して価格ファセットを追加します

RangeFacetComponentと2つの「useStates」を追加します。GraphQLクエリで送信される変数には、minPriceとmaxPriceも渡す必要があります。

    const [lowPrice, setLowPrice] = useState(() => 0);
    const [highPrice, setHighPrice] = useState(() => 600)
    const { data } = useQuery(ProductListing, { 
        variables: { 
            brands, 
            colors, 
            sizes,
            minPrice: lowPrice,
            maxPrice: highPrice,
        } 
    })

RangeFacetComponentを追加した後、ProductListingComponentには次のコードが含まれるはずです。

import React, { FC, useEffect, useState } from 'react'
import { useQuery } from '@apollo/client'

import { graphql } from '@/graphql'
import GenericProductTeaserComponent from './GenericProductTeaserComponent'
import ProductDetailComponent from './ProductDetailComponent'
import TermFacetComponent from './TermFacetComponent'
import { NumberFacet, StringFacet } from '@/graphql/graphql'
import RangeFacetComponent from './RangeFacetComponent'
 
export const ProductListing = graphql(/* GraphQL */ `
    query ProductListing(
        $languages: [Locales] = en
        $searchText: String,
        $brands: [String!],
        $sizes: [String!],
        $colors: [String!],
        $minPrice: Float,
        $maxPrice: Float,
        $skip: Int = 0,
        $limit: Int = 10,
        $order: GenericProductOrderByInput = {       
            _ranking: SEMANTIC,
            DefaultMarketPrice: ASC 
        }
        )
        {
        GenericProduct(
            locale: $languages
            where:{
                _or:[
                    {
					    _fulltext: {
                            match: $searchText
                        }
                    },
                    {
						Name: {
                            match: $searchText
                            boost: 20
                        }
                    }
                ]
                DefaultMarketPrice: {
                    gte: $minPrice
                    lte: $maxPrice
                }
            }
            skip: $skip,
            limit: $limit
            orderBy: $order
        ) {
            total
            items {
                ...GenericProductTeaser
            }
            facets {
                Brand(filters: $brands) {
                    name
                    count
                }
                Sizes(filters:$sizes) {
                    name
                    count
                }
                Colors(filters:$colors) {
                    name
                    count
                }
                DefaultMarketPrice(ranges: [
                    { to: 50 },
                    { from: 51, to: 100 },
                    { from: 101, to: 150 },
                    { from: 151, to: 200 },
                    { from: 201, to: 250 },
                    { from: 251, to: 300 },
                    { from: 301, to: 350 },
                    { from: 351, to: 400 },
                    { from: 401, to: 450 },
                    { from: 451, to: 500 },
                    { from: 501 },
                ]) {
                    name
                    count
                }
            }
        }
    }
`)
 
const ProductListingComponent: FC = () => {

    const [brands, setBrands] = useState(() => new Array());
    const [brandFacet, setBrandFacet] = useState(() => new Array())

    const [colors, setColors] = useState(() => new Array());
    const [colorFacet, setColorFacet] = useState(() => new Array())

    const [sizes, setSizes] = useState(() => new Array());
    const [sizeFacet, setSizeFacet] = useState(() => new Array())

    const [lowPrice, setLowPrice] = useState(() => 0);
    const [highPrice, setHighPrice] = useState(() => 600)

    const [showModal, setShowModal] = useState(false);
    const [selectedCode, setSelectedCode] = useState(() => '');

    const { data } = useQuery(ProductListing, { 
        variables: { 
            brands, 
            colors, 
            sizes,
        } 
    })
    
    function facetOptionChanged(fasetQueryResult: StringFacet[], faset: StringFacet[]): boolean {
        if(fasetQueryResult.length != faset.length) {
            return true
        }

        for (let i = 0; i  {
        if(data?.GenericProduct?.facets?.Brand != undefined && data?.GenericProduct?.facets?.Brand) {
          if(facetOptionChanged(data?.GenericProduct?.facets?.Brand as StringFacet[], brandFacet)) {
            setBrandFacet(data.GenericProduct.facets?.Brand as StringFacet[])
          }
        }

        if(data?.GenericProduct?.facets?.Colors != undefined && data?.GenericProduct?.facets?.Colors) {
            if(facetOptionChanged(data?.GenericProduct?.facets?.Colors as StringFacet[], colorFacet)) {
              setColorFacet(data.GenericProduct.facets?.Colors as StringFacet[])
            }
        }

        if(data?.GenericProduct?.facets?.Sizes != undefined && data?.GenericProduct?.facets?.Sizes) {
            if(facetOptionChanged(data?.GenericProduct?.facets?.Sizes as StringFacet[], sizeFacet)) {
              setSizeFacet(data.GenericProduct.facets?.Sizes as StringFacet[])
            }
        }
      }, [brandFacet, colorFacet, sizeFacet, data?.GenericProduct?.facets]);

    return (
        

Hits: { data?.GenericProduct?.total }

    { data?.GenericProduct?.items?.map((item, index) => { return })}
{ showModal ? ( ) : null }
) } export default ProductListingComponent

OrderByComponent.tsx を追加する

次のコードで「components」の下にOrderByComponent.tsxを追加します。

import { OrderBy } from "@/graphql/graphql";
import { Dispatch, FC, SetStateAction, useState } from "react"

interface OrderByProps {
    orderBy: string
    setorderBy: Dispatch>;
    orderByDirection: OrderBy
    setorderByDirection: Dispatch>;
}

const OrderByComponent: FC = ({ orderBy, setorderBy, orderByDirection, setorderByDirection }) => {
    const [isOrderByInputOpen, setOrderByInputIsOpen] = useState(false);
    const [isOrderByDirectionOpen, setOrderByDirectionIsOpen] = useState(false);

    const toggleOrderByDirectionDropdown = () => {
        setOrderByDirectionIsOpen(!isOrderByDirectionOpen);
    };

    const toggleOrderByInputDropdown = () => {
        setOrderByInputIsOpen(!isOrderByInputOpen);
    };

    const orderBySemantic = () => {
        setorderBy('Semantic')
        setOrderByInputIsOpen(false);
    };

    const orderByName = () => {
        setorderBy('Name')
        setOrderByInputIsOpen(false);
        setorderByDirection(OrderBy.Desc)
    };

    const orderByPrice = () => {
        setorderBy('DefaultMarketPrice')
        setOrderByInputIsOpen(false);
    };

    const orderByBrand = () => {
        setorderBy('Brand')
        setOrderByInputIsOpen(false);
    };

    const orderAsc = () => {
        setorderByDirection(OrderBy.Asc)
        setOrderByDirectionIsOpen(false);
    };

    const orderDesc = () => {
        setorderByDirection(OrderBy.Desc)
        setOrderByDirectionIsOpen(false);
    };

    return (
        
{isOrderByInputOpen && ( )}
{isOrderByDirectionOpen && ( )}
) } export default OrderByComponent

ProductListingComponent を更新して注文を含める

ProductListingComponentは次のようになります。

import React, { FC, useEffect, useState } from 'react'
import { useQuery } from '@apollo/client'

import { graphql } from '@/graphql'
import GenericProductTeaserComponent from './GenericProductTeaserComponent'
import ProductDetailComponent from './ProductDetailComponent'
import TermFacetComponent from './TermFacetComponent'
import { GenericProductOrderByInput, NumberFacet, OrderBy, Ranking, StringFacet } from '@/graphql/graphql'
import RangeFacetComponent from './RangeFacetComponent'
import OrderByComponent from './OrderByCompontent'
 
export const ProductListing = graphql(/* GraphQL */ `
    query ProductListing(
        $languages: [Locales] = en
        $searchText: String,
        $brands: [String!],
        $sizes: [String!],
        $colors: [String!],
        $minPrice: Float,
        $maxPrice: Float,
        $skip: Int = 0,
        $limit: Int = 10,
        $order: GenericProductOrderByInput = {       
            _ranking: SEMANTIC,
            DefaultMarketPrice: ASC 
        }
        )
        {
        GenericProduct(
            locale: $languages
            where:{
                _or:[
                    {
					    _fulltext: {
                            match: $searchText
                        }
                    },
                    {
						Name: {
                            match: $searchText
                            boost: 20
                        }
                    }
                ]
                DefaultMarketPrice: {
                    gte: $minPrice
                    lte: $maxPrice
                }
            }
            skip: $skip,
            limit: $limit
            orderBy: $order
        ) {
            total
            items {
                ...GenericProductTeaser
            }
            facets {
                Brand(filters: $brands) {
                    name
                    count
                }
                Sizes(filters:$sizes) {
                    name
                    count
                }
                Colors(filters:$colors) {
                    name
                    count
                }
                DefaultMarketPrice(ranges: [
                    { to: 50 },
                    { from: 51, to: 100 },
                    { from: 101, to: 150 },
                    { from: 151, to: 200 },
                    { from: 201, to: 250 },
                    { from: 251, to: 300 },
                    { from: 301, to: 350 },
                    { from: 351, to: 400 },
                    { from: 401, to: 450 },
                    { from: 451, to: 500 },
                    { from: 501 },
                ]) {
                    name
                    count
                }
            }
        }
    }
`)
 
const ProductListingComponent: FC = () => {

    const [orderByInput, setOrderByInput] = useState(() => 'DefaultMarketPrice');
    const [orderByDirection, setOrderByDirection] = useState(() => OrderBy.Asc);

    const [brands, setBrands] = useState(() => new Array());
    const [brandFacet, setBrandFacet] = useState(() => new Array())

    const [colors, setColors] = useState(() => new Array());
    const [colorFacet, setColorFacet] = useState(() => new Array())

    const [sizes, setSizes] = useState(() => new Array());
    const [sizeFacet, setSizeFacet] = useState(() => new Array())

    const [lowPrice, setLowPrice] = useState(() => 0);
    const [highPrice, setHighPrice] = useState(() => 600)

    const [showModal, setShowModal] = useState(false);
    const [selectedCode, setSelectedCode] = useState(() => '');

    const { data } = useQuery(ProductListing, { 
        variables: { 
            brands, 
            colors, 
            sizes,
            minPrice: lowPrice,
            maxPrice: highPrice,
            order: getOrder()
        } 
    })

    function getOrder(): GenericProductOrderByInput {
        if(orderByInput === "Name") {
            return { Name: orderByDirection }
        } else if (orderByInput === "Brand") {
            return { Brand: orderByDirection }
        } else if(orderByInput === "DefaultMarketPrice") {
            return { DefaultMarketPrice: orderByDirection }
        } else {
            return { _ranking: Ranking.Semantic }
        }
    }
    
    function facetOptionChanged(fasetQueryResult: StringFacet[], faset: StringFacet[]): boolean {
        if(fasetQueryResult.length != faset.length) {
            return true
        }

        for (let i = 0; i  {
        if(data?.GenericProduct?.facets?.Brand != undefined && data?.GenericProduct?.facets?.Brand) {
          if(facetOptionChanged(data?.GenericProduct?.facets?.Brand as StringFacet[], brandFacet)) {
            setBrandFacet(data.GenericProduct.facets?.Brand as StringFacet[])
          }
        }

        if(data?.GenericProduct?.facets?.Colors != undefined && data?.GenericProduct?.facets?.Colors) {
            if(facetOptionChanged(data?.GenericProduct?.facets?.Colors as StringFacet[], colorFacet)) {
              setColorFacet(data.GenericProduct.facets?.Colors as StringFacet[])
            }
        }

        if(data?.GenericProduct?.facets?.Sizes != undefined && data?.GenericProduct?.facets?.Sizes) {
            if(facetOptionChanged(data?.GenericProduct?.facets?.Sizes as StringFacet[], sizeFacet)) {
              setSizeFacet(data.GenericProduct.facets?.Sizes as StringFacet[])
            }
        }
      }, [brandFacet, colorFacet, sizeFacet, data?.GenericProduct?.facets]);

    return (
        

Hits: { data?.GenericProduct?.total }

    { data?.GenericProduct?.items?.map((item, index) => { return })}
{ showModal ? ( ) : null }
) } export default ProductListingComponent

SearchTextComponent.tsx を作成する

次のコードを使用して、「components」の下に SearchTextComponent.tsx を作成します。

import { Dispatch, FC, SetStateAction, useState } from "react"

interface SearchTextProps {
    searchText: string
    setSearchText: Dispatch>;
}

const SearchTextComponent: FC = ({ searchText, setSearchText }) => {

    const [internalSearchText, setInternalSearchText] = useState(() => searchText);

    const handleSearchClick = (event: any) => {
        setSearchText(internalSearchText)
    };
  
    const handleSearchInput = (event: React.ChangeEvent) => {
        setInternalSearchText(event.target.value);
    };
  
    const handleSearchboxKeyDown = (event: React.KeyboardEvent) => {
      if (event.key === "Enter") {
        setSearchText(internalSearchText)  
      }
    };

    return (
      
    )
}

export default SearchTextComponent

ProductListingComponent を更新して検索ボックスを含める

ProductListingComponent.tsxを次のように更新します。

import React, { FC, useEffect, useState } from 'react'
import { useQuery } from '@apollo/client'

import { graphql } from '@/graphql'
import { GenericProductOrderByInput, NumberFacet, OrderBy, Ranking, StringFacet } from '@/graphql/graphql'
import TermFacetComponent from './TermFacetComponent'
import SearchTextComponent from './SearchTextComponent'
import GenericProductTeaserComponent from './GenericProductTeaserComponent'
import OrderByComponent from './OrderByCompontent'
import RangeFacetComponent from './RangeFacetComponent'
import ProductDetailComponent from './ProductDetailComponent'
 
export const ProductListing = graphql(/* GraphQL */ `
    query ProductListing(
        $languages: [Locales] = en
        $searchText: String,
        $brands: [String!],
        $sizes: [String!],
        $colors: [String!],
        $minPrice: Float,
        $maxPrice: Float,
        $skip: Int = 0,
        $limit: Int = 10,
        $order: GenericProductOrderByInput = {       
            _ranking: SEMANTIC,
            DefaultMarketPrice: ASC 
        }
        )
        {
        GenericProduct(
            locale: $languages
            where:{
                _or:[
                    {
					    _fulltext: {
                            match: $searchText
                        }
                    },
                    {
						Name: {
                            match: $searchText
                            boost: 20
                        }
                    }
                ]
                DefaultMarketPrice: {
                    gte: $minPrice
                    lte: $maxPrice
                }
            }
            skip: $skip,
            limit: $limit
            orderBy: $order
        ) {
            total
            items {
                ...GenericProductTeaser
            }
            facets {
                Brand(filters: $brands) {
                    name
                    count
                }
                Sizes(filters:$sizes) {
                    name
                    count
                }
                Colors(filters:$colors) {
                    name
                    count
                }
                DefaultMarketPrice(ranges: [
                    { to: 50 },
                    { from: 51, to: 100 },
                    { from: 101, to: 150 },
                    { from: 151, to: 200 },
                    { from: 201, to: 250 },
                    { from: 251, to: 300 },
                    { from: 301, to: 350 },
                    { from: 351, to: 400 },
                    { from: 401, to: 450 },
                    { from: 451, to: 500 },
                    { from: 501 },
                ]) {
                    name
                    count
                }
            }
        }
    }
`)
 
const ProductListingComponent: FC = () => {

    const [searchText, setSearchText] = useState(() => '');
    const [orderByInput, setOrderByInput] = useState(() => 'DefaultMarketPrice');
    const [orderByDirection, setOrderByDirection] = useState(() => OrderBy.Asc);

    const [brands, setBrands] = useState(() => new Array());
    const [brandFacet, setBrandFacet] = useState(() => new Array())

    const [colors, setColors] = useState(() => new Array());
    const [colorFacet, setColorFacet] = useState(() => new Array())

    const [sizes, setSizes] = useState(() => new Array());
    const [sizeFacet, setSizeFacet] = useState(() => new Array())

    const [lowPrice, setLowPrice] = useState(() => 0);
    const [highPrice, setHighPrice] = useState(() => 600)

    const [showModal, setShowModal] = useState(false);
    const [selectedCode, setSelectedCode] = useState(() => '');

    const { data } = useQuery(ProductListing, { 
        variables: { 
            searchText, 
            brands, 
            colors, 
            sizes,
            minPrice: lowPrice,
            maxPrice: highPrice,
            order: getOrder()
        } 
    })

    function getOrder(): GenericProductOrderByInput {
        if(orderByInput === "Name") {
            return { Name: orderByDirection }
        } else if (orderByInput === "Brand") {
            return { Brand: orderByDirection }
        } else if(orderByInput === "DefaultMarketPrice") {
            return { DefaultMarketPrice: orderByDirection }
        } else {
            return { _ranking: Ranking.Semantic }
        }
    }

    function facetOptionChanged(fasetQueryResult: StringFacet[], faset: StringFacet[]): boolean {
        if(fasetQueryResult.length != faset.length) {
            return true
        }

        for (let i = 0; i  {
        if(data?.GenericProduct?.facets?.Brand != undefined && data?.GenericProduct?.facets?.Brand) {
          if(facetOptionChanged(data?.GenericProduct?.facets?.Brand as StringFacet[], brandFacet)) {
            setBrandFacet(data.GenericProduct.facets?.Brand as StringFacet[])
          }
        }

        if(data?.GenericProduct?.facets?.Colors != undefined && data?.GenericProduct?.facets?.Colors) {
            if(facetOptionChanged(data?.GenericProduct?.facets?.Colors as StringFacet[], colorFacet)) {
              setColorFacet(data.GenericProduct.facets?.Colors as StringFacet[])
            }
        }

        if(data?.GenericProduct?.facets?.Sizes != undefined && data?.GenericProduct?.facets?.Sizes) {
            if(facetOptionChanged(data?.GenericProduct?.facets?.Sizes as StringFacet[], sizeFacet)) {
              setSizeFacet(data.GenericProduct.facets?.Sizes as StringFacet[])
            }
        }
      }, [brandFacet, colorFacet, sizeFacet, data?.GenericProduct?.facets]);

    return (
        

Hits: { data?.GenericProduct?.total }

    { data?.GenericProduct?.items?.map((item, index) => { return })}
{ showModal ? ( ) : null }
) } export default ProductListingComponent

テスト検索

「靴」を検索し、「並べ替え」ドロップダウンで「セマンティック」を選択します

2024年7月5日

#商品一覧ページ #グラフの使用

執筆者について: nipponese

Nipponese News編集部は、国内外のニュースを日本語で分かりやすくお届けします。