assignedTo.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import {useEffect} from 'react';
  2. import styled from '@emotion/styled';
  3. import {fetchOrgMembers} from 'sentry/actionCreators/members';
  4. import {
  5. AssigneeSelectorDropdown,
  6. AssigneeSelectorDropdownProps,
  7. } from 'sentry/components/assigneeSelectorDropdown';
  8. import ActorAvatar from 'sentry/components/avatar/actorAvatar';
  9. import {AutoCompleteRoot} from 'sentry/components/dropdownAutoComplete/menu';
  10. import LoadingIndicator from 'sentry/components/loadingIndicator';
  11. import * as SidebarSection from 'sentry/components/sidebarSection';
  12. import {IconChevron, IconUser} from 'sentry/icons';
  13. import {t} from 'sentry/locale';
  14. import MemberListStore from 'sentry/stores/memberListStore';
  15. import TeamStore from 'sentry/stores/teamStore';
  16. import space from 'sentry/styles/space';
  17. import type {Actor, Group, Project} from 'sentry/types';
  18. import useApi from 'sentry/utils/useApi';
  19. import useOrganization from 'sentry/utils/useOrganization';
  20. interface AssignedToProps {
  21. group: Group;
  22. projectId: Project['id'];
  23. onAssign?: AssigneeSelectorDropdownProps['onAssign'];
  24. }
  25. function AssignedTo({group, projectId}: AssignedToProps) {
  26. const organization = useOrganization();
  27. const api = useApi();
  28. useEffect(() => {
  29. // TODO: We should check if this is already loaded
  30. fetchOrgMembers(api, organization.slug, [projectId]);
  31. }, [api, organization.slug, projectId]);
  32. function getAssignedToDisplayName(assignedTo?: Actor) {
  33. if (assignedTo?.type === 'team') {
  34. const team = TeamStore.getById(group.assignedTo.id);
  35. return `#${team?.slug ?? group.assignedTo.name}`;
  36. }
  37. if (assignedTo?.type === 'user') {
  38. const user = MemberListStore.getById(assignedTo.id);
  39. return user?.name ?? group.assignedTo.name;
  40. }
  41. return group.assignedTo?.name ?? t('No-one');
  42. }
  43. return (
  44. <SidebarSection.Wrap>
  45. <SidebarSection.Title>{t('Assigned To')}</SidebarSection.Title>
  46. <StyledSidebarSectionContent>
  47. <AssigneeSelectorDropdown id={group.id}>
  48. {({loading, assignedTo, isOpen, getActorProps}) => (
  49. <DropdownButton data-test-id="assignee-selector" {...getActorProps({})}>
  50. <ActorWrapper>
  51. {loading ? (
  52. <StyledLoadingIndicator mini size={24} />
  53. ) : assignedTo ? (
  54. <ActorAvatar
  55. data-test-id="assigned-avatar"
  56. actor={assignedTo}
  57. hasTooltip={false}
  58. size={24}
  59. />
  60. ) : (
  61. <IconWrapper>
  62. <IconUser size="md" />
  63. </IconWrapper>
  64. )}
  65. <ActorName>{getAssignedToDisplayName(assignedTo)}</ActorName>
  66. </ActorWrapper>
  67. <IconChevron direction={isOpen ? 'up' : 'down'} />
  68. </DropdownButton>
  69. )}
  70. </AssigneeSelectorDropdown>
  71. </StyledSidebarSectionContent>
  72. </SidebarSection.Wrap>
  73. );
  74. }
  75. export default AssignedTo;
  76. const DropdownButton = styled('div')`
  77. display: flex;
  78. align-items: center;
  79. justify-content: space-between;
  80. gap: ${space(1)};
  81. `;
  82. const ActorWrapper = styled('div')`
  83. display: flex;
  84. align-items: center;
  85. gap: ${space(1)};
  86. max-width: 85%;
  87. line-height: 1;
  88. `;
  89. const IconWrapper = styled('div')`
  90. display: flex;
  91. padding: ${space(0.25)};
  92. `;
  93. const ActorName = styled('div')`
  94. line-height: 1.2;
  95. ${p => p.theme.overflowEllipsis}
  96. `;
  97. const StyledSidebarSectionContent = styled(SidebarSection.Content)`
  98. ${AutoCompleteRoot} {
  99. display: block;
  100. }
  101. `;
  102. const StyledLoadingIndicator = styled(LoadingIndicator)`
  103. width: 24px;
  104. height: 24px;
  105. margin: 0 !important;
  106. `;