blob: 130dd5dc27d5f16803f4d527f92dccb5dc1baeb4 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { FC, useEffect, useState } from 'react';
import { Dropdown, Form } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { loggedUserInfoStore } from '@/stores';
import { userSearchByName } from '@/services';
import { Avatar } from '@/components';
import * as Type from '@/common/interface';
import './PeopleDropdown.scss';
interface Props {
selectedPeople: Type.UserInfoBase[] | undefined;
onSelect: (people: Type.UserInfoBase) => void;
saveInviteUsers: () => void;
visible?: boolean;
}
interface UserInfoCheck extends Type.UserInfoBase {
checked?: boolean;
}
const Index: FC<Props> = ({
selectedPeople = [],
visible = false,
onSelect,
saveInviteUsers,
}) => {
const { user: currentUser } = loggedUserInfoStore();
const { t } = useTranslation('translation', {
keyPrefix: 'invite_to_answer',
});
const [peopleList, setPeopleList] = useState<UserInfoCheck[]>([]);
const [currentIndex, setCurrentIndex] = useState(-1);
const [searchValue, setSearchValue] = useState('');
const filterAndSetPeople = (source) => {
const filteredPeople: Type.UserInfoBase[] = [];
source.forEach((p) => {
if (
currentUser &&
currentUser.role_id === 1 &&
currentUser.username === p.username
) {
return;
}
if (selectedPeople?.find((_) => _.username === p.username)) {
return;
}
filteredPeople.push(p);
});
setPeopleList([...selectedPeople, ...filteredPeople]);
};
const searchPeople = (s) => {
if (!s) {
setPeopleList([...selectedPeople]);
return;
}
userSearchByName(s).then((resp) => {
filterAndSetPeople(resp);
});
};
const handleSearch = (evt) => {
const s = evt.target.value;
setSearchValue(s);
searchPeople(s);
};
const resetSearch = () => {
setCurrentIndex(-1);
setSearchValue('');
setPeopleList([]);
};
const handleSelect = (idx) => {
if (idx < 0 || idx >= peopleList.length) {
return;
}
const people = peopleList[idx];
if (people) {
onSelect(people);
}
};
const handleKeyDown = (evt) => {
evt.stopPropagation();
if (!peopleList?.length) {
return;
}
const { keyCode } = evt;
if (keyCode === 38 && currentIndex > 0) {
setCurrentIndex(currentIndex - 1);
}
if (keyCode === 40 && currentIndex < peopleList.length - 1) {
setCurrentIndex(currentIndex + 1);
}
if (keyCode === 13 && currentIndex > -1) {
evt.preventDefault();
handleSelect(currentIndex);
}
};
useEffect(() => {
if (visible && selectedPeople.length > 0) {
setPeopleList(selectedPeople);
}
if (!visible) {
resetSearch();
}
}, [visible]);
return visible ? (
<Dropdown
autoClose="outside"
show
className="people-dropdown"
onSelect={handleSelect}
onKeyDown={handleKeyDown}
onToggle={(state) => {
if (!state) {
saveInviteUsers();
}
}}>
<Dropdown.Menu
renderOnMount
show
className="w-100 py-0 position-relative">
<div className="p-3">
<Form.Control
type="search"
autoFocus
placeholder={t('search')}
value={searchValue}
onChange={handleSearch}
/>
</div>
<div className={`${peopleList.length > 0 ? 'py-2 border-top' : ''}`}>
{peopleList.map((p, idx) => {
return (
<Dropdown.Item
key={p.username}
eventKey={idx}
onFocus={() => {
setCurrentIndex(idx);
}}
active={idx === currentIndex}>
<Form.Check
type="checkbox"
id={p.username}
className="position-relative">
<Form.Check.Input
type="checkbox"
tabIndex={-1}
checked={Boolean(
selectedPeople?.find((v) => v.id === p.id),
)}
onChange={() => {}}
/>
<div className="check-cover" />
<Form.Check.Label className="d-flex align-items-center text-nowrap">
<Avatar
avatar={p.avatar}
size="24"
alt={p.display_name}
className="rounded-1"
/>
<div className="d-flex flex-wrap text-truncate">
<span className="ms-2 text-truncate">
{p.display_name}
</span>
<small className="text-secondary text-truncate ms-2">
@{p.username}
</small>
</div>
</Form.Check.Label>
</Form.Check>
</Dropdown.Item>
);
})}
</div>
</Dropdown.Menu>
</Dropdown>
) : null;
};
export default Index;