@ant-design/icons#EditFilled TypeScript Examples

The following examples show how to use @ant-design/icons#EditFilled. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Columns.tsx    From wildduck-ui with MIT License 5 votes vote down vote up
getAddressColumns = ({
	dataSource,
	edit,
	deleteAddress,
}: {
	dataSource: any;
	edit(value: string): void;
	deleteAddress(value: string): void;
}): any => {
	const columns = [
		{
			title: 'Address',
			dataIndex: 'address',
			filter: true,
			render: (text: string, record: GetUserAddressesResult) => (
				<a onClick={() => edit(record.id)}>
					{record.main ? <span style={{ color: 'green' }}>* </span> : null}
					{text}
				</a>
			),
		},
		{
			title: 'Name',
			dataIndex: 'name',
			filter: true,
		},
		{
			title: 'Created',
			dataIndex: 'created',
			sortable: 'date',
			align: 'center' as const,
			render: (date: string) => moment(date).format(DATE_TIME_FORMAT),
		},
		{
			title: 'Tags',
			dataIndex: 'tags',
			filter: true,
			render: (tags: string[]) => (
				<>
					{_.map(tags, (tag) => {
						return <Tag key={tag}>{tag}</Tag>;
					})}
				</>
			),
		},
		{
			title: 'Actions',
			dataIndex: 'actions',
			align: 'center' as const,
			render: (text: string, record: GetUserAddressesResult) => (
				<Space size={'middle'}>
					<Tooltip title={'Edit'}>
						<Button className='ant-btn-icon' shape='circle' onClick={() => edit(record.id)}>
							<EditFilled className={'green-color'} />
						</Button>
					</Tooltip>
					{_.get(record, 'main') ? null : (
						<Tooltip title={'Delete'}>
							<Button
								onClick={() => {
									showConfirm(
										() => deleteAddress(record.id),
										'Are you sure you want to Delete this Address ?',
									);
								}}
								className='ant-btn-icon'
								shape='circle'
							>
								<DeleteFilled className='red-color' />
							</Button>
						</Tooltip>
					)}
				</Space>
			),
		},
	];

	return getColumnsWithFilterAndSort(columns, dataSource);
}
Example #2
Source File: DkimColumns.tsx    From wildduck-ui with MIT License 5 votes vote down vote up
getDkimColumns: any = ({ dataSource, deleteDkim }: { dataSource: any; deleteDkim(id: string): void }) => {
	const columnsDkim = [
		{
			title: 'Domain',
			dataIndex: 'domain',
			filter: true,
			render: (text: string, record: GetDkimKeysResult) => <DkimDetailsLink id={record.id} name={text} />,
		},
		{
			title: 'Selector',
			filter: true,
			dataIndex: 'selector',
		},
		{
			title: 'Description',
			dataIndex: 'description',
		},
		{
			title: 'Created',
			dataIndex: 'created',
			sortable: 'date',
			align: 'center' as const,
			render: (date: string) => moment(date).format(DATE_TIME_FORMAT_AP),
		},
		{
			title: 'Actions',
			dataIndex: 'Action',
			align: 'center' as const,
			render: (text: string, record: GetDkimKeysResult) => (
				<Space>
					<Tooltip title={'View Details'}>
						<DkimDetailsLink
							id={record.id}
							name={
								<Button className='ant-btn-icon' shape='circle'>
									<EditFilled className={'green-color'}></EditFilled>
								</Button>
							}
						/>
					</Tooltip>
					<Tooltip title={'Delete'}>
						<Button
							onClick={() =>
								showConfirm(() => deleteDkim(record.id), 'Are you sure you want to Delete this DKIM ?')
							}
							className='ant-btn-icon'
							shape='circle'
						>
							<DeleteFilled className='red-color' />
						</Button>
					</Tooltip>
				</Space>
			),
		},
	];

	return getColumnsWithFilterAndSort(columnsDkim, dataSource);
}
Example #3
Source File: FiltersTable.tsx    From wildduck-ui with MIT License 5 votes vote down vote up
FiltersTable: React.FC = () => {
	const { setShowAddFilterForm, setFilterId } = useActions(filtersLogic);
	const { filtersList } = useValues(filtersLogic);

	const params: { id: string } = useParams();
	const { data, isLoading } = useFilters(params.id);
	const { mutate: deleteFilter } = useDeleteFilter();

	const columns = [
		{
			title: 'Name',
			dataIndex: 'name',
			filter: true,
			render: (name: string, record: GetFiltersResult) => {
				return {
					props: {
						style: { background: record.disabled ? '#ffcccb' : null },
					},
					children: <div>{name}</div>,
				};
			},
		},
		{
			title: 'Actions',
			dataIndex: 'action',
			align: 'center' as const,
			render: (text: string, record: GetFiltersResult) => {
				return {
					props: {
						style: { background: record.disabled ? '#ffcccb' : null },
					},
					children: (
						<Space>
							<Tooltip title={'Edit'}>
								<Button className='ant-btn-icon' shape='circle' onClick={() => setFilterId(record.id)}>
									<EditFilled className={'green-color'} />
								</Button>
							</Tooltip>
							<Tooltip title={'Delete'}>
								<Button
									onClick={() =>
										showConfirm(() => deleteFilter({ userId: params.id, filterId: record.id }))
									}
									className='ant-btn-icon'
									shape='circle'
								>
									<DeleteFilled className='red-color' />
								</Button>
							</Tooltip>
						</Space>
					),
				};
			},
		},
	];

	return (
		<>
			<Table
				size='small'
				columns={getColumnsWithFilterAndSort(columns, filtersList)}
				dataSource={data}
				pagination={_.size(filtersList) > 10 ? null : false}
				scroll={{ y: 550 }}
				loading={isLoading}
			></Table>
			<FloatingButton>
				<Tooltip title='Add New Filter'>
					<DiffOutlined
						onClick={() => {
							setShowAddFilterForm(true);
						}}
					/>
				</Tooltip>
			</FloatingButton>
		</>
	);
}
Example #4
Source File: Columns.tsx    From wildduck-ui with MIT License 5 votes vote down vote up
getForwardedAddressColumns = ({
	dataSource,
	deleteAddress,
}: {
	dataSource: any;
	deleteAddress(value: string): void;
}): any => {
	const columnsForwarded = [
		{
			title: 'Forwarded address',
			dataIndex: 'address',
			key: 'address',
			filter: true,
			render: (text: string, record: Address.IForwardedAddress) => (
				<Space size='middle'>
					<ForwardedAddressLink id={record.id} name={text} />
				</Space>
			),
		},
		{
			title: 'Name',
			dataIndex: 'name',
			key: 'name',
			filter: true,
		},
		{
			title: 'Tags',
			key: 'tags',
			dataIndex: 'tags',
			filter: true,
			align: 'center' as const,
			render: (tags: string[]) => (
				<>
					{_.map(tags, (tag) => {
						return <Tag key={tag}>{tag}</Tag>;
					})}
				</>
			),
		},
		{
			title: 'Action',
			key: 'action',
			align: 'center' as const,
			width: 100,
			render: (text: string, record: Address.IForwardedAddress) => (
				<Space size={'middle'}>
					<Tooltip title={'Edit'}>
						<ForwardedAddressLink
							id={record.id}
							name={
								<Button className='ant-btn-icon' shape='circle'>
									<EditFilled className={'green-color'} />
								</Button>
							}
						/>
					</Tooltip>
					<Tooltip title={'Delete'}>
						<Button
							onClick={() =>
								showConfirm(() => deleteAddress(record.id), 'Are you sure you want to delete?')
							}
							className='ant-btn-icon'
							shape='circle'
						>
							<DeleteFilled className='red-color' />
						</Button>
					</Tooltip>
				</Space>
			),
		},
	];

	return getColumnsWithFilterAndSort(columnsForwarded, dataSource);
}
Example #5
Source File: index.tsx    From electron-playground with MIT License 5 votes vote down vote up
Apidoc: React.FunctionComponent<IApidocProps> = props => {
  const [markdownPath, setMarkdownPath] = useState('')
  const [content, setContent] = useState('')

  const handleMenuClick = (filePath: string) => {
    const relativePath = filePath.replace(apidoc_path, '')
    import(`../../apidocs${relativePath.replace(/\\/g, '/')}`).then(res => {
      setContent('')
      setContent(res.default)
      setMarkdownPath(relativePath)
    })
  }

  // 用于menu的子菜单展开
  const defaultOpenKeys = useMemo(() => {
    return menus.filter(m => m.children?.length).map(i => i.filePath)
  }, [])

  const generateMenus = (menu: MenusInfo) => {
    const { filePath, title, children } = menu
    if (children?.length) {
      return <Menu.SubMenu title={title} key={filePath}>
        {children.map((item, index) => generateMenus(item))}
      </Menu.SubMenu>
    }
    return <Menu.Item onClick={() => handleMenuClick(filePath as string)} key={filePath}>{title}</Menu.Item>
  }

  return (
    <Layout className={style.container}>
      <Layout.Sider width={256}>
        <Menu mode="inline" className={style.menu} defaultOpenKeys={defaultOpenKeys}>
          {menus.map(i => generateMenus(i))}
        </Menu>
      </Layout.Sider>
      <Layout className={style.main}>
        <Layout.Content className={style.content}>
          {content && <Markdown content={content} />}
          {content && <p className={style.edit}>
            <EditFilled /> 对文档内容有不同意见?欢迎
            <a href={`https://github.com/tal-tech/electron-playground/edit/master/playground/apidocs${markdownPath}`}>提供修改</a>
          </p>}
        </Layout.Content>
      </Layout>
    </Layout>
  )
}
Example #6
Source File: Comment.tsx    From condo with MIT License 4 votes vote down vote up
Comment: React.FC<ICommentProps> = ({ comment, setEditableComment, deleteAction }) => {
    const intl = useIntl()
    const ConfirmDeleteTitle = intl.formatMessage({ id: 'Comments.actions.delete.confirm.title' })
    const ConfirmDeleteOkText = intl.formatMessage({ id: 'Comments.actions.delete.confirm.okText' })
    const ConfirmDeleteCancelText = intl.formatMessage({ id: 'Comments.actions.delete.confirm.cancelText' })
    const CommentDeletedText = intl.formatMessage({ id: 'Comments.deleted' })
    const MetaUpdatedText = intl.formatMessage({ id: 'Comments.meta.updated' })

    const { user } = useAuth()

    const [dateShowMode, setDateShowMode] = useState<'created' | 'updated'>('created')

    const handleDeleteComment = useCallback(() => {
        deleteAction({}, comment)
    }, [comment, deleteAction])
    const handleUpdateComment = useCallback(() => setEditableComment(comment), [comment, setEditableComment])
    const datetimeText = useMemo(() => dayjs(dateShowMode === 'created' ? comment.createdAt : comment.updatedAt).format(COMMENT_DATE_FORMAT),
        [comment.createdAt, comment.updatedAt, dateShowMode])
    const actions = useMemo(() => user.id === comment.user.id && ([
        <Popconfirm
            key="delete"
            title={ConfirmDeleteTitle}
            okText={ConfirmDeleteOkText}
            cancelText={ConfirmDeleteCancelText}
            onConfirm={handleDeleteComment}
        >
            <Button
                size="large"
                css={DeleteButtonStyle}
                icon={<DeleteFilled/>}
            />
        </Popconfirm>,
        <Button
            key="update"
            size="large"
            css={UpdateButtonStyle}
            icon={<EditFilled />}
            onClick={handleUpdateComment}
        />,
    ]), [ConfirmDeleteCancelText, ConfirmDeleteOkText, ConfirmDeleteTitle, comment.user.id, handleDeleteComment, handleUpdateComment, user.id])

    if (comment.deletedAt) {
        return (
            <Typography.Paragraph
                italic
                css={DeletedTextStyle}
            >
                {CommentDeletedText}
            </Typography.Paragraph>
        )
    }

    return (
        <AntComment
            content={
                <>
                    <Typography.Text>
                        {comment.content}
                    </Typography.Text>
                    <CommentFileList comment={comment} />
                </>
            }
            author={
                <Typography.Text type={'secondary'}>
                    <Typography.Text type={'secondary'} underline style={AUTHOR_TEXT_STYLES}>
                        {comment.user.name}
                    </Typography.Text>
                    ({getCommentAuthorRoleMessage(comment.user, intl)}),
                </Typography.Text>
            }
            datetime={
                <div
                    onMouseOut={() => setDateShowMode('created')}
                    onMouseOver={() => setDateShowMode('updated')}
                >
                    <Typography.Text title={MetaUpdatedText}>
                        {datetimeText}
                    </Typography.Text>
                </div>
            }
            actions={actions}
            css={CommentStyle}
        />
    )
}
Example #7
Source File: MultipleFileUpload.tsx    From condo with MIT License 4 votes vote down vote up
MultipleFileUpload: React.FC<IMultipleFileUploadProps> = (props) => {
    const intl = useIntl()
    const AddFileLabel = intl.formatMessage({ id: 'component.uploadlist.AddFileLabel' })
    const FileTooBigErrorMessage = intl.formatMessage({ id: 'component.uploadlist.error.FileTooBig' },
        { maxSizeInMb: MAX_UPLOAD_FILE_SIZE / (1024 * 1024) })
    const UploadFailedErrorMessage = intl.formatMessage({ id: 'component.uploadlist.error.UploadFailedErrorMessage' })
    const {
        setFilesCount,
        fileList,
        initialCreateValues,
        Model,
        onFilesChange,
        UploadButton,
        uploadProps = {},
    } = props

    const [listFiles, setListFiles] = useState<UploadListFile[]>([])

    useEffect(() => {
        const convertedFiles = convertFilesToUploadFormat(fileList)
        setListFiles(convertedFiles)
    }, [fileList])

    const createAction = Model.useCreate(initialCreateValues, (file: DBFile) => Promise.resolve(file))

    useEffect(() => {
        if (listFiles.length === 0) {
            setFilesCount(0)
        }
    }, [listFiles.length, setFilesCount])

    const options = {
        fileList: listFiles,
        multiple: true,
        onChange: (info) => {
            let fileList = [...info.fileList]
            fileList = fileList.map(file => {
                if (file.response) {
                    file.url = file.response.url
                }
                return file
            })
            setListFiles(fileList)
        },
        showUploadList: {
            showRemoveIcon: true,
            removeIcon: (file) => {
                const removeIcon = (
                    <DeleteFilled onClick={() => {
                        const { id, uid } = file
                        const fileError = get(file, 'error')
                        if (!fileError) {
                            setFilesCount(filesCount => filesCount - 1)
                        }

                        if (!id) {
                            // remove file that failed to upload from list
                            setListFiles([...listFiles].filter(file => file.uid !== uid))
                            onFilesChange({ type: 'delete', payload: file })
                            return
                        }
                        setListFiles([...listFiles].filter(file => file.id !== id))
                        onFilesChange({ type: 'delete', payload: file })
                    }} />
                )
                return removeIcon
            },
        },
        customRequest: (options: UploadRequestOption) => {
            const { onSuccess, onError } = options
            const file = options.file as UploadFile
            if (file.size > MAX_UPLOAD_FILE_SIZE) {
                const error = new Error(FileTooBigErrorMessage)
                onError(error)
                return
            }
            return createAction({ ...initialCreateValues, file }).then( dbFile  => {
                const [uploadFile] = convertFilesToUploadFormat([dbFile])
                onSuccess(uploadFile, null)
                onFilesChange({ type: 'add', payload: dbFile })
                setFilesCount(filesCount => filesCount + 1)
            }).catch(err => {
                const error = new Error(UploadFailedErrorMessage)
                console.error('Upload failed', err)
                onError(error)
            })
        },
        ...uploadProps,
    }

    return (
        <div className={'upload-control-wrapper'}>
            <Upload { ...options }>
                {
                    UploadButton || (
                        <Button
                            type={'sberDefaultGradient'}
                            secondary
                            icon={<EditFilled />}
                        >
                            {AddFileLabel}
                        </Button>
                    )
                }
            </Upload>
        </div>
    )
}
Example #8
Source File: index.tsx    From condo with MIT License 4 votes vote down vote up
ContactPageContent = ({ contact, isContactEditable, softDeleteAction }) => {
    const intl = useIntl()
    const ContactLabel = intl.formatMessage({ id:'Contact' }).toLowerCase()
    const PhoneLabel = intl.formatMessage({ id: 'Phone' })
    const AddressLabel = intl.formatMessage({ id: 'field.Address' })
    const EmailLabel = intl.formatMessage({ id: 'field.EMail' })
    const UpdateMessage = intl.formatMessage({ id: 'Edit' })
    const DeletedMessage = intl.formatMessage({ id: 'Deleted' })
    const ConfirmDeleteButtonLabel = intl.formatMessage({ id: 'Delete' })
    const ConfirmDeleteTitle = intl.formatMessage({ id: 'contact.ConfirmDeleteTitle' })
    const ConfirmDeleteMessage = intl.formatMessage({ id: 'contact.ConfirmDeleteMessage' })

    const contactId = get(contact, 'id', null)
    const contactName = get(contact, 'name')
    const contactEmail = get(contact, 'email', '')
    const contactUnitName = get(contact, 'unitName')
    const contactUnitType = get(contact, 'unitType', BuildingUnitSubType.Flat)
    const unitSuffix = contactUnitName
        ? `${intl.formatMessage({ id: `pages.condo.ticket.field.unitType.${contactUnitType}` }).toLowerCase()} ${contactUnitName}`
        : ''
    const contactAddress = `${get(contact, ['property', 'address'], DeletedMessage)} ${unitSuffix}`

    const { isSmall } = useLayoutContext()

    const deleteCallback = useCallback(() => {
        return new Promise((resolve) => {
            resolve(softDeleteAction({}, contact))
        })
    }, [softDeleteAction, contact])

    return (
        <>
            <Head>
                <title>{contactName}</title>
            </Head>
            <PageWrapper>
                <Row gutter={[0, 40]} justify={'center'}>
                    <Col xs={10} lg={3}>
                        <UserAvatar borderRadius={24}/>
                    </Col>
                    <Col xs={24} lg={20} offset={isSmall ? 0 : 1}>
                        <Row gutter={[0, 60]}>
                            <Col lg={15} xs={24}>
                                <Row gutter={[0, 40]}>
                                    <Col span={24}>
                                        <Typography.Title>
                                            {contactName}
                                        </Typography.Title>
                                        <Typography.Title
                                            level={2}
                                            style={{ margin: '8px 0 0', fontWeight: 400 }}
                                        >
                                            {ContactLabel}
                                        </Typography.Title>
                                    </Col>
                                    <Col span={24}>
                                        <FrontLayerContainer>
                                            <Row gutter={[0, 24]}>
                                                <FieldPairRow
                                                    fieldTitle={AddressLabel}
                                                    fieldValue={contactAddress}
                                                />
                                                <FieldPairRow
                                                    fieldTitle={PhoneLabel}
                                                    fieldValue={get(contact, ['phone'])}
                                                />
                                                {
                                                    contactEmail && <FieldPairRow
                                                        fieldTitle={EmailLabel}
                                                        fieldValue={get(contact, ['email'])}
                                                    />
                                                }
                                            </Row>
                                        </FrontLayerContainer>
                                    </Col>
                                    {isContactEditable && (
                                        <Col span={24}>
                                            <Space direction={'horizontal'} size={40}>
                                                <Link href={`/contact/${get(contact, 'id')}/update`}>
                                                    <Button
                                                        color={'green'}
                                                        type={'sberPrimary'}
                                                        secondary
                                                        icon={<EditFilled />}
                                                    >
                                                        {UpdateMessage}
                                                    </Button>
                                                </Link>
                                                <DeleteButtonWithConfirmModal
                                                    title={ConfirmDeleteTitle}
                                                    message={ConfirmDeleteMessage}
                                                    okButtonLabel={ConfirmDeleteButtonLabel}
                                                    action={deleteCallback}
                                                />
                                            </Space>
                                        </Col>
                                    )}
                                </Row>
                            </Col>
                            <Col xs={24} lg={8} offset={isSmall ? 0 : 1}>
                                <TicketCardList
                                    contactId={contactId}
                                />
                            </Col>
                        </Row>
                    </Col>
                </Row>
            </PageWrapper>
        </>
    )
}
Example #9
Source File: index.tsx    From condo with MIT License 4 votes vote down vote up
DivisionPageContent = ({ division, loading, columns, role }: DivisionPageContentProps) => {
    const intl = useIntl()
    const PageTitleMessage = intl.formatMessage({ id: 'pages.condo.division.id.PageTitle' }, { name: division.name })
    const NameBlankMessage = intl.formatMessage({ id: 'pages.condo.division.id.NameBlank' })
    const ResponsibleLabelMessage = intl.formatMessage({ id: 'division.field.responsible' })
    const PropertiesLabelMessage = intl.formatMessage({ id: 'division.field.properties' })
    const ExecutorsLabelMessage = intl.formatMessage({ id: 'pages.condo.division.id.ExecutorsLabel' })
    const ExecutorsEmptyTitleMessage = intl.formatMessage({ id: 'pages.condo.division.id.ExecutorsEmpty.title' })
    const ExecutorsEmptyDescriptionMessage = intl.formatMessage({ id: 'pages.condo.division.id.ExecutorsEmpty.description' })
    const ConfirmDeleteTitle = intl.formatMessage({ id: 'division.action.delete.confirm.title' })
    const ConfirmDeleteMessage = intl.formatMessage({ id: 'division.action.delete.confirm.message' })
    const DeleteDivisionLabel = intl.formatMessage({ id: 'division.action.delete.confirm.ok' })
    const UpdateTitle = intl.formatMessage({ id: 'Edit' })

    const router = useRouter()
    const { offset } = parseQuery(router.query)
    const currentPageIndex = getPageIndexFromOffset(offset, EMPLOYEE_TABLE_PAGE_SIZE)

    const handleCompleteSoftDelete = () => {
        router.push('/property/')
    }
    const softDeleteAction = useSoftDelete({}, handleCompleteSoftDelete)

    // Transform executors array to make `name` attribute required. This fixes following error:
    // TS2322: Type 'OrganizationEmployee[]' is not assignable to type 'readonly { id: any; name: any; }[]'.
    // Property 'name' is optional in type 'OrganizationEmployee' but required in type '{ id: any; name: any; }'.
    const executors = division.executors.map(executor => ({
        name: executor.name || '',
        ...executor,
    }))

    const responsible = get(division, 'responsible')

    const dataSource = useMemo(() => {
        return executors.slice(EMPLOYEE_TABLE_PAGE_SIZE * (currentPageIndex - 1), EMPLOYEE_TABLE_PAGE_SIZE * currentPageIndex)
    }, [currentPageIndex, executors])

    return (
        <>
            <Typography.Title style={{ marginBottom: '60px' }}>
                {division.name ? PageTitleMessage : NameBlankMessage}
            </Typography.Title>
            <Row gutter={[0, 20]}>
                <PageFieldRow labelSpan={5} title={ResponsibleLabelMessage} highlight>
                    <Link href={`/employee/${get(responsible, 'id')}`}>
                        <Typography.Link style={{ color: green[6], display: 'block' }}>
                            {get(responsible, 'name')}
                        </Typography.Link>
                    </Link>
                </PageFieldRow>
                <PageFieldRow labelSpan={5} title={PropertiesLabelMessage}>
                    {division.properties.map(property => (
                        <Link
                            key={property.id}
                            href={`/property/${get(property, 'id')}`}
                        >
                            <Typography.Link style={{ color: green[6], display: 'block' }}>
                                {property.name || property.address}
                            </Typography.Link>
                        </Link>
                    ))}
                </PageFieldRow>
                <Col span={24}>
                    <Typography.Title
                        level={2}
                        style={{ fontSize: '20px' }}
                    >
                        {ExecutorsLabelMessage}
                    </Typography.Title>
                </Col>
                <Col span={24}>
                    {executors.length > 0 ? (
                        <Table
                            dataSource={dataSource}
                            columns={columns}
                            loading={loading}
                            totalRows={executors.length}
                            pageSize={EMPLOYEE_TABLE_PAGE_SIZE}
                            shouldHidePaginationOnSinglePage
                        />
                    ) : (
                        <FocusContainer>
                            <BasicEmptyListView>
                                <Typography.Title level={3}>
                                    {ExecutorsEmptyTitleMessage}
                                </Typography.Title>
                                <Typography.Text style={{ width: '37ex', display: 'block' }}>
                                    {ExecutorsEmptyDescriptionMessage}
                                </Typography.Text>
                            </BasicEmptyListView>
                        </FocusContainer>
                    )}
                </Col>
            </Row>
            {
                role && role.canManageDivisions ? (
                    <ActionBar>
                        <Link href={`/division/${division.id}/update`}>
                            <span>
                                <Button
                                    color={'green'}
                                    type={'sberPrimary'}
                                    secondary
                                    icon={<EditFilled />}
                                    size={'large'}
                                >
                                    {UpdateTitle}
                                </Button>
                            </span>
                        </Link>
                        <DeleteButtonWithConfirmModal
                            title={ConfirmDeleteTitle}
                            message={ConfirmDeleteMessage}
                            okButtonLabel={DeleteDivisionLabel}
                            action={() => softDeleteAction({}, division)}
                        />
                    </ActionBar>
                ) : null
            }
        </>
    )
}
Example #10
Source File: index.tsx    From condo with MIT License 4 votes vote down vote up
EmployeePageContent = ({
    employee,
    isEmployeeEditable,
    isEmployeeReinvitable,
    updateEmployeeAction,
    softDeleteAction,
}) => {
    const intl = useIntl()
    const PhoneMessage = intl.formatMessage({ id: 'Phone' })
    const EmailMessage = intl.formatMessage({ id: 'field.EMail' })
    const UpdateMessage = intl.formatMessage({ id: 'Edit' })
    const RoleMessage = intl.formatMessage({ id: 'employee.Role' })
    const PositionMessage = intl.formatMessage({ id: 'employee.Position' })
    const SpecializationsMessage = intl.formatMessage({ id: 'employee.Specializations' })
    const BlockUserMessage = intl.formatMessage({ id: 'employee.BlockUser' })
    const CanNotBlockYourselfMessage = intl.formatMessage({ id: 'employee.CanNotBlockYourself' })
    const ConfirmDeleteButtonLabel = intl.formatMessage({ id: 'Delete' })
    const ConfirmDeleteTitle = intl.formatMessage({ id: 'employee.ConfirmDeleteTitle' })
    const ConfirmDeleteMessage = intl.formatMessage({ id: 'employee.ConfirmDeleteMessage' })

    const { user } = useAuth()
    const { isSmall } = useLayoutContext()

    const userId = get(user, 'id')
    const employeeUserId = get(employee, 'user.id')
    const isMyEmployee = userId && employeeUserId && userId === employeeUserId
    const isEmployeeBlocked = get(employee, 'isBlocked')

    const name = get(employee, 'name')
    const email = get(employee, 'email')

    const handleEmployeeBlock = (blocked) => {
        if (!isEmployeeEditable) {
            return
        }

        updateEmployeeAction({ isBlocked: blocked }, employee)
    }

    return (
        <>
            <Head>
                <title>{name}</title>
            </Head>
            <PageWrapper>
                <PageContent>
                    <Row gutter={[0, 40]} justify={'center'}>
                        <Col xs={10} lg={3}>
                            <UserAvatar borderRadius={24} isBlocked={isEmployeeBlocked}/>
                        </Col>
                        <Col xs={24} lg={20} offset={isSmall ? 0 : 1}>
                            <Row gutter={[0, 60]}>
                                <Col span={24}>
                                    <Row gutter={[0, 40]}>
                                        <Col span={24}>
                                            <Typography.Title
                                                level={1}
                                                style={{ margin: 0, fontWeight: 'bold' }}
                                            >
                                                {name}
                                            </Typography.Title>
                                            <NotDefinedField
                                                showMessage={false}
                                                value={get(employee, ['position'])}
                                                render={(value) => (
                                                    <Typography.Title
                                                        level={2}
                                                        style={{ margin: '8px 0 0', fontWeight: 400 }}
                                                    >
                                                        {value}
                                                    </Typography.Title>
                                                )}
                                            />
                                        </Col>
                                        {isEmployeeReinvitable && (
                                            <ReInviteActionAlert employee={employee} />
                                        )}
                                        {isEmployeeEditable && (
                                            <Col span={24}>
                                                <label>
                                                    <Space direction={'horizontal'} size={8}>
                                                        <Switch
                                                            onChange={handleEmployeeBlock}
                                                            defaultChecked={isEmployeeBlocked}
                                                            disabled={isMyEmployee}
                                                        />
                                                        <Typography.Text type='danger' style={{ fontSize: fontSizes.content }}>
                                                            {BlockUserMessage}
                                                        </Typography.Text>
                                                        {
                                                            (isMyEmployee) ?
                                                                <Typography.Text style={{ fontSize: fontSizes.content }}>
                                                                    {CanNotBlockYourselfMessage}
                                                                </Typography.Text>
                                                                :
                                                                null
                                                        }
                                                    </Space>
                                                </label>
                                            </Col>
                                        )}
                                        <Col span={24}>
                                            <FrontLayerContainer showLayer={isEmployeeBlocked}>
                                                <Row gutter={[0, 24]}>
                                                    <Col lg={4} xs={10}>
                                                        <Typography.Text type='secondary'>
                                                            {PhoneMessage}
                                                        </Typography.Text>
                                                    </Col>
                                                    <Col lg={18} xs={13} offset={1}>
                                                        <NotDefinedField value={get(employee, 'phone')}/>
                                                    </Col>

                                                    <Col lg={4} xs={10}>
                                                        <Typography.Text type='secondary'>
                                                            {RoleMessage}
                                                        </Typography.Text>
                                                    </Col>
                                                    <Col lg={18} xs={13} offset={1}>
                                                        <NotDefinedField
                                                            value={get(employee, ['role', 'name'])}
                                                            render={
                                                                (roleName) => (
                                                                    <Tag color='default'>{roleName}</Tag>
                                                                )
                                                            }
                                                        />
                                                    </Col>

                                                    <Col lg={4} xs={10}>
                                                        <Typography.Text type='secondary'>
                                                            {PositionMessage}
                                                        </Typography.Text>
                                                    </Col>
                                                    <Col lg={18} xs={13} offset={1}>
                                                        <NotDefinedField
                                                            value={get(employee, 'position')}
                                                            render={
                                                                (value) => (
                                                                    <Tag color='default'>{value}</Tag>
                                                                )
                                                            }
                                                        />
                                                    </Col>

                                                    <Col lg={4} xs={10}>
                                                        <Typography.Text type='secondary'>
                                                            {SpecializationsMessage}
                                                        </Typography.Text>
                                                    </Col>
                                                    <Col lg={18} xs={13} offset={1}>
                                                        <NotDefinedField
                                                            value={get(employee, 'specializations')}
                                                            render={
                                                                (specializations) => (
                                                                    <Typography.Text>
                                                                        {map(specializations, 'name').join(', ')}
                                                                    </Typography.Text>
                                                                )
                                                            }
                                                        />
                                                    </Col>
                                                    {
                                                        email && <>
                                                            <Col lg={4} xs={10}>
                                                                <Typography.Text type='secondary'>
                                                                    {EmailMessage}
                                                                </Typography.Text>
                                                            </Col>
                                                            <Col lg={18} xs={13} offset={1}>
                                                                <NotDefinedField value={email}/>
                                                            </Col>
                                                        </>
                                                    }
                                                </Row>
                                            </FrontLayerContainer>
                                        </Col>
                                        {isEmployeeEditable && (
                                            <Col span={24}>
                                                <Space direction={'horizontal'} size={40}>
                                                    <Link href={`/employee/${get(employee, 'id')}/update`}>
                                                        <Button
                                                            color={'green'}
                                                            type={'sberPrimary'}
                                                            secondary
                                                            icon={<EditFilled />}
                                                        >
                                                            {UpdateMessage}
                                                        </Button>
                                                    </Link>
                                                    {(!isMyEmployee) ?
                                                        <DeleteButtonWithConfirmModal
                                                            title={ConfirmDeleteTitle}
                                                            message={ConfirmDeleteMessage}
                                                            okButtonLabel={ConfirmDeleteButtonLabel}
                                                            action={() => softDeleteAction({}, employee)}
                                                        />
                                                        : null}
                                                </Space>
                                            </Col>
                                        )}
                                    </Row>
                                </Col>
                            </Row>
                        </Col>
                    </Row>
                </PageContent>
            </PageWrapper>
        </>
    )
}
Example #11
Source File: index.tsx    From condo with MIT License 4 votes vote down vote up
TicketAnalyticsPage: ITicketAnalyticsPage = () => {
    const intl = useIntl()
    const PageTitle = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.PageTitle' })
    const HeaderButtonTitle = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.HeaderButtonTitle' })
    const ViewModeTitle = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.ViewModeTitle' })
    const StatusFilterLabel = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.groupByFilter.Status' })
    const PropertyFilterLabel = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.groupByFilter.Property' })
    const CategoryFilterLabel = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.groupByFilter.Category' })
    const UserFilterLabel = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.groupByFilter.User' })
    const ResponsibleFilterLabel = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.groupByFilter.Responsible' })
    const AllAddresses = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.AllAddresses' })
    const ManyAddresses = intl.formatMessage({ id:'pages.condo.analytics.TicketAnalyticsPage.ManyAddresses' })
    const AllAddressTitle = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.tableColumns.AllAddresses' })
    const SingleAddress = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.SingleAddress' })
    const AllCategories = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.AllCategories' })
    const AllCategoryClassifiersTitle = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.tableColumns.AllClassifiers' })
    const AllExecutorsTitle = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.tableColumns.AllExecutors' })
    const AllAssigneesTitle = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.tableColumns.AllAssignees' })
    const EmptyCategoryClassifierTitle = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.NullReplaces.CategoryClassifier' })
    const EmptyExecutorTitle = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.NullReplaces.Executor' })
    const EmptyAssigneeTitle = intl.formatMessage({ id: 'pages.condo.analytics.TicketAnalyticsPage.NullReplaces.Assignee' })

    const TableTitle = intl.formatMessage({ id: 'Table' })
    const NotImplementedYetMessage = intl.formatMessage({ id: 'NotImplementedYet' })
    const PrintTitle = intl.formatMessage({ id: 'Print' })
    const ExcelTitle = intl.formatMessage({ id: 'Excel' })

    const router = useRouter()
    const userOrganization = useOrganization()
    const userOrganizationId = get(userOrganization, ['organization', 'id'])
    const { isSmall } = useLayoutContext()
    const filtersRef = useRef<null | ticketAnalyticsPageFilters>(null)
    const mapperInstanceRef = useRef(null)
    const ticketLabelsRef = useRef<TicketLabel[]>([])
    const [groupTicketsBy, setGroupTicketsBy] = useState<GroupTicketsByTypes>('status')
    const [viewMode, setViewMode] = useState<ViewModeTypes>('line')
    const [analyticsData, setAnalyticsData] = useState<null | TicketGroupedCounter[]>(null)
    const [excelDownloadLink, setExcelDownloadLink] = useState<null | string>(null)
    const [ticketType, setTicketType] = useState<TicketSelectTypes>('all')
    const [dateFrom, dateTo] = filtersRef.current !== null ? filtersRef.current.range : []
    const selectedPeriod = filtersRef.current !== null ? filtersRef.current.range.map(e => e.format(DATE_DISPLAY_FORMAT)).join(' - ') : ''
    const selectedAddresses = filtersRef.current !== null ? filtersRef.current.addressList : []
    const ticketTypeRef = useRef<TicketSelectTypes>('all')

    const { TicketWarningModal, setIsVisible } = useTicketWarningModal(groupTicketsBy)
    const nullReplaces = {
        categoryClassifier: EmptyCategoryClassifierTitle,
        executor: EmptyExecutorTitle,
        assignee: EmptyAssigneeTitle,
    }

    const [loadTicketAnalytics, { loading }] = useLazyQuery(TICKET_ANALYTICS_REPORT_QUERY, {
        onError: error => {
            console.log(error)
            notification.error(error)
        },
        fetchPolicy: 'network-only',
        onCompleted: response => {
            const { result: { groups, ticketLabels } } = response
            ticketLabelsRef.current = ticketLabels
            setAnalyticsData(groups)
        },
    })
    const [exportTicketAnalyticsToExcel, { loading: isXSLXLoading }] = useLazyQuery(EXPORT_TICKET_ANALYTICS_TO_EXCEL, {
        onError: error => {
            console.log(error)
            notification.error(error)
        },
        fetchPolicy: 'network-only',
        onCompleted: response => {
            const { result: { link } } = response
            setExcelDownloadLink(link)
        },
    })

    const getAnalyticsData = useCallback(() => {
        if (filtersRef.current !== null) {
            mapperInstanceRef.current = new TicketChart({
                line: {
                    chart: (viewMode, ticketGroupedCounter) => {
                        const { groupBy } = filterToQuery(
                            { filter: filtersRef.current, viewMode, ticketType, mainGroup: groupTicketsBy }
                        )
                        const data = getAggregatedData(ticketGroupedCounter, groupBy)
                        const axisLabels = Array.from(new Set(Object.values(data).flatMap(e => Object.keys(e))))
                        const legend = Object.keys(data)
                        const series = []
                        Object.entries(data).map(([groupBy, dataObj]) => {
                            series.push({
                                name: groupBy,
                                type: viewMode,
                                symbol: 'none',
                                stack: groupBy,
                                data: Object.values(dataObj),
                                emphasis: {
                                    focus: 'none',
                                    blurScope: 'none',
                                },
                            })
                        })
                        const axisData = { yAxis: { type: 'value', data: null }, xAxis: { type: 'category', data: axisLabels } }
                        const tooltip = { trigger: 'axis', axisPointer: { type: 'line' } }
                        const result = { series, legend, axisData, tooltip }
                        if (groupBy[0] === 'status') {
                            result['color'] = ticketLabelsRef.current.map(({ color }) => color)
                        }
                        return result
                    },
                    table: (viewMode, ticketGroupedCounter, restOptions) => {
                        const { groupBy } = filterToQuery(
                            { filter: filtersRef.current, viewMode, ticketType, mainGroup: groupTicketsBy }
                        )
                        const data = getAggregatedData(ticketGroupedCounter, groupBy)
                        const dataSource = []
                        const { translations, filters } = restOptions
                        const tableColumns: TableColumnsType = [
                            { title: translations['address'], dataIndex: 'address', key: 'address', sorter: (a, b) => a['address'] - b['address'] },
                            {
                                title: translations['date'],
                                dataIndex: 'date',
                                key: 'date',
                                defaultSortOrder: 'descend',
                                sorter: (a, b) => dayjs(a['date'], DATE_DISPLAY_FORMAT).unix() - dayjs(b['date'], DATE_DISPLAY_FORMAT).unix(),
                            },
                            ...Object.entries(data).map(([key]) => ({ title: key, dataIndex: key, key, sorter: (a, b) =>a[key] - b[key] })),
                        ]
                        const uniqueDates = Array.from(new Set(Object.values(data).flatMap(e => Object.keys(e))))
                        uniqueDates.forEach((date, key) => {
                            const restTableColumns = {}
                            Object.keys(data).forEach(ticketType => (restTableColumns[ticketType] = data[ticketType][date]))
                            let address = translations['allAddresses']
                            const addressList = get(filters, 'address')
                            if (addressList && addressList.length) {
                                address = addressList.join(', ')
                            }
                            dataSource.push({ key, address, date, ...restTableColumns })
                        })
                        return { dataSource, tableColumns }
                    },
                },
                bar: {
                    chart: (viewMode, ticketGroupedCounter) => {
                        const { groupBy } = filterToQuery(
                            { filter: filtersRef.current, viewMode, ticketType, mainGroup: groupTicketsBy }
                        )
                        const data = getAggregatedData(ticketGroupedCounter, groupBy, true)
                        const series = []
                        const axisLabels = Object.keys(data.summary)
                            .sort((firstLabel, secondLabel) => data.summary[firstLabel] - data.summary[secondLabel])
                        const legend = Object.keys(data)
                        Object.entries(data).map(([name, dataObj]) => {
                            const seriesData = []
                            axisLabels.forEach(axisLabel => {
                                seriesData.push(dataObj[axisLabel])
                            })
                            series.push({
                                name,
                                type: viewMode,
                                symbol: 'none',
                                stack: 'total',
                                sampling: 'sum',
                                large: true,
                                largeThreshold: 200,
                                data: seriesData,
                                emphasis: {
                                    focus: 'self',
                                    blurScope: 'self',
                                },
                            })
                        })
                        const axisData = { yAxis: { type: 'category', data: axisLabels }, xAxis: { type: 'value', data: null } }
                        const tooltip = { trigger: 'item', axisPointer: { type: 'line' }, borderColor: '#fff' }
                        const result = { series, legend, axisData, tooltip }
                        if (groupBy[0] === 'status') {
                            result['color'] = ticketLabelsRef.current.map(({ color }) => color)
                        }
                        return result
                    },
                    table: (viewMode, ticketGroupedCounter, restOptions) => {
                        const { groupBy } = filterToQuery(
                            { filter: filtersRef.current, viewMode, ticketType, mainGroup: groupTicketsBy }
                        )
                        const data = getAggregatedData(ticketGroupedCounter, groupTicketsBy === 'status' ? groupBy.reverse() : groupBy)
                        const { translations, filters } = restOptions
                        const dataSource = []
                        const tableColumns: TableColumnsType = [
                            { title: translations['address'], dataIndex: 'address', key: 'address', sorter: (a, b) => a['address'] - b['address'] },
                            ...Object.entries(data).map(([key]) => ({ title: key, dataIndex: key, key, sorter: (a, b) => a[key] - b[key] })),
                        ]

                        if (TICKET_REPORT_TABLE_MAIN_GROUP.includes(groupBy[1])) {
                            tableColumns.unshift({
                                title: translations[groupBy[1]],
                                dataIndex: groupBy[1],
                                key: groupBy[1],
                                sorter: (a, b) => a[groupBy[1]] - b[groupBy[1]],
                            })
                        }

                        const restTableColumns = {}
                        const addressList = get(filters, 'address', [])
                        const aggregateSummary = [addressList, get(filters, groupBy[1], [])]
                            .every(filterList => filterList.length === 0)
                        if (aggregateSummary) {
                            Object.entries(data).forEach((rowEntry) => {
                                const [ticketType, dataObj] = rowEntry
                                const counts = Object.values(dataObj) as number[]
                                restTableColumns[ticketType] = sum(counts)
                            })
                            dataSource.push({
                                key: 0,
                                address: translations['allAddresses'],
                                categoryClassifier: translations['allCategoryClassifiers'],
                                executor: translations['allExecutors'],
                                assignee: translations['allAssignees'],
                                ...restTableColumns,
                            })
                        } else {
                            const mainAggregation = TICKET_REPORT_TABLE_MAIN_GROUP.includes(groupBy[1]) ? get(filters, groupBy[1], []) : null
                            // TODO(sitozzz): find clean solution for aggregation by 2 id_in fields
                            if (mainAggregation === null) {
                                addressList.forEach((address, key) => {
                                    const tableRow = { key, address }
                                    Object.entries(data).forEach(rowEntry => {
                                        const [ticketType, dataObj] = rowEntry
                                        const counts = Object.entries(dataObj)
                                            .filter(obj => obj[0] === address).map(e => e[1]) as number[]
                                        tableRow[ticketType] = sum(counts)
                                    })
                                    dataSource.push(tableRow)
                                })
                            } else {
                                mainAggregation.forEach((aggregateField, key) => {
                                    const tableRow = { key, [groupBy[1]]: aggregateField }
                                    tableRow['address'] = addressList.length
                                        ? addressList.join(', ')
                                        : translations['allAddresses']
                                    Object.entries(data).forEach(rowEntry => {
                                        const [ticketType, dataObj] = rowEntry
                                        const counts = Object.entries(dataObj)
                                            .filter(obj => obj[0] === aggregateField).map(e => e[1]) as number[]
                                        tableRow[ticketType] = sum(counts)
                                    })
                                    dataSource.push(tableRow)
                                })
                            }
                        }
                        return { dataSource, tableColumns }
                    },
                },
                pie: {
                    chart: (viewMode, ticketGroupedCounter) => {
                        const { groupBy } = filterToQuery(
                            { filter: filtersRef.current, viewMode, ticketType, mainGroup: groupTicketsBy }
                        )
                        const data = getAggregatedData(ticketGroupedCounter, groupBy)
                        const series = []

                        const legend = [...new Set(Object.values(data).flatMap(e => Object.keys(e)))]
                        Object.entries(data).forEach(([label, groupObject]) => {
                            const chartData = Object.entries(groupObject)
                                .map(([name, value]) => ({ name, value }))
                            if (chartData.map(({ value }) => value).some(value => value > 0)) {
                                series.push({
                                    name: label,
                                    data: chartData,
                                    selectedMode: false,
                                    type: viewMode,
                                    radius: [60, 120],
                                    center: ['25%', '50%'],
                                    symbol: 'none',
                                    emphasis: {
                                        focus: 'self',
                                        blurScope: 'self',
                                    },
                                    labelLine: { show: false },
                                    label: {
                                        show: true,
                                        fontSize: fontSizes.content,
                                        overflow: 'none',
                                        formatter: [
                                            '{value|{b}} {percent|{d} %}',
                                        ].join('\n'),
                                        rich: {
                                            value: {
                                                fontSize: fontSizes.content,
                                                align: 'left',
                                                width: 100,
                                            },
                                            percent: {
                                                align: 'left',
                                                fontWeight: 700,
                                                fontSize: fontSizes.content,
                                                width: 40,
                                            },
                                        },
                                    },
                                    labelLayout: (chart) =>  {
                                        const { dataIndex, seriesIndex } = chart
                                        const elementYOffset = 25 * dataIndex
                                        const yOffset = 75 + 250 * Math.floor(seriesIndex / 2) + 10 + elementYOffset
                                        return {
                                            x: 340,
                                            y: yOffset,
                                            align: 'left',
                                            verticalAlign: 'top',
                                        }
                                    },
                                })
                            }
                        })
                        return { series, legend, color: ticketLabelsRef.current.map(({ color }) => color) }
                    },
                    table: (viewMode, ticketGroupedCounter, restOptions) => {
                        const { groupBy } = filterToQuery(
                            { filter: filtersRef.current, viewMode, ticketType, mainGroup: groupTicketsBy }
                        )
                        const data = getAggregatedData(ticketGroupedCounter, groupBy.reverse())
                        const { translations, filters } = restOptions
                        const dataSource = []
                        const tableColumns: TableColumnsType = [
                            { title: translations['address'], dataIndex: 'address', key: 'address', sorter: (a, b) => a['address'] - b['address'] },
                            ...Object.entries(data).map(([key]) => ({ title: key, dataIndex: key, key, sorter: (a, b) => a[key] - b[key] })),
                        ]

                        if (TICKET_REPORT_TABLE_MAIN_GROUP.includes(groupBy[1])) {
                            tableColumns.unshift({
                                title: translations[groupBy[1]],
                                dataIndex: groupBy[1],
                                key: groupBy[1],
                                sorter: (a, b) => a[groupBy[1]] - b[groupBy[1]],
                            })
                        }

                        const restTableColumns = {}
                        const addressList = get(filters, 'address', [])
                        const aggregateSummary = [addressList, get(filters, groupBy[1], [])]
                            .every(filterList => filterList.length === 0)
                        if (aggregateSummary) {
                            const totalCount = Object.values(data)
                                .reduce((prev, curr) => prev + sum(Object.values(curr)), 0)

                            Object.entries(data).forEach((rowEntry) => {
                                const [ticketType, dataObj] = rowEntry
                                const counts = Object.values(dataObj) as number[]
                                restTableColumns[ticketType] = totalCount > 0
                                    ? ((sum(counts) / totalCount) * 100).toFixed(2) + ' %'
                                    : totalCount
                            })
                            dataSource.push({
                                key: 0,
                                address: translations['allAddresses'],
                                categoryClassifier: translations['allCategoryClassifiers'],
                                executor: translations['allExecutors'],
                                assignee: translations['allAssignees'],
                                ...restTableColumns,
                            })
                        } else {
                            const totalCounts = {}
                            Object.values(data).forEach((dataObj) => {
                                Object.entries(dataObj).forEach(([aggregationField, count]) => {
                                    if (get(totalCounts, aggregationField, false)) {
                                        totalCounts[aggregationField] += count
                                    } else {
                                        totalCounts[aggregationField] = count
                                    }
                                })
                            })
                            const mainAggregation = TICKET_REPORT_TABLE_MAIN_GROUP.includes(groupBy[1]) ? get(filters, groupBy[1], []) : null
                            // TODO(sitozzz): find clean solution for aggregation by 2 id_in fields
                            if (mainAggregation === null) {
                                addressList.forEach((address, key) => {
                                    const tableRow = { key, address }
                                    Object.entries(data).forEach(rowEntry => {
                                        const [ticketType, dataObj] = rowEntry
                                        const counts = Object.entries(dataObj)
                                            .filter(obj => obj[0] === address).map(e => e[1]) as number[]
                                        const totalPropertyCount = sum(counts)
                                        tableRow[ticketType] = totalCounts[address] > 0
                                            ? (totalPropertyCount / totalCounts[address] * 100).toFixed(2) + ' %'
                                            : totalCounts[address]
                                    })
                                    dataSource.push(tableRow)
                                })
                            } else {
                                mainAggregation.forEach((aggregateField, key) => {
                                    const tableRow = { key, [groupBy[1]]: aggregateField }
                                    tableRow['address'] = addressList.length
                                        ? addressList.join(', ')
                                        : translations['allAddresses']
                                    Object.entries(data).forEach(rowEntry => {
                                        const [ticketType, dataObj] = rowEntry
                                        const counts = Object.entries(dataObj)
                                            .filter(obj => obj[0] === aggregateField).map(e => e[1]) as number[]
                                        const totalPropertyCount = sum(counts)
                                        tableRow[ticketType] = totalCounts[aggregateField] > 0
                                            ? (totalPropertyCount / totalCounts[aggregateField] * 100).toFixed(2) + ' %'
                                            : totalCounts[aggregateField]
                                    })
                                    dataSource.push(tableRow)
                                })
                            }
                        }
                        return { dataSource, tableColumns }
                    },
                },
            })
            const { AND, groupBy } = filterToQuery(
                { filter: filtersRef.current, viewMode, ticketType: ticketTypeRef.current, mainGroup: groupTicketsBy }
            )

            const where = { organization: { id: userOrganizationId }, AND }
            loadTicketAnalytics({ variables: { data: { groupBy, where, nullReplaces } } })
        }
    }, [userOrganizationId, viewMode, groupTicketsBy])

    useEffect(() => {
        const queryParams = getQueryParams()
        setGroupTicketsBy(get(queryParams, 'groupTicketsBy', 'status'))
        setViewMode(get(queryParams, 'viewMode', 'line'))
    }, [])

    useEffect(() => {
        ticketTypeRef.current = ticketType
        setAnalyticsData(null)
        getAnalyticsData()
    }, [groupTicketsBy, userOrganizationId, ticketType, viewMode])

    // Download excel file when file link was created
    useEffect(() => {
        if (excelDownloadLink !== null && !isXSLXLoading) {
            const link = document.createElement('a')
            link.href = excelDownloadLink
            link.target = '_blank'
            link.hidden = true
            document.body.appendChild(link)
            link.click()
            link.parentNode.removeChild(link)
            setExcelDownloadLink(null)
        }
    }, [excelDownloadLink, isXSLXLoading])

    const printPdf = useCallback(
        () => {
            let currentFilter
            switch (groupTicketsBy) {
                case 'property':
                    currentFilter = filtersRef.current.addressList
                    break
                case 'categoryClassifier':
                    currentFilter = filtersRef.current.classifierList
                    break
                case 'executor':
                    currentFilter = filtersRef.current.executorList
                    break
                case 'assignee':
                    currentFilter = filtersRef.current.responsibleList
                    break
                default:
                    currentFilter = null
            }

            const uniqueDataSets = Array.from(new Set(analyticsData.map(ticketCounter => ticketCounter[groupTicketsBy])))
            const isPdfAvailable = currentFilter === null
                || uniqueDataSets.length < MAX_FILTERED_ELEMENTS
                || (currentFilter.length !== 0 && currentFilter.length < MAX_FILTERED_ELEMENTS)
            if (isPdfAvailable) {
                router.push(router.route + '/pdf?' + qs.stringify({
                    dateFrom: dateFrom.toISOString(),
                    dateTo: dateTo.toISOString(),
                    groupBy: groupTicketsBy,
                    ticketType,
                    viewMode,
                    addressList: JSON.stringify(filtersRef.current.addressList),
                    executorList: JSON.stringify(filtersRef.current.executorList),
                    assigneeList: JSON.stringify(filtersRef.current.responsibleList),
                    categoryClassifierList: JSON.stringify(filtersRef.current.classifierList),
                    specification: filtersRef.current.specification,
                }))
            } else {
                setIsVisible(true)
            }
        },
        [ticketType, viewMode, dateFrom, dateTo, groupTicketsBy, userOrganizationId, analyticsData],
    )

    const downloadExcel = useCallback(
        () => {
            const { AND, groupBy } = filterToQuery({ filter: filtersRef.current, viewMode, ticketType, mainGroup: groupTicketsBy })
            const where = { organization: { id: userOrganizationId }, AND }
            const filters = filtersRef.current
            const translates: ExportTicketAnalyticsToExcelTranslates = {
                property: filters.addressList.length
                    ? filters.addressList.map(({ value }) => value).join('@')
                    : AllAddressTitle,
                categoryClassifier: filters.classifierList.length
                    ? filters.classifierList.map(({ value }) => value).join('@')
                    : AllCategoryClassifiersTitle,
                executor: filters.executorList.length
                    ? filters.executorList.map(({ value }) => value).join('@')
                    : AllExecutorsTitle,
                assignee: filters.responsibleList.length
                    ? filters.responsibleList.map(({ value }) => value).join('@')
                    : AllAssigneesTitle,
            }

            exportTicketAnalyticsToExcel({ variables: { data: { groupBy, where, translates, nullReplaces } } })
        },
        [ticketType, viewMode, dateFrom, dateTo, groupTicketsBy, userOrganizationId],
    )
    const onFilterChange: ITicketAnalyticsPageFilterProps['onChange'] = useCallback((filters) => {
        setAnalyticsData(null)
        filtersRef.current = filters
        getAnalyticsData()
    }, [viewMode, userOrganizationId, groupTicketsBy, dateFrom, dateTo])

    let addressFilterTitle = selectedAddresses.length === 0 ? AllAddresses : `${SingleAddress} «${selectedAddresses[0].value}»`
    if (selectedAddresses.length > 1) {
        addressFilterTitle = ManyAddresses
    }

    const onTabChange = useCallback((key: GroupTicketsByTypes) => {
        setGroupTicketsBy((prevState) => {
            if (prevState !== key) {
                setAnalyticsData(null)
                return key
            } else { 
                return prevState
            }
        })
        if (key === 'status') {
            setViewMode('line')
        } else {
            setViewMode('bar')
        }
    }, [viewMode, groupTicketsBy])

    const isControlsDisabled = loading || isXSLXLoading || filtersRef.current === null

    return <>
        <Head>
            <title>{PageTitle}</title>
        </Head>
        <PageWrapper>
            <PageContent>
                <Row gutter={[0, 40]}>
                    <Col xs={24} sm={18}>
                        <PageHeader title={<Typography.Title>{PageTitle}</Typography.Title>} />
                    </Col>
                    <Col span={6} hidden={isSmall}>
                        <Tooltip title={NotImplementedYetMessage}>
                            <Button icon={<PlusCircleFilled />} type='sberPrimary' secondary>{HeaderButtonTitle}</Button>
                        </Tooltip>
                    </Col>
                </Row>
                <Row gutter={[0, 24]} align={'top'} justify={'space-between'}>
                    <Col span={24}>
                        <Tabs
                            css={tabsCss}
                            defaultActiveKey='status'
                            activeKey={groupTicketsBy}
                            onChange={onTabChange}
                        >
                            <Tabs.TabPane key='status' tab={StatusFilterLabel} />
                            <Tabs.TabPane key='property' tab={PropertyFilterLabel} />
                            <Tabs.TabPane key='categoryClassifier' tab={CategoryFilterLabel} />
                            <Tabs.TabPane key='executor' tab={UserFilterLabel} />
                            <Tabs.TabPane key='assignee' tab={ResponsibleFilterLabel} />
                        </Tabs>
                    </Col>
                    <Col span={24}>
                        <Row justify={'space-between'} gutter={[0, 20]}>
                            <Col span={24}>
                                <TicketAnalyticsPageFilter
                                    onChange={onFilterChange}
                                    viewMode={viewMode}
                                    groupTicketsBy={groupTicketsBy}
                                />
                            </Col>
                            <Col span={24}>
                                <Divider/>
                            </Col>
                            <Col xs={24} lg={16}>
                                <Typography.Title level={3}>
                                    {ViewModeTitle} {selectedPeriod} {addressFilterTitle} {AllCategories}
                                </Typography.Title>
                            </Col>
                            <Col xs={12} lg={3}>
                                <RadioGroupWithIcon
                                    value={viewMode}
                                    size='small'
                                    buttonStyle='outline'
                                    onChange={(e) => setViewMode(e.target.value)}
                                >
                                    {groupTicketsBy === 'status' && (
                                        <Radio.Button value='line'>
                                            <LinearChartIcon height={32} width={24} />
                                        </Radio.Button>
                                    )}
                                    <Radio.Button value='bar'>
                                        <BarChartIcon height={32} width={24} />
                                    </Radio.Button>
                                    {groupTicketsBy !== 'status' && (
                                        <Radio.Button value='pie'>
                                            <PieChartIcon height={32} width={24} />
                                        </Radio.Button>
                                    )}
                                </RadioGroupWithIcon>
                            </Col>
                            <Col
                                xs={8}
                                hidden={!isSmall}
                            >
                                <TicketTypeSelect
                                    ticketType={ticketType}
                                    setTicketType={setTicketType}
                                    loading={loading}
                                />
                            </Col>
                        </Row>
                    </Col>
                    <Col span={24}>
                        {useMemo(() => (
                            <TicketChartView
                                data={analyticsData}
                                loading={loading}
                                viewMode={viewMode}
                                mainGroup={groupTicketsBy}
                                chartConfig={{
                                    animationEnabled: true,
                                    chartOptions: { renderer: 'svg', height: viewMode === 'line' ? 440 : 'auto' },
                                }}
                                mapperInstance={mapperInstanceRef.current}
                            >
                                <Col
                                    style={{ position: 'absolute', top: 0, right: 0, minWidth: '132px' }}
                                    hidden={isSmall}
                                >
                                    <TicketTypeSelect
                                        ticketType={ticketType}
                                        setTicketType={setTicketType}
                                        loading={loading}
                                    />
                                </Col>
                            </TicketChartView>
                        ), [analyticsData, loading, viewMode, ticketType, userOrganizationId, groupTicketsBy, isSmall])}
                    </Col>
                    <Col span={24}>
                        <Row gutter={[0, 20]}>
                            <Col span={24}>
                                <Typography.Title level={4}>{TableTitle}</Typography.Title>
                            </Col>
                            <Col span={24}>
                                {useMemo(() => (
                                    <TicketListView
                                        data={analyticsData}
                                        loading={loading}
                                        viewMode={viewMode}
                                        filters={filtersRef.current}
                                        mapperInstance={mapperInstanceRef.current}
                                    />
                                ), [analyticsData, loading, viewMode, ticketType, userOrganizationId, groupTicketsBy])}
                            </Col>
                        </Row>
                    </Col>
                    <ActionBar hidden={isSmall}>
                        <Button disabled={isControlsDisabled || isEmpty(analyticsData)} onClick={printPdf} icon={<FilePdfFilled />} type='sberPrimary' secondary>
                            {PrintTitle}
                        </Button>
                        <Button disabled={isControlsDisabled || isEmpty(analyticsData)} onClick={downloadExcel} loading={isXSLXLoading} icon={<EditFilled />} type='sberPrimary' secondary>
                            {ExcelTitle}
                        </Button>
                    </ActionBar>
                </Row>
                <TicketWarningModal />
            </PageContent>
        </PageWrapper>
    </>
}
Example #12
Source File: index.tsx    From condo with MIT License 4 votes vote down vote up
TicketPageContent = ({ organization, employee, TicketContent }) => {
    const intl = useIntl()
    const ServerErrorMessage = intl.formatMessage({ id: 'ServerError' })
    const UpdateMessage = intl.formatMessage({ id: 'Edit' })
    const PrintMessage = intl.formatMessage({ id: 'Print' })
    const SourceMessage = intl.formatMessage({ id: 'pages.condo.ticket.field.Source' })
    const TicketAuthorMessage = intl.formatMessage({ id: 'Author' })
    const EmergencyMessage = intl.formatMessage({ id: 'Emergency' })
    const PaidMessage = intl.formatMessage({ id: 'Paid' })
    const WarrantyMessage = intl.formatMessage({ id: 'Warranty' })
    const ReturnedMessage = intl.formatMessage({ id: 'Returned' })
    const ChangedMessage = intl.formatMessage({ id: 'Changed' })
    const TimeHasPassedMessage = intl.formatMessage({ id: 'TimeHasPassed' })
    const DaysShortMessage = intl.formatMessage({ id: 'DaysShort' })
    const HoursShortMessage = intl.formatMessage({ id: 'HoursShort' })
    const MinutesShortMessage = intl.formatMessage({ id: 'MinutesShort' })
    const LessThanMinuteMessage = intl.formatMessage({ id: 'LessThanMinute' })
    const ResidentCannotReadTicketMessage = intl.formatMessage({ id: 'pages.condo.ticket.title.ResidentCannotReadTicket' })

    const router = useRouter()
    const auth = useAuth() as { user: { id: string } }
    const user = get(auth, 'user')
    const { isSmall } = useLayoutContext()

    // NOTE: cast `string | string[]` to `string`
    const { query: { id } } = router as { query: { [key: string]: string } }

    const { refetch: refetchTicket, loading, obj: ticket, error } = Ticket.useObject({
        where: { id: id },
    }, {
        fetchPolicy: 'network-only',
    })
    // TODO(antonal): get rid of separate GraphQL query for TicketChanges
    const ticketChangesResult = TicketChange.useObjects({
        where: { ticket: { id } },
        // TODO(antonal): fix "Module not found: Can't resolve '@condo/schema'"
        // sortBy: [SortTicketChangesBy.CreatedAtDesc],
        // @ts-ignore
        sortBy: ['createdAt_DESC'],
    }, {
        fetchPolicy: 'network-only',
    })

    const { objs: comments, refetch: refetchComments } = TicketComment.useObjects({
        where: { ticket: { id } },
        // @ts-ignore
        sortBy: ['createdAt_DESC'],
    })

    const commentsIds = useMemo(() => map(comments, 'id'), [comments])

    const { objs: ticketCommentFiles, refetch: refetchCommentFiles } = TicketCommentFile.useObjects({
        where: { ticketComment: { id_in: commentsIds } },
        // @ts-ignore
        sortBy: ['createdAt_DESC'],
    })

    const commentsWithFiles = useMemo(() => comments.map(comment => {
        comment.files = ticketCommentFiles.filter(file => file.ticketComment.id === comment.id)

        return comment
    }), [comments, ticketCommentFiles])

    const updateComment = TicketComment.useUpdate({}, () => {
        refetchComments()
        refetchCommentFiles()
    })
    const deleteComment = TicketComment.useSoftDelete({}, () => {
        refetchComments()
    })

    const createCommentAction = TicketComment.useCreate({
        ticket: id,
        user: auth.user && auth.user.id,
    }, () => Promise.resolve())

    const { obj: ticketCommentsTime, refetch: refetchTicketCommentsTime } = TicketCommentsTime.useObject({
        where: {
            ticket: { id: id },
        },
    })
    const {
        obj: userTicketCommentReadTime, refetch: refetchUserTicketCommentReadTime, loading: loadingUserTicketCommentReadTime,
    } = UserTicketCommentReadTime.useObject({
        where: {
            user: { id: user.id },
            ticket: { id: id },
        },
    })
    const createUserTicketCommentReadTime = UserTicketCommentReadTime.useCreate({
        user: user.id,
        ticket: id,
    }, () => refetchUserTicketCommentReadTime())
    const updateUserTicketCommentReadTime = UserTicketCommentReadTime.useUpdate({
        user: user.id,
        ticket: id,
    }, () => refetchUserTicketCommentReadTime())

    const canShareTickets = get(employee, 'role.canShareTickets')
    const TicketTitleMessage = useMemo(() => getTicketTitleMessage(intl, ticket), [ticket])
    const TicketCreationDate = useMemo(() => getTicketCreateMessage(intl, ticket), [ticket])

    const refetchCommentsWithFiles = useCallback(async () => {
        await refetchComments()
        await refetchCommentFiles()
        await refetchTicketCommentsTime()
        await refetchUserTicketCommentReadTime()
    }, [refetchCommentFiles, refetchComments, refetchTicketCommentsTime, refetchUserTicketCommentReadTime])

    const actionsFor = useCallback(comment => {
        const isAuthor = comment.user.id === auth.user.id
        const isAdmin = get(auth, ['user', 'isAdmin'])
        return {
            updateAction: isAdmin || isAuthor ? updateComment : null,
            deleteAction: isAdmin || isAuthor ? deleteComment : null,
        }
    }, [auth, deleteComment, updateComment])

    useEffect(() => {
        const handler = setInterval(refetchCommentsWithFiles, COMMENT_RE_FETCH_INTERVAL)
        return () => {
            clearInterval(handler)
        }
    })

    const isEmergency = get(ticket, 'isEmergency')
    const isPaid = get(ticket, 'isPaid')
    const isWarranty = get(ticket, 'isWarranty')
    const statusReopenedCounter = get(ticket, 'statusReopenedCounter')

    const handleTicketStatusChanged = () => {
        refetchTicket()
        ticketChangesResult.refetch()
    }

    const ticketStatusType = get(ticket, ['status', 'type'])
    const disabledEditButton = useMemo(() => ticketStatusType === CLOSED_STATUS_TYPE, [ticketStatusType])
    const statusUpdatedAt = get(ticket, 'statusUpdatedAt')
    const isResidentTicket = useMemo(() => get(ticket, ['createdBy', 'type']) === RESIDENT, [ticket])
    const canReadByResident = useMemo(() => get(ticket,  'canReadByResident'), [ticket])
    const canCreateComments = useMemo(() => get(auth, ['user', 'isAdmin']) || get(employee, ['role', 'canManageTicketComments']),
        [auth, employee])

    const getTimeSinceCreation = useCallback(() => {
        const diffInMinutes = dayjs().diff(dayjs(statusUpdatedAt), 'minutes')
        const daysHavePassed = dayjs.duration(diffInMinutes, 'minutes').format('D')
        const hoursHavePassed = dayjs.duration(diffInMinutes, 'minutes').format('H')
        const minutesHavePassed = dayjs.duration(diffInMinutes, 'minutes').format('m')

        const timeSinceCreation = compact([
            Number(daysHavePassed) > 0 && DaysShortMessage.replace('${days}', daysHavePassed),
            Number(hoursHavePassed) > 0 && HoursShortMessage.replace('${hours}', hoursHavePassed),
            Number(minutesHavePassed) > 0 && MinutesShortMessage.replace('${minutes}', minutesHavePassed),
        ])

        if (isEmpty(timeSinceCreation)) {
            return LessThanMinuteMessage
        }

        return timeSinceCreation.join(' ')
    }, [DaysShortMessage, HoursShortMessage, LessThanMinuteMessage, MinutesShortMessage, statusUpdatedAt])

    if (!ticket) {
        return (
            <LoadingOrErrorPage title={TicketTitleMessage} loading={loading} error={error ? ServerErrorMessage : null}/>
        )
    }

    return (
        <>
            <Head>
                <title>{TicketTitleMessage}</title>
            </Head>
            <PageWrapper>
                <PageContent>
                    <Row gutter={[0, 40]}>
                        <Col lg={16} xs={24}>
                            <Row gutter={[0, 40]}>
                                <Col span={24}>
                                    <Row gutter={[0, 40]}>
                                        <Col lg={18} xs={24}>
                                            <Row gutter={[0, 20]}>
                                                <Col span={24}>
                                                    <Typography.Title style={{ margin: 0 }} level={1}>{TicketTitleMessage}</Typography.Title>
                                                </Col>
                                                <Col span={24}>
                                                    <Row>
                                                        <Col span={24}>
                                                            <Typography.Text style={TICKET_CREATE_INFO_TEXT_STYLE}>
                                                                <Typography.Text style={TICKET_CREATE_INFO_TEXT_STYLE} type='secondary'>{TicketCreationDate}, {TicketAuthorMessage} </Typography.Text>
                                                                <UserNameField user={get(ticket, ['createdBy'])}>
                                                                    {({ name, postfix }) => (
                                                                        <Typography.Text style={TICKET_CREATE_INFO_TEXT_STYLE}>
                                                                            {name}
                                                                            {postfix && <Typography.Text type='secondary' ellipsis>&nbsp;{postfix}</Typography.Text>}
                                                                        </Typography.Text>
                                                                    )}
                                                                </UserNameField>
                                                            </Typography.Text>
                                                        </Col>
                                                        <Col span={24}>
                                                            <Typography.Text type='secondary' style={TICKET_CREATE_INFO_TEXT_STYLE}>
                                                                {SourceMessage} — {get(ticket, ['source', 'name'], '').toLowerCase()}
                                                            </Typography.Text>
                                                        </Col>
                                                        <Col span={24}>
                                                            {
                                                                !isResidentTicket && !canReadByResident && (
                                                                    <Typography.Text type='secondary' style={TICKET_CREATE_INFO_TEXT_STYLE}>
                                                                        <FormattedMessage
                                                                            id={'pages.condo.ticket.title.CanReadByResident'}
                                                                            values={{
                                                                                canReadByResident: (
                                                                                    <Typography.Text type={'danger'}>
                                                                                        {ResidentCannotReadTicketMessage}
                                                                                    </Typography.Text>
                                                                                ),
                                                                            }}
                                                                        />
                                                                    </Typography.Text>
                                                                )
                                                            }
                                                        </Col>
                                                    </Row>
                                                </Col>
                                            </Row>
                                        </Col>
                                        <Col lg={6} xs={24}>
                                            <Row justify={isSmall ? 'center' : 'end'} gutter={[0, 20]}>
                                                <Col span={24}>
                                                    <TicketStatusSelect
                                                        organization={organization}
                                                        employee={employee}
                                                        ticket={ticket}
                                                        onUpdate={handleTicketStatusChanged}
                                                        loading={loading}
                                                        data-cy={'ticket__status-select'}
                                                    />
                                                </Col>
                                                {
                                                    statusUpdatedAt && (
                                                        <Col>
                                                            <Typography.Paragraph style={TICKET_UPDATE_INFO_TEXT_STYLE}>
                                                                {ChangedMessage}: {dayjs(statusUpdatedAt).format('DD.MM.YY, HH:mm')}
                                                            </Typography.Paragraph>
                                                            <Typography.Paragraph style={TICKET_UPDATE_INFO_TEXT_STYLE} type={'secondary'}>
                                                                {TimeHasPassedMessage.replace('${time}', getTimeSinceCreation())}
                                                            </Typography.Paragraph>
                                                        </Col>
                                                    )
                                                }
                                            </Row>
                                        </Col>
                                    </Row>
                                    <Space direction={'horizontal'} style={{ marginTop: '1.6em ' }}>
                                        {isEmergency && <TicketTag color={TICKET_TYPE_TAG_COLORS.emergency}>{EmergencyMessage.toLowerCase()}</TicketTag>}
                                        {isPaid && <TicketTag color={TICKET_TYPE_TAG_COLORS.paid}>{PaidMessage.toLowerCase()}</TicketTag>}
                                        {isWarranty && <TicketTag color={TICKET_TYPE_TAG_COLORS.warranty}>{WarrantyMessage.toLowerCase()}</TicketTag>}
                                        {
                                            statusReopenedCounter > 0 && (
                                                <TicketTag color={TICKET_TYPE_TAG_COLORS.returned}>
                                                    {ReturnedMessage.toLowerCase()} {statusReopenedCounter > 1 && `(${statusReopenedCounter})`}
                                                </TicketTag>
                                            )
                                        }
                                    </Space>
                                </Col>
                                <TicketContent ticket={ticket}/>
                                <ActionBar>
                                    <Link href={`/ticket/${ticket.id}/update`}>
                                        <Button
                                            disabled={disabledEditButton}
                                            color={'green'}
                                            type={'sberDefaultGradient'}
                                            secondary
                                            icon={<EditFilled />}
                                            data-cy={'ticket__update-link'}
                                        >
                                            {UpdateMessage}
                                        </Button>
                                    </Link>
                                    {
                                        !isSmall && (
                                            <Button
                                                type={'sberDefaultGradient'}
                                                icon={<FilePdfFilled />}
                                                href={`/ticket/${ticket.id}/pdf`}
                                                target={'_blank'}
                                                secondary
                                            >
                                                {PrintMessage}
                                            </Button>
                                        )
                                    }
                                    {
                                        canShareTickets
                                            ? <ShareTicketModal
                                                organization={organization}
                                                date={get(ticket, 'createdAt')}
                                                number={get(ticket, 'number')}
                                                details={get(ticket, 'details')}
                                                id={id}
                                                locale={get(organization, 'country')}
                                            />
                                            : null
                                    }
                                </ActionBar>
                                <TicketChanges
                                    loading={get(ticketChangesResult, 'loading')}
                                    items={get(ticketChangesResult, 'objs')}
                                    total={get(ticketChangesResult, 'count')}
                                />
                            </Row>
                        </Col>
                        <Col lg={7} xs={24} offset={isSmall ? 0 : 1}>
                            <Affix offsetTop={40}>
                                <Comments
                                    ticketCommentsTime={ticketCommentsTime}
                                    userTicketCommentReadTime={userTicketCommentReadTime}
                                    createUserTicketCommentReadTime={createUserTicketCommentReadTime}
                                    updateUserTicketCommentReadTime={updateUserTicketCommentReadTime}
                                    loadingUserTicketCommentReadTime={loadingUserTicketCommentReadTime}
                                    FileModel={TicketCommentFile}
                                    fileModelRelationField={'ticketComment'}
                                    ticket={ticket}
                                    // @ts-ignore
                                    createAction={createCommentAction}
                                    updateAction={updateComment}
                                    refetchComments={refetchCommentsWithFiles}
                                    comments={commentsWithFiles}
                                    canCreateComments={canCreateComments}
                                    actionsFor={actionsFor}
                                />
                            </Affix>
                        </Col>
                    </Row>
                </PageContent>
            </PageWrapper>
        </>
    )
}
Example #13
Source File: index.tsx    From condo with MIT License 4 votes vote down vote up
UserInfoPage = () => {
    const intl = useIntl()
    const PhoneMessage = intl.formatMessage({ id: 'Phone' })
    const EmailMessage = intl.formatMessage({ id: 'field.EMail' })
    const PasswordMessage = intl.formatMessage({ id: 'pages.auth.signin.field.Password' })
    const UpdateMessage = intl.formatMessage({ id: 'Edit' })

    const { user, refetch } = useAuth()
    const userOrganization = useOrganization()
    const { isSmall } = useLayoutContext()

    useEffect(() => {
        refetch()
    }, [])

    const name = get(user, 'name')
    const email = get(user, 'email', '')

    return (
        <>
            <Head>
                <title>{name}</title>
            </Head>
            <FeatureFlagsController/>
            <PageWrapper>
                <PageContent>
                    <Row gutter={[0, 40]} justify={'center'}>
                        <Col xs={10} lg={3}>
                            <UserAvatar borderRadius={24}/>
                        </Col>
                        <Col xs={24} lg={20} offset={ isSmall ? 0 : 1}>
                            <Row gutter={[0, 60]}>
                                <Col span={24}>
                                    <Row gutter={[0, 40]}>
                                        <Col span={24}>
                                            <Typography.Title
                                                level={1}
                                                style={{ margin: 0, fontWeight: 'bold' }}
                                            >
                                                {name}
                                            </Typography.Title>
                                        </Col>
                                        <Col span={24}>
                                            <Row gutter={[0, 24]}>
                                                <Col lg={3} xs={10}>
                                                    <Typography.Text type='secondary'>
                                                        {PhoneMessage}
                                                    </Typography.Text>
                                                </Col>
                                                <Col lg={19} xs={10} offset={2}>
                                                    <NotDefinedField value={get(user, 'phone')}/>
                                                </Col>
                                                {
                                                    email && <>
                                                        <Col lg={3} xs={10}>
                                                            <Typography.Text type='secondary'>
                                                                {EmailMessage}
                                                            </Typography.Text>
                                                        </Col>
                                                        <Col lg={19} xs={10} offset={2}>
                                                            <NotDefinedField value={get(user, 'email')}/>
                                                        </Col>
                                                    </>
                                                }
                                                <Col lg={3} xs={10}>
                                                    <Typography.Text type='secondary'>
                                                        {PasswordMessage}
                                                    </Typography.Text>
                                                </Col>
                                                <Col lg={19} xs={10} offset={2}>
                                                    <NotDefinedField value='******'/>
                                                </Col>
                                            </Row>
                                        </Col>
                                        <Col span={24}>
                                            <Link href={'/user/update'}>
                                                <Button
                                                    color={'green'}
                                                    type={'sberPrimary'}
                                                    secondary
                                                    icon={<EditFilled />}
                                                >
                                                    {UpdateMessage}
                                                </Button>
                                            </Link>
                                        </Col>
                                    </Row>
                                </Col>
                                <Col span={24}>
                                    {
                                        userOrganization
                                            ? (<UserOrganizationsList userOrganization={userOrganization}/>)
                                            : null
                                    }
                                </Col>
                            </Row>
                        </Col>
                    </Row>
                </PageContent>
            </PageWrapper>
        </>
    )
}
Example #14
Source File: detail.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
CustomerDetail: React.FC = () => {
  const queryFormRef = useRef<FormInstance>();
  const actionRef = useRef<ActionType>();
  const [customerDetail, setCustomerDetail] = useState<CustomerItem>()
  const [staffMap, setStaffMap] = useState<Dictionary<StaffOption>>({});
  const [allCustomerTagGroups, setAllCustomerTagGroups] = useState<CustomerTagGroupItem[]>([]);
  const [defaultCustomerTags, setDefaultCustomerTags] = useState<CustomerTag[]>([]);
  const [defaultInternalTagsIds, setDefaultInternalTagsIds] = useState<string []>([])
  const [personalTagModalVisible, setPersonalTagModalVisible] = useState(false)
  const [customerTagModalVisible, setCustomerTagModalVisible] = useState(false)
  const [internalTagList, setInternalTagList] = useState<InternalTags.Item[]>([])
  const [internalTagListMap, setInternalTagListMap] = useState<Dictionary<InternalTags.Item>>({});
  const [initialEvents, setInitialEvents] = useState<CustomerEvents.Item[]>([])
  const [currentTab, setCurrentTab] = useState('survey')
  const [basicInfoDisplay, setBasicInfoDisplay] = useState({} as any)// 展示哪些基本信息
  const [basicInfoValues, setBasicInfoValues] = useState({} as any) // 基本信息取值
  const [remarkValues, setRemarkValues] = useState<Remark[]>([])
  const [reloadCusDataTimesTamp, setReloadCusDataTimesTamp] = useState(Date.now)
  const [formRef] = Form.useForm()

  const params = new URLSearchParams(window.location.search);
  const currentStaff = localStorage.getItem('extStaffAdminID') as string
  const extCustomerID = params.get('ext_customer_id') || "";
  if (!extCustomerID) {
    message.error('传入参数请带上ID');
  }

  const extStaff = () => {
    const staffs: StaffItem[] = [];
    customerDetail?.staff_relations?.forEach((staff_relation) => {
      // @ts-ignore
      const staff = staffMap[staff_relation.ext_staff_id];
      if (staff) {
        staffs.push(staff);
      }
    });
    return staffs;
  }

  const getCustomerDetail = () => {
    const hide = message.loading("加载数据中");
    GetCustomerDetail(extCustomerID).then(res => {
      hide();
      if (res?.code !== 0) {
        message.error("获取客户详情失败");
        return;
      }
      setCustomerDetail(res?.data);
      const cusTags: any[] = [];
      const interTagsIds: any[] = []
      res?.data?.staff_relations?.forEach((relation: any) => {
        if (relation.ext_staff_id === currentStaff) {
          relation.customer_staff_tags?.forEach((tag: any) => {
            cusTags.push({...tag, name: tag.tag_name, ext_id: tag.ext_tag_id});
          });
          relation.internal_tags?.forEach((tagId: string) => {
            interTagsIds.push(tagId);
          })
        }

      });
      setDefaultCustomerTags(cusTags)
      setDefaultInternalTagsIds(interTagsIds)
    }).catch(() => {
      hide();
    })
  }

  const getInternalTags = () => {
    QueryInternalTags({page_size: 5000, ext_staff_id: currentStaff}).then(res => {
      if (res?.code === 0) {
        setInternalTagList(res?.data?.items)
        setInternalTagListMap(_.keyBy(res?.data?.items, 'id'))
      } else {
        message.error(res?.message)
      }
    })
  }

  const getCustomerRemark = () => { // 自定义信息id-key
    QueryCustomerRemark().then(res => {
      if (res?.code === 0) {
        console.log('QueryCustomerRemark', res.data)
      } else {
        message.error(res?.message)
      }
    })
  }

  const getBasicInfoDisplay = () => {
    GetCustomerBasicInfoDisplay().then(res => {
      if (res?.code === 0) {
        const displayData = res?.data
        delete displayData.id
        delete displayData.ext_corp_id
        delete displayData.created_at
        delete displayData.updated_at
        delete displayData.deleted_at
        setBasicInfoDisplay(displayData || {})
      } else {
        message.error(res?.message)
      }
    })
  }

  const getBasicInfoAndRemarkValues = () => {
    GetBasicInfoAndRemarkValues({
      ext_customer_id: extCustomerID,
      ext_staff_id: currentStaff,
    }).then(res => {
      if (res?.code === 0) {
        const resData = res?.data
        delete resData.id
        delete resData.ext_corp_id
        delete resData.ext_creator_id
        delete resData.ext_customer_id
        delete resData.ext_staff_id
        delete resData.created_at
        delete resData.updated_at
        delete resData.deleted_at
        delete resData.remark_values
        setBasicInfoValues(resData)
        setRemarkValues(res?.data?.remark_values || [])
      }
    })
  }

  const updateBasicInfoAndRemark = (basicInfoParams: any) => {
    UpdateBasicInfoAndRemark({
      ext_staff_id: currentStaff,
      ext_customer_id: extCustomerID,
      ...basicInfoParams
    }).then(res => {
      if (res?.code === 0) {
        message.success('客户信息更新成功')
        setReloadCusDataTimesTamp(Date.now)
      } else {
        message.error('客户信息更新失败')
      }
    })
  }

  useEffect(() => {
    getInternalTags()
    getCustomerDetail()
    getCustomerRemark()
    getBasicInfoDisplay()
    getBasicInfoAndRemarkValues()
  }, [reloadCusDataTimesTamp])

  useEffect(() => {
    QueryCustomerTagGroups({page_size: 5000}).then((res) => {
      if (res.code === 0) {
        setAllCustomerTagGroups(res?.data?.items);
      } else {
        message.error(res.message);
      }
    });
  }, []);

  useEffect(() => {
    QuerySimpleStaffs({page_size: 5000}).then((res) => {
      if (res.code === 0) {
        const staffs = res?.data?.items?.map((item: SimpleStaffInterface) => {
          return {
            label: item.name,
            value: item.ext_id,
            ...item,
          };
        }) || [];
        setStaffMap(_.keyBy<StaffOption>(staffs, 'ext_id'));
      } else {
        message.error(res.message);
      }
    });
  }, []);

  useEffect(() => {
    QueryCustomerEvents({
      ext_customer_id: extCustomerID,
      ext_staff_id: currentStaff,
      page_size: 5
    }).then(res => {
      console.log('QueryCustomerEventsQueryCustomerEvents', res)
      setInitialEvents(res?.data?.items || [])
    })
  }, [])

  useEffect(() => {
    formRef.setFieldsValue(basicInfoValues)
  }, [basicInfoValues])

  return (
    <PageContainer
      fixedHeader
      onBack={() => history.go(-1)}
      backIcon={<LeftOutlined/>}
      header={{
        title: '客户详情',
      }}
    >
      <ProCard>
        <Descriptions title="客户信息" column={1}>
          <Descriptions.Item>
            <div className={'customer-info-field'}>
              <div><img src={customerDetail?.avatar} alt={customerDetail?.name} style={{borderRadius: 5}}/></div>
              <div style={{fontSize: 16, marginLeft: 10}}>
                <p>{customerDetail?.name}</p>
                {customerDetail?.corp_name && (
                  <p style={{color: '#eda150', marginTop: 10}}>@{customerDetail?.corp_name}</p>
                )}
                {customerDetail?.type === 1 && (
                  <p style={{
                    color: '#5ec75d',
                    fontSize: '13px'
                  }}>@微信</p>
                )}
              </div>
            </div>
          </Descriptions.Item>
          <div>
            <div style={{width: 70, display: 'inline-block'}}>企业标签:</div>
            <div className={styles.tagContainer}>
              <Space direction={'horizontal'} wrap={true}>
                {
                  defaultCustomerTags?.length > 0 && defaultCustomerTags?.map((tag) =>
                    <Tag
                      key={tag?.id}
                      className={'tag-item selected-tag-item'}
                    >
                      {tag?.name}
                    </Tag>
                  )}
              </Space>
            </div>
            <Button
              key='addCusTags'
              icon={<EditOutlined/>}
              type={'link'}
              onClick={() => {
                setCustomerTagModalVisible(true);
              }}
            >
              编辑
            </Button>
          </div>

          <div>
            <div style={{width: 70, display: 'inline-block'}}>个人标签:</div>
            <div className={styles.tagContainer}>
              <Space direction={'horizontal'} wrap={true}>
                {
                  defaultInternalTagsIds?.length > 0 && defaultInternalTagsIds.map(id => internalTagListMap[id])?.map((tag) =>
                    <Tag
                      key={tag?.id}
                      className={'tag-item selected-tag-item'}
                    >
                      {tag?.name}
                      <span>
                     </span>
                    </Tag>
                  )}
              </Space>
            </div>
            <Button
              key='addInternalTags'
              icon={<EditOutlined/>}
              type={'link'}
              onClick={() => {
                setPersonalTagModalVisible(true);
              }}
            >
              编辑
            </Button>
          </div>
        </Descriptions>
      </ProCard>

      <ProCard
        tabs={{
          onChange: (activeKey: string) => setCurrentTab(activeKey),
          activeKey: currentTab
        }}
        style={{marginTop: 25}}
      >
        <ProCard.TabPane key="survey" tab="客户概况">
          <div className={styles.survey}>
            <div className={styles.cusSurveyLeft}>
              <div>
                <Descriptions title={<div><ContactsFilled/>&nbsp;&nbsp;添加客服信息</div>} layout="vertical" bordered
                              column={4}>
                  <Descriptions.Item label="所属员工">
                    <CollapsedStaffs limit={2} staffs={extStaff()}/>
                  </Descriptions.Item>

                  <Descriptions.Item label="客户来源">
                       <span>
                         {customerDetail?.staff_relations?.map((para) => {
                           return (`${addWayEnums[para.add_way || 0]}\n`);
                         })}
                       </span>
                  </Descriptions.Item>
                  <Descriptions.Item label="添加时间">
                    <Space>
                      {customerDetail?.staff_relations?.map((para) => {
                        return (
                          <div className={styles.staffTag}
                               dangerouslySetInnerHTML={{
                                 __html: moment(para.createtime)
                                   .format('YYYY-MM-DD HH:mm')
                                   .split(' ')
                                   .join('<br />'),
                               }}
                          />
                        );
                      })}
                    </Space>
                  </Descriptions.Item>
                  <Descriptions.Item label="更新时间">
                    <div
                      dangerouslySetInnerHTML={{
                        __html: moment(customerDetail?.updated_at)
                          .format('YYYY-MM-DD HH:mm')
                          .split(' ')
                          .join('<br />'),
                      }}
                    />
                  </Descriptions.Item>
                </Descriptions>
              </div>
              <Form form={formRef} onFinish={(values) => {
                console.log('ooooooooooooooovaluesvalues', values)
                const basicInfoParams = {...values}
                updateBasicInfoAndRemark(basicInfoParams)
              }}>
                <div style={{paddingTop: 20}} className={styles.baseInfoContainer}>
                  <Descriptions
                    title={<div><BookFilled/>&nbsp;&nbsp;基本信息</div>}
                    bordered
                    column={2}
                    size={'small'}
                  >
                    {
                      Object.keys(basicInfoDisplay).map(key => {
                        return <Descriptions.Item label={basicInfo[key]}>
                          <TableInput name={key} />
                        </Descriptions.Item>
                      })
                    }
                  </Descriptions>
                </div>
                {
                  remarkValues.length > 0 && <div style={{paddingTop: 20}} className={styles.customInfoContainer}>
                    <Descriptions
                      title={<div><EditFilled/>&nbsp;&nbsp;自定义信息</div>}
                      bordered
                      column={2}
                      size={'small'}
                    >
                      <Descriptions.Item label="sfdsf">
                        <TableInput name={'aa'}/>
                      </Descriptions.Item>
                      <Descriptions.Item label="违法的">
                        <TableInput name={'bb'}/>
                      </Descriptions.Item>
                      <Descriptions.Item label="sdf434">
                        <TableInput name={'cc'}/>
                      </Descriptions.Item>
                      <Descriptions.Item label="yjkyujy">
                        <TableInput name={'dd'}/>
                      </Descriptions.Item>
                    </Descriptions>
                  </div>
                }
                <div style={{display: 'flex', justifyContent: 'center', marginTop: 40}}>
                  <Space>
                    <Button onClick={() => formRef.setFieldsValue(basicInfoValues)}>重置</Button>
                    <Button type={"primary"} onClick={() => {
                      formRef.submit()
                    }}>提交</Button>
                  </Space>
                </div>
              </Form>
            </div>

            <div className={styles.cusSurveyRight}>
              <div className={styles.eventsTitle}>
                <span className={styles.titleText}><SoundFilled/>&nbsp;&nbsp;客户动态</span>
                <a onClick={() => setCurrentTab('events')} style={{fontSize: 12}}>查看更多<RightOutlined/></a>
              </div>
              <Events data={initialEvents.filter(elem => elem !== null)} simpleRender={true} staffMap={staffMap}
                      extCustomerID={extCustomerID}/>
            </div>
          </div>

        </ProCard.TabPane>

        <ProCard.TabPane key="events" tab="客户动态">
          <Events staffMap={staffMap} extCustomerID={extCustomerID}/>
        </ProCard.TabPane>

        <ProCard.TabPane key="room" tab="所在群聊">
          <ProTable<GroupChatItem>
            search={false}
            formRef={queryFormRef}
            actionRef={actionRef}
            className={'table'}
            scroll={{x: 'max-content'}}
            columns={columns}
            rowKey="id"
            toolBarRender={false}
            bordered={false}
            tableAlertRender={false}
            dateFormatter="string"
            request={async (originParams: any, sort, filter) => {
              return ProTableRequestAdapter(
                originParams,
                sort,
                filter,
                QueryCustomerGroupsList,
              );
            }}
          />
        </ProCard.TabPane>

        <ProCard.TabPane key="chat" tab="聊天记录">
          {
            setStaffMap[currentStaff]?.enable_msg_arch === 1 ? <Button
                key={'chatSession'}
                type={"link"}
                icon={<ClockCircleOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
                onClick={() => {
                  window.open(`/staff-admin/corp-risk-control/chat-session?staff=${currentStaff}`)
                }}
              >
                聊天记录查询
              </Button>
              :
              <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={<span>员工暂未开启消息存档</span>}/>
          }
        </ProCard.TabPane>

      </ProCard>

      <CustomerTagSelectionModal
        type={'customerDetailEnterpriseTag'}
        isEditable={true}
        withLogicalCondition={false}
        width={'630px'}
        visible={customerTagModalVisible}
        setVisible={setCustomerTagModalVisible}
        defaultCheckedTags={defaultCustomerTags}
        onFinish={(selectedTags) => {
          const removeAry = _.difference(defaultCustomerTags.map(dt => dt.ext_id), selectedTags.map(st => st.ext_id))
          UpdateCustomerTags({
            // @ts-ignore
            add_ext_tag_ids: selectedTags.map((tag) => tag.ext_id),
            ext_customer_ids: [extCustomerID],
            ext_staff_id: currentStaff,
            // @ts-ignore
            remove_ext_tag_ids: removeAry
          }).then(() => {
            getCustomerDetail()
          })
        }}
        allTagGroups={allCustomerTagGroups}
      />

      <InternalTagModal
        width={560}
        allTags={internalTagList}
        allTagsMap={internalTagListMap}
        setAllTags={setInternalTagList}
        visible={personalTagModalVisible}
        setVisible={setPersonalTagModalVisible}
        defaultCheckedTagsIds={defaultInternalTagsIds}
        reloadTags={getInternalTags}
        onFinish={(selectedTags) => {
          console.log('selectedTags', selectedTags)
          const removeAry = _.difference(defaultInternalTagsIds, selectedTags.map(st => st.id))
          CustomerInternalTags({
            // @ts-ignore
            add_ext_tag_ids: selectedTags.map((tag) => tag.id),
            ext_customer_id: extCustomerID,
            ext_staff_id: currentStaff,
            // @ts-ignore
            remove_ext_tag_ids: removeAry
          }).then(() => {
            getCustomerDetail()
          })
        }
        }
      />
    </PageContainer>
  );
}
Example #15
Source File: Columns.tsx    From wildduck-ui with MIT License 4 votes vote down vote up
getMailboxesColumns = ({
	dataSource,
	drilldown,
	edit,
	deleteAllMessages,
}: {
	dataSource: any;
	drilldown(value: any): void;
	edit(value: any): void;
	deleteAllMessages(value: string): void;
}): any => {
	const columns = [
		{
			title: 'Path',
			dataIndex: 'path',
			align: 'center' as const,
			render: (text: string, record: any) => (
				<Space size='middle'>
					<a onClick={() => drilldown(record)}>{text}</a>
				</Space>
			),
		},
		{
			title: 'Name',
			dataIndex: 'name',
			filter: true,
		},
		{
			title: 'Special use',
			dataIndex: 'specialUse',
		},
		{
			title: 'Total',
			dataIndex: 'total',
			sortable: 'number',
		},
		{
			title: 'Unseen',
			dataIndex: 'unseen',
			sortable: 'number',
		},
		{
			title: 'Space Used',
			dataIndex: 'size',
			sortable: 'number',
			render: (size: number) => <>{_.isNaN(_.round(size / 1024000, 3)) ? 0 : _.round(size / 1024000, 3)} MB</>,
		},
		{
			title: 'Modified index',
			dataIndex: 'modifyIndex',
			sortable: 'number',
		},
		{
			title: 'Subscribed',
			dataIndex: 'subscribed',
			align: 'center' as const,
			filter: true,
			render: (subscribed: string) => (
				<Tag color={_.isEqual(subscribed, 'true') ? 'green' : 'red'}>{_.toString(subscribed)}</Tag>
			),
		},
		{
			title: 'Hidden',
			dataIndex: 'hidden',
			align: 'center' as const,
			filter: true,
			render: (hidden: string) => (
				<Tag color={_.isEqual(hidden, 'true') ? 'green' : 'red'}>{_.toString(!hidden)}</Tag>
			),
		},
		{
			title: 'Action',
			key: 'action',
			align: 'center' as const,
			render: (text: string, record: any) => (
				<Space size={'middle'}>
					<Tooltip title={'Edit'}>
						<Button className='ant-btn-icon' shape='circle' onClick={() => edit(record)}>
							<EditFilled className={'green-color'} />
						</Button>
					</Tooltip>
					<Tooltip title={'Purge all messages'}>
						<Button
							onClick={() => {
								showConfirm(
									() => deleteAllMessages(record.id),
									'Are you sure you want to delete all messages in the mailbox?',
								);
							}}
							className='ant-btn-icon'
							shape='circle'
						>
							<DeleteTwoTone />
						</Button>
					</Tooltip>
				</Space>
			),
		},
	];

	return getColumnsWithFilterAndSort(columns, dataSource);
}