diff --git a/apps/condo/domains/news/components/NewsForm/BaseNewsForm.tsx b/apps/condo/domains/news/components/NewsForm/BaseNewsForm.tsx index 8a474947cab..212ffcf6e4e 100644 --- a/apps/condo/domains/news/components/NewsForm/BaseNewsForm.tsx +++ b/apps/condo/domains/news/components/NewsForm/BaseNewsForm.tsx @@ -10,6 +10,7 @@ import { Property as IProperty, QueryAllNewsItemsArgs as IQueryAllNewsItemsArgs, } from '@app/condo/schema' +import styled from '@emotion/styled' import { Col, Form, FormInstance, notification, Row } from 'antd' import { Gutter } from 'antd/es/grid/row' import { ArgsProps } from 'antd/lib/notification' @@ -20,6 +21,7 @@ import flattenDeep from 'lodash/flattenDeep' import get from 'lodash/get' import has from 'lodash/has' import includes from 'lodash/includes' +import isArray from 'lodash/isArray' import isEmpty from 'lodash/isEmpty' import isFunction from 'lodash/isFunction' import isNull from 'lodash/isNull' @@ -37,6 +39,7 @@ import { colors } from '@open-condo/ui/dist/colors' import Input from '@condo/domains/common/components/antd/Input' import { FormWithAction } from '@condo/domains/common/components/containers/FormList' +import { GraphQlSearchInput } from '@condo/domains/common/components/GraphQlSearchInput' import { GraphQlSearchInputWithCheckAll, InputWithCheckAllProps, @@ -60,6 +63,7 @@ import { searchOrganizationProperty } from '@condo/domains/ticket/utils/clientSc import { SectionNameInput } from '@condo/domains/user/components/SectionNameInput' import { UnitNameInput, UnitNameInputOption } from '@condo/domains/user/components/UnitNameInput' + type FormWithActionChildrenProps = ComponentProps['children']> type ActionBarProps = Pick @@ -87,8 +91,13 @@ export type BaseNewsFormProps = { OnCompletedMsg: (INewsItem) => ArgsProps | null, allNews: INewsItem[], actionName: ActionNameProps, + totalProperties: number } +const HiddenBlock = styled.div<{ hide?: boolean }>` + ${({ hide }) => hide ? 'display: none;' : ''} +` + //TODO(DOMA-6846) wrap form label with 0 margin and use default spacing (details in 6613 pr) const NO_RESIZE_STYLE: React.CSSProperties = { resize: 'none' } const FLEX_START_STYLE: React.CSSProperties = { alignItems: 'flex-start' } @@ -284,6 +293,7 @@ export const BaseNewsForm: React.FC = ({ OnCompletedMsg, allNews, actionName, + totalProperties, }) => { const intl = useIntl() const TypeLabel = intl.formatMessage({ id: 'news.fields.type.label' }) @@ -524,7 +534,7 @@ export const BaseNewsForm: React.FC = ({ const propertyCheckboxChange = (form) => { return (value) => { if (value) setSelectedPropertiesId(selectedPropertiesId => { - if (countPropertiesAvaliableToSelect.current === 1 && selectedPropertiesId.length === 1) + if (countPropertiesAvaliableToSelect.current === 1 && selectedPropertiesId.length === 1) return selectedPropertiesId if (countPropertiesAvaliableToSelect.current === 1 && selectedPropertiesId.length === 0 && has(onlyPropertyThatCanBeSelected, 'current.value')) { return [onlyPropertyThatCanBeSelected.current.value] @@ -551,7 +561,7 @@ export const BaseNewsForm: React.FC = ({ required: true, placeholder: SelectAddressPlaceholder, onChange: (propIds: string[]) => { - setSelectedPropertiesId(propIds) + setSelectedPropertiesId(!isArray(propIds) ? [propIds].filter(Boolean) : propIds) form.setFieldsValue({ 'unitNames': [] }) form.setFieldsValue({ 'sectionIds': [] }) setSelectedUnitNameKeys([]) @@ -594,6 +604,7 @@ export const BaseNewsForm: React.FC = ({ title: selectedTitle, body: selectedBody, properties: selectedPropertiesId, + ...(totalProperties === 1 && selectedPropertiesId.length === 1 ? { property: selectedPropertiesId[0] } : undefined), validBefore: initialValidBefore ? dayjs(initialValidBefore) : null, sendAt: initialSendAt ? dayjs(initialSendAt) : null, } @@ -848,6 +859,26 @@ export const BaseNewsForm: React.FC = ({ }, }), [ProfanityInBody, ProfanityInTitle]) + const newsItemForOneProperty = totalProperties === 1 && initialPropertyIds.length < 2 + const propertyIsAutoFilled = useRef(false) + const handleAllPropertiesLoading = useCallback((form: FormInstance) => (data) => { + if (!isEmpty(initialValues)) return + if (!newsItemForOneProperty) return + if (data.length !== 1) return + if (propertyIsAutoFilled.current) return + + propertyIsAutoFilled.current = true + const propertyId = get(data, '0.value') + if (propertyId) { + setSelectedPropertiesId([propertyId]) + form.setFieldsValue({ + property: propertyId, + 'properties': [propertyId], + hasAllProperties: true, + }) + } + }, [initialValues, newsItemForOneProperty]) + return ( @@ -1034,16 +1065,32 @@ export const BaseNewsForm: React.FC = ({ {SelectAddressLabel} - + { + newsItemForOneProperty && ( + + + + ) + } + + + { isOnlyOnePropertySelected && ( diff --git a/apps/condo/domains/news/components/NewsForm/CreateNewsForm.tsx b/apps/condo/domains/news/components/NewsForm/CreateNewsForm.tsx index dde023a9ab4..e20de965323 100644 --- a/apps/condo/domains/news/components/NewsForm/CreateNewsForm.tsx +++ b/apps/condo/domains/news/components/NewsForm/CreateNewsForm.tsx @@ -13,6 +13,7 @@ import { ActionBar, Button, Typography } from '@open-condo/ui' import LoadingOrErrorPage from '@condo/domains/common/components/containers/LoadingOrErrorPage' import { NewsItem, NewsItemTemplate } from '@condo/domains/news/utils/clientSchema' +import { Property } from '@condo/domains/property/utils/clientSchema' import { BaseNewsForm, BaseNewsFormProps } from './BaseNewsForm' @@ -138,6 +139,14 @@ export const CreateNewsForm: React.FC = () => { }, }) + const { + loading: totalPropertiesLoading, + count: totalProperties, + error: totalPropertiesError, + } = Property.useCount({ + where: { organization: { id: organizationId } }, + }) + const dateStart = dayjs().startOf('day') const { loading: isNewsFetching, @@ -173,8 +182,8 @@ export const CreateNewsForm: React.FC = () => { ) }, [intl, softDeleteNewsItem]) - const error = useMemo(() => newsItemTemplatesError || allNewsError, [allNewsError, newsItemTemplatesError]) - const loading = isNewsFetching || isNewsItemTemplatesFetching + const error = useMemo(() => newsItemTemplatesError || allNewsError || totalPropertiesError, [allNewsError, newsItemTemplatesError, totalPropertiesError]) + const loading = isNewsFetching || isNewsItemTemplatesFetching || totalPropertiesLoading if (loading || error) { return ( @@ -194,6 +203,7 @@ export const CreateNewsForm: React.FC = () => { OnCompletedMsg={OnCompletedMsg} allNews={allNews} actionName='create' + totalProperties={totalProperties} /> ) } diff --git a/apps/condo/domains/news/components/NewsForm/ResendNewsForm.tsx b/apps/condo/domains/news/components/NewsForm/ResendNewsForm.tsx index e01916de1f5..85316c8f8da 100644 --- a/apps/condo/domains/news/components/NewsForm/ResendNewsForm.tsx +++ b/apps/condo/domains/news/components/NewsForm/ResendNewsForm.tsx @@ -32,11 +32,11 @@ export const ResendNewsForm: React.FC = ({ id }) => { }, [createNewsItem]) const { - loading: newsItemLoading, - obj: newsItem, - error: newsItemError, - } = NewsItem.useObject({ - where: { id }, + loading: totalPropertiesLoading, + count: totalProperties, + error: totalPropertiesError, + } = Property.useCount({ + where: { organization: { id: organizationId } }, }) const { @@ -51,6 +51,14 @@ export const ResendNewsForm: React.FC = ({ id }) => { }, }) + const { + loading: newsItemLoading, + obj: newsItem, + error: newsItemError, + } = NewsItem.useObject({ + where: { id }, + }) + const selectedPropertiesId = useMemo(() => { return uniq(newsItemScopes.filter(item => has(item, ['property', 'id'])).map(item => item.property.id)) }, [newsItemScopes]) @@ -61,11 +69,11 @@ export const ResendNewsForm: React.FC = ({ id }) => { const sendPeriod: SendPeriodType = useMemo(() => { return get(newsItem, 'sendAt', null) ? 'later' : 'now' }, [newsItem]) + const sendAt = useMemo(() => get(newsItem, 'sendAt', null), [newsItem]) + const validBefore = useMemo(() => get(newsItem, 'validBefore', null), [newsItem]) const hasAllProperties = useMemo(() => { return newsItemScopes.filter((scope) => scope.property === null && scope.unitType === null && scope.unitName === null).length > 0 }, [newsItemScopes]) - const sendAt = useMemo(() => get(newsItem, 'sendAt', null), [newsItem]) - const validBefore = useMemo(() => get(newsItem, 'validBefore', null), [newsItem]) const initialValues = useMemo(() => ({ ...newsItem, newsItemScopes: newsItemScopes, @@ -76,6 +84,18 @@ export const ResendNewsForm: React.FC = ({ id }) => { validBefore: validBefore ? validBefore : null, }), [hasAllProperties, newsItem, newsItemScopes, properties, sendAt, sendPeriod, validBefore]) + const dateStart = dayjs().startOf('day') + const { + loading: isNewsFetching, + objs: allNews, + error: allNewsError, + } = NewsItem.useAllObjects({ + where: { + organization: { id: organizationId }, + createdAt_gte: dateStart.toISOString(), + }, + }) + const { loading: isNewsItemTemplatesFetching, objs: newsItemTemplates, @@ -89,18 +109,6 @@ export const ResendNewsForm: React.FC = ({ id }) => { }, }) - const dateStart = dayjs().startOf('day') - const { - loading: isNewsFetching, - objs: allNews, - error: allNewsError, - } = NewsItem.useAllObjects({ - where: { - organization: { id: organizationId }, - createdAt_gte: dateStart.toISOString(), - }, - }) - const templates = isNewsItemTemplatesFetching ? null : newsItemTemplates .reduce((acc, template) => { acc[template.id] = { @@ -117,11 +125,11 @@ export const ResendNewsForm: React.FC = ({ id }) => { }, [intl, softDeleteNewsItem]) const error = useMemo( - () => newsItemError || newsItemScopeError || newsItemTemplatesError || allNewsError, - [allNewsError, newsItemError, newsItemScopeError, newsItemTemplatesError]) + () => newsItemError || newsItemScopeError || newsItemTemplatesError || allNewsError || totalPropertiesError, + [allNewsError, newsItemError, newsItemScopeError, newsItemTemplatesError, totalPropertiesError]) const loading = useMemo( - () => propertiesLoading || newsItemLoading || !newsItemScopeAllDataLoaded || isNewsFetching || isNewsItemTemplatesFetching, - [isNewsFetching, isNewsItemTemplatesFetching, newsItemLoading, newsItemScopeAllDataLoaded, propertiesLoading]) + () => propertiesLoading || newsItemLoading || !newsItemScopeAllDataLoaded || isNewsFetching || isNewsItemTemplatesFetching || totalPropertiesLoading, + [isNewsFetching, isNewsItemTemplatesFetching, newsItemLoading, newsItemScopeAllDataLoaded, propertiesLoading, totalPropertiesLoading]) if (loading || error) { return ( @@ -143,6 +151,7 @@ export const ResendNewsForm: React.FC = ({ id }) => { OnCompletedMsg={OnCompletedMsg} allNews={allNews} actionName='create' + totalProperties={totalProperties} /> ) } diff --git a/apps/condo/domains/news/components/NewsForm/UpdateNewsForm.tsx b/apps/condo/domains/news/components/NewsForm/UpdateNewsForm.tsx index 102d37c8b27..7f51d5dbb4d 100644 --- a/apps/condo/domains/news/components/NewsForm/UpdateNewsForm.tsx +++ b/apps/condo/domains/news/components/NewsForm/UpdateNewsForm.tsx @@ -102,6 +102,14 @@ export const UpdateNewsForm: React.FC = ({ id }) => { const organizationId = useMemo(() => get(newsItem, 'organization.id', null), [newsItem]) + const { + loading: totalPropertiesLoading, + count: totalProperties, + error: totalPropertiesError, + } = Property.useCount({ + where: { organization: { id: organizationId } }, + }) + const { loading: isNewsItemTemplatesFetching, objs: NewsItemTemplates, @@ -138,11 +146,11 @@ export const UpdateNewsForm: React.FC = ({ id }) => { }, { emptyTemplate: { title: EmptyTemplateTitle, body: '', type: null } }) const error = useMemo( - () => newsItemError || newsItemScopeError || allNewsError || newsItemTemplatesError, - [allNewsError, newsItemError, newsItemScopeError, newsItemTemplatesError]) + () => newsItemError || newsItemScopeError || allNewsError || newsItemTemplatesError || totalPropertiesError, + [allNewsError, newsItemError, newsItemScopeError, newsItemTemplatesError, totalPropertiesError]) const loading = useMemo( - () => propertiesLoading || newsItemLoading || !newsItemScopeAllDataLoaded || isNewsFetching || isNewsItemTemplatesFetching, - [isNewsFetching, isNewsItemTemplatesFetching, newsItemLoading, newsItemScopeAllDataLoaded, propertiesLoading]) + () => propertiesLoading || newsItemLoading || !newsItemScopeAllDataLoaded || isNewsFetching || isNewsItemTemplatesFetching || totalPropertiesLoading, + [isNewsFetching, isNewsItemTemplatesFetching, newsItemLoading, newsItemScopeAllDataLoaded, propertiesLoading, totalPropertiesLoading]) if (loading || error) { return ( @@ -165,6 +173,7 @@ export const UpdateNewsForm: React.FC = ({ id }) => { OnCompletedMsg={null} allNews={allNews} actionName='update' + totalProperties={totalProperties} /> ) } diff --git a/apps/condo/k6/src/news.test.ts b/apps/condo/k6/src/news.test.ts index 29f0c01b3fb..a249e39ca8f 100644 --- a/apps/condo/k6/src/news.test.ts +++ b/apps/condo/k6/src/news.test.ts @@ -95,18 +95,17 @@ export async function createNewsViaBrowser (data) { page.locator('[data-cy="news__create-title-input"] textarea').type('Some title here') page.locator('[data-cy="news__create-body-input"] textarea').type('Some long description here') - await page.locator('[data-cy="news__create-property-search"] .ant-select').click() - page.waitForSelector('[data-cy="search-input--option"]').isVisible() - await page.locator('[data-cy="search-input--option"]').click() page.waitForSelector('[data-cy="news__create-property-section-search"] .ant-select-selector').isVisible() await page.locator('[data-cy="news__create-property-section-search"] .ant-select-selector').click() page.keyboard.down('Enter') - for (let i = 0; i < 7; i++) { + for (let i = 0; i < 3; i++) { page.keyboard.down('ArrowDown') page.keyboard.down('Enter') } + page.waitForSelector('button.condo-btn.condo-btn-primary').isVisible() + await page.locator('button.condo-btn.condo-btn-primary').click() await page.waitForNavigation()