project-list-root.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. import {
  2. ProjectListProvider,
  3. useProjectListContext,
  4. } from '../context/project-list-context'
  5. import { ColorPickerProvider } from '../context/color-picker-context'
  6. import * as eventTracking from '../../../infrastructure/event-tracking'
  7. import { useTranslation } from 'react-i18next'
  8. import useWaitForI18n from '../../../shared/hooks/use-wait-for-i18n'
  9. import CurrentPlanWidget from './current-plan-widget/current-plan-widget'
  10. import NewProjectButton from './new-project-button'
  11. import ProjectListTable from './table/project-list-table'
  12. import SurveyWidget from './survey-widget'
  13. import WelcomeMessage from './welcome-message'
  14. import LoadingBranded from '../../../shared/components/loading-branded'
  15. import SystemMessages from '../../../shared/components/system-messages'
  16. import UserNotifications from './notifications/user-notifications'
  17. import SearchForm from './search-form'
  18. import ProjectsDropdown from './dropdown/projects-dropdown'
  19. import SortByDropdown from './dropdown/sort-by-dropdown'
  20. import ProjectTools from './table/project-tools/project-tools'
  21. import ProjectListTitle from './title/project-list-title'
  22. import Sidebar from './sidebar/sidebar'
  23. import LoadMore from './load-more'
  24. import { useEffect } from 'react'
  25. import withErrorBoundary from '../../../infrastructure/error-boundary'
  26. import { GenericErrorBoundaryFallback } from '../../../shared/components/generic-error-boundary-fallback'
  27. import { SplitTestProvider } from '@/shared/context/split-test-context'
  28. import OLCol from '@/features/ui/components/ol/ol-col'
  29. import { bsVersion } from '@/features/utils/bootstrap-5'
  30. import classnames from 'classnames'
  31. import Notification from '@/shared/components/notification'
  32. import OLRow from '@/features/ui/components/ol/ol-row'
  33. import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
  34. import { TableContainer } from '@/features/ui/components/bootstrap-5/table'
  35. function ProjectListRoot() {
  36. const { isReady } = useWaitForI18n()
  37. if (!isReady) {
  38. return null
  39. }
  40. return <ProjectListRootInner />
  41. }
  42. export function ProjectListRootInner() {
  43. return (
  44. <ProjectListProvider>
  45. <ColorPickerProvider>
  46. <SplitTestProvider>
  47. <ProjectListPageContent />
  48. </SplitTestProvider>
  49. </ColorPickerProvider>
  50. </ProjectListProvider>
  51. )
  52. }
  53. function ProjectListPageContent() {
  54. const {
  55. totalProjectsCount,
  56. error,
  57. isLoading,
  58. loadProgress,
  59. searchText,
  60. setSearchText,
  61. selectedProjects,
  62. filter,
  63. tags,
  64. selectedTagId,
  65. } = useProjectListContext()
  66. const selectedTag = tags.find(tag => tag._id === selectedTagId)
  67. useEffect(() => {
  68. eventTracking.sendMB('loads_v2_dash', {})
  69. }, [])
  70. const { t } = useTranslation()
  71. const tableTopArea = (
  72. <div
  73. className={classnames(
  74. 'pt-2',
  75. 'pb-3',
  76. bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
  77. )}
  78. >
  79. <div className="clearfix">
  80. <NewProjectButton
  81. id="new-project-button-projects-table"
  82. className="pull-left me-2"
  83. showAddAffiliationWidget
  84. />
  85. <SearchForm
  86. inputValue={searchText}
  87. setInputValue={setSearchText}
  88. filter={filter}
  89. selectedTag={selectedTag}
  90. className="overflow-hidden"
  91. />
  92. </div>
  93. </div>
  94. )
  95. return isLoading ? (
  96. <div className="loading-container">
  97. <LoadingBranded loadProgress={loadProgress} label={t('loading')} />
  98. </div>
  99. ) : (
  100. <>
  101. <SystemMessages />
  102. <div
  103. className={classnames(
  104. 'project-list-wrapper',
  105. bsVersion({ bs3: 'clearfix container mx-0 px-0' })
  106. )}
  107. >
  108. {totalProjectsCount > 0 ? (
  109. <>
  110. <Sidebar />
  111. <div className="project-list-main-react">
  112. {error ? <DashApiError /> : ''}
  113. <OLRow>
  114. <OLCol>
  115. <UserNotifications />
  116. </OLCol>
  117. </OLRow>
  118. <div className="project-list-header-row">
  119. <ProjectListTitle
  120. filter={filter}
  121. selectedTag={selectedTag}
  122. selectedTagId={selectedTagId}
  123. className={classnames(
  124. 'text-truncate',
  125. bsVersion({
  126. bs5: 'd-none d-md-block',
  127. bs3: 'hidden-xs',
  128. })
  129. )}
  130. />
  131. <div className="project-tools">
  132. <div
  133. className={bsVersion({
  134. bs5: 'd-none d-md-block',
  135. bs3: 'hidden-xs',
  136. })}
  137. >
  138. {selectedProjects.length === 0 ? (
  139. <CurrentPlanWidget />
  140. ) : (
  141. <ProjectTools />
  142. )}
  143. </div>
  144. <div
  145. className={bsVersion({
  146. bs5: 'd-md-none',
  147. bs3: 'visible-xs',
  148. })}
  149. >
  150. <CurrentPlanWidget />
  151. </div>
  152. </div>
  153. </div>
  154. <OLRow
  155. className={bsVersion({
  156. bs5: 'd-none d-md-block',
  157. bs3: 'hidden-xs',
  158. })}
  159. >
  160. <OLCol lg={7}>
  161. <SearchForm
  162. inputValue={searchText}
  163. setInputValue={setSearchText}
  164. filter={filter}
  165. selectedTag={selectedTag}
  166. />
  167. </OLCol>
  168. </OLRow>
  169. <div
  170. className={classnames(
  171. 'project-list-sidebar-survey-wrapper',
  172. bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
  173. )}
  174. >
  175. <SurveyWidget />
  176. </div>
  177. <div
  178. className={classnames(
  179. 'mt-1',
  180. bsVersion({ bs5: 'd-md-none', bs3: 'visible-xs' })
  181. )}
  182. >
  183. <div
  184. role="toolbar"
  185. className="projects-toolbar"
  186. aria-label={t('projects')}
  187. >
  188. <ProjectsDropdown />
  189. <SortByDropdown />
  190. </div>
  191. </div>
  192. <OLRow className="row-spaced">
  193. <OLCol>
  194. <BootstrapVersionSwitcher
  195. bs3={
  196. <div className="card project-list-card">
  197. {tableTopArea}
  198. <ProjectListTable />
  199. </div>
  200. }
  201. bs5={
  202. <TableContainer bordered>
  203. {tableTopArea}
  204. <ProjectListTable />
  205. </TableContainer>
  206. }
  207. />
  208. </OLCol>
  209. </OLRow>
  210. <OLRow className="row-spaced">
  211. <OLCol>
  212. <LoadMore />
  213. </OLCol>
  214. </OLRow>
  215. </div>
  216. </>
  217. ) : (
  218. <div className="project-list-welcome-wrapper">
  219. {error ? <DashApiError /> : ''}
  220. <OLRow className="row-spaced mx-0">
  221. <OLCol
  222. md={{ span: 10, offset: 1 }}
  223. lg={{ span: 8, offset: 2 }}
  224. className="project-list-empty-col"
  225. >
  226. <OLRow>
  227. <OLCol>
  228. <UserNotifications />
  229. </OLCol>
  230. </OLRow>
  231. <WelcomeMessage />
  232. </OLCol>
  233. </OLRow>
  234. </div>
  235. )}
  236. </div>
  237. </>
  238. )
  239. }
  240. function DashApiError() {
  241. const { t } = useTranslation()
  242. return (
  243. <OLRow className="row-spaced">
  244. <OLCol
  245. xs={{ span: 8, offset: 2 }}
  246. bs3Props={{ xs: 8, xsOffset: 2 }}
  247. aria-live="polite"
  248. >
  249. <div className="notification-list">
  250. <Notification
  251. content={t('generic_something_went_wrong')}
  252. type="error"
  253. />
  254. </div>
  255. </OLCol>
  256. </OLRow>
  257. )
  258. }
  259. export default withErrorBoundary(ProjectListRoot, GenericErrorBoundaryFallback)