Files
klz-cables.com/.pnpm-store/v10/files/0f/bb777bc243aaddffd9bd84754ecb4daf7d084a4ddf4cf7f697eda668d105feeca5ad5233155f4c02e236be4c78df90ff2285b1c9751304b4db8693839720a7
Marc Mintel 5397309103
Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 20s
Build & Deploy / 🧪 QA (push) Failing after 34s
Build & Deploy / 🏗️ Build (push) Has started running
Build & Deploy / 🚀 Deploy (push) Has been cancelled
Build & Deploy / 🧪 Smoke Test (push) Has been cancelled
Build & Deploy / ⚡ Lighthouse (push) Has been cancelled
Build & Deploy / 🔔 Notify (push) Has been cancelled
fix(products): fix breadcrumbs and product filtering (backport from main)
2026-02-24 16:04:21 +01:00

346 lines
11 KiB
Plaintext

'use client';
import { jsx as _jsx } from "react/jsx-runtime";
import { formatAdminURL } from 'payload/shared';
import * as qs from 'qs-esm';
import React, { useCallback, useEffect, useReducer, useState } from 'react';
import { useDebounce } from '../../../../hooks/useDebounce.js';
import { useEffectEvent } from '../../../../hooks/useEffectEvent.js';
import { useConfig } from '../../../../providers/Config/index.js';
import { useLocale } from '../../../../providers/Locale/index.js';
import { useTranslation } from '../../../../providers/Translation/index.js';
import { ReactSelect } from '../../../ReactSelect/index.js';
import './index.scss';
import optionsReducer from './optionsReducer.js';
const baseClass = 'condition-value-relationship';
const maxResultsPerRequest = 10;
export const RelationshipFilter = props => {
const {
disabled,
field: {
admin = {},
hasMany,
relationTo
},
filterOptions,
onChange,
value
} = props;
const placeholder = 'placeholder' in admin ? admin?.placeholder : undefined;
const isSortable = admin?.isSortable;
const {
config: {
routes: {
api
}
},
getEntityConfig
} = useConfig();
const hasMultipleRelations = Array.isArray(relationTo);
const [options, dispatchOptions] = useReducer(optionsReducer, []);
const [search, setSearch] = useState('');
const debouncedSearch = useDebounce(search, 300);
const [errorLoading, setErrorLoading] = useState('');
const [hasLoadedFirstOptions, setHasLoadedFirstOptions] = useState(false);
const {
i18n,
t
} = useTranslation();
const locale = useLocale();
const relationSlugs = hasMultipleRelations ? relationTo : [relationTo];
const loadedRelationships = React.useRef(new Map(relationSlugs.map(relation => [relation, {
hasLoadedAll: false,
nextPage: 1
}])));
const addOptions = useCallback((data, relation_0) => {
const collection = getEntityConfig({
collectionSlug: relation_0
});
dispatchOptions({
type: 'ADD',
collection,
data,
hasMultipleRelations,
i18n,
relation: relation_0
});
}, [hasMultipleRelations, i18n, getEntityConfig]);
const loadOptions = useEffectEvent(async ({
abortController,
relationSlug
}) => {
const loadedRelationship = loadedRelationships.current.get(relationSlug);
if (relationSlug && !loadedRelationship.hasLoadedAll) {
const collection_0 = getEntityConfig({
collectionSlug: relationSlug
});
const fieldToSearch = collection_0?.admin?.useAsTitle || 'id';
const where = {
and: []
};
const query = {
depth: 0,
limit: maxResultsPerRequest,
locale: locale.code,
page: loadedRelationship.nextPage,
select: {
[fieldToSearch]: true
},
where
};
if (filterOptions && filterOptions?.[relationSlug]) {
query.where.and.push(filterOptions[relationSlug]);
}
if (debouncedSearch) {
query.where.and.push({
[fieldToSearch]: {
like: debouncedSearch
}
});
}
try {
const response = await fetch(formatAdminURL({
apiRoute: api,
path: `/${relationSlug}${qs.stringify(query, {
addQueryPrefix: true
})}`
}), {
credentials: 'include',
headers: {
'Accept-Language': i18n.language
},
signal: abortController.signal
});
if (response.ok) {
const data_0 = await response.json();
if (data_0.docs.length > 0) {
addOptions(data_0, relationSlug);
if (data_0.nextPage) {
loadedRelationships.current.set(relationSlug, {
hasLoadedAll: false,
nextPage: data_0.nextPage
});
} else {
loadedRelationships.current.set(relationSlug, {
hasLoadedAll: true,
nextPage: null
});
}
}
} else {
setErrorLoading(t('error:unspecific'));
}
} catch (e) {
if (!abortController.signal.aborted) {
console.error(e); // eslint-disable-line no-console
}
}
}
setHasLoadedFirstOptions(true);
});
const handleScrollToBottom = React.useCallback(() => {
const relationshipToLoad = loadedRelationships.current.entries().next().value;
if (relationshipToLoad[0] && !relationshipToLoad[1].hasLoadedAll) {
const abortController_0 = new AbortController();
void loadOptions({
abortController: abortController_0,
relationSlug: relationshipToLoad[0]
});
}
}, []);
const findOptionsByValue = useCallback(() => {
if (value) {
if (hasMany) {
if (Array.isArray(value)) {
return value.map(val => {
if (hasMultipleRelations) {
let matchedOption;
options.forEach(opt => {
if (opt.options) {
opt.options.some(subOpt => {
if (subOpt?.value == val.value) {
matchedOption = subOpt;
return true;
}
return false;
});
}
});
return matchedOption;
}
return options.find(opt_0 => opt_0.value == val);
});
}
return undefined;
}
if (hasMultipleRelations) {
let matchedOption_0;
const valueWithRelation = value;
options.forEach(opt_1 => {
if (opt_1?.options) {
opt_1.options.some(subOpt_0 => {
if (subOpt_0?.value == valueWithRelation.value) {
matchedOption_0 = subOpt_0;
return true;
}
return false;
});
}
});
return matchedOption_0;
}
return options.find(opt_2 => opt_2.value == value);
}
return undefined;
}, [hasMany, hasMultipleRelations, value, options]);
const handleInputChange = useCallback(input => {
if (input !== search) {
dispatchOptions({
type: 'CLEAR',
i18n,
required: false
});
const relationSlugs_0 = Array.isArray(relationTo) ? relationTo : [relationTo];
loadedRelationships.current = new Map(relationSlugs_0.map(relation_1 => [relation_1, {
hasLoadedAll: false,
nextPage: 1
}]));
setSearch(input);
}
}, [i18n, relationTo, search]);
const addOptionByID = useCallback(async (id, relation_2) => {
if (!errorLoading && id !== 'null' && id && relation_2) {
const response_0 = await fetch(formatAdminURL({
apiRoute: api,
path: `/${relation_2}/${id}?depth=0`
}), {
credentials: 'include',
headers: {
'Accept-Language': i18n.language
}
});
if (response_0.ok) {
const data_1 = await response_0.json();
addOptions({
docs: [data_1]
}, relation_2);
} else {
// eslint-disable-next-line no-console
console.error(t('error:loadingDocument', {
id
}));
}
}
}, [i18n, addOptions, api, errorLoading, t]);
/**
* When `relationTo` changes externally, reset the options and reload them from scratch
* The `loadOptions` dependency is a useEffectEvent which has no dependencies of its own
* This means we can safely depend on it without it triggering this effect to run
* This is useful because this effect should _only_ run when `relationTo` changes
*/
useEffect(() => {
const relations = Array.isArray(relationTo) ? relationTo : [relationTo];
loadedRelationships.current = new Map(relations.map(relation_3 => [relation_3, {
hasLoadedAll: false,
nextPage: 1
}]));
dispatchOptions({
type: 'CLEAR',
i18n,
required: false
});
setHasLoadedFirstOptions(false);
const abortControllers = [];
relations.forEach(relation_4 => {
const abortController_1 = new AbortController();
void loadOptions({
abortController: abortController_1,
relationSlug: relation_4
});
abortControllers.push(abortController_1);
});
return () => {
abortControllers.forEach(controller => {
if (controller.signal) {
try {
controller.abort();
} catch (_err) {
// swallow error
}
}
});
};
}, [i18n, relationTo, debouncedSearch, filterOptions]);
/**
* Load any other options that might exist in the value that were not loaded already
*/
useEffect(() => {
if (value && hasLoadedFirstOptions) {
if (hasMany) {
const matchedOptions = findOptionsByValue();
(matchedOptions || []).forEach((option, i) => {
if (!option) {
if (hasMultipleRelations) {
void addOptionByID(value[i].value, value[i].relationTo);
} else {
void addOptionByID(value[i], relationTo);
}
}
});
} else {
const matchedOption_1 = findOptionsByValue();
if (!matchedOption_1) {
if (hasMultipleRelations) {
const valueWithRelation_0 = value;
void addOptionByID(valueWithRelation_0.value, valueWithRelation_0.relationTo);
} else {
void addOptionByID(value, relationTo);
}
}
}
}
}, [addOptionByID, findOptionsByValue, hasMany, hasMultipleRelations, relationTo, value, hasLoadedFirstOptions]);
const classes = ['field-type', baseClass, errorLoading && 'error-loading'].filter(Boolean).join(' ');
const valueToRender = findOptionsByValue() || value;
return /*#__PURE__*/_jsx("div", {
className: classes,
children: errorLoading ? /*#__PURE__*/_jsx("div", {
className: `${baseClass}__error-loading`,
children: errorLoading
}) : /*#__PURE__*/_jsx(ReactSelect, {
disabled: disabled,
isMulti: hasMany,
isSortable: isSortable,
onChange: selected => {
if (!selected) {
onChange(null);
return;
}
if (hasMany && Array.isArray(selected)) {
onChange(selected ? selected.map(option_0 => {
if (hasMultipleRelations) {
return {
relationTo: option_0?.relationTo,
value: option_0?.value
};
}
return option_0?.value;
}) : null);
} else if (hasMultipleRelations && !Array.isArray(selected)) {
onChange({
relationTo: selected?.relationTo,
value: selected?.value
});
} else if (!Array.isArray(selected)) {
onChange(selected?.value);
}
},
onInputChange: handleInputChange,
onMenuScrollToBottom: handleScrollToBottom,
options: options,
placeholder: placeholder,
value: valueToRender
})
});
};
//# sourceMappingURL=index.js.map