tasks-timers.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. import { useEffect, useState } from 'react';
  2. import { View, Row } from 'app/design/view';
  3. import { Text } from 'app/design/typography';
  4. import { BlockWrapper } from 'app/components/block-wrapper';
  5. import BrowseSimple from 'app/components/elements/browse-simple';
  6. import { NeoButton } from 'app/design/controls';
  7. import { components } from 'app/components/registry';
  8. import { fetcher } from 'app/lib/fetcher';
  9. import emitter, { EVENTS } from 'app/context/emitter';
  10. /** Payload is either `{ sections: [...] }` or a bare sections array. */
  11. function getSections(data) {
  12. if (Array.isArray(data?.sections)) return data.sections;
  13. if (Array.isArray(data)) return data;
  14. return [];
  15. }
  16. function removeTimer(prev, timerId) {
  17. const sections = getSections(prev)
  18. .map((section) => ({
  19. ...section,
  20. timers: (section.timers || []).filter((item) => item?.timer?.id !== timerId),
  21. }))
  22. .filter((section) => section.timers.length > 0);
  23. return { ...prev, sections };
  24. }
  25. export default function ElementTasksTimers({ data, blockWrapperProps }) {
  26. const NoContent = components['molecule']['no_content'];
  27. const [timersData, setTimersData] = useState(data);
  28. // Fresh block data (page reload) replaces the locally pruned copy.
  29. const [seenData, setSeenData] = useState(data);
  30. if (seenData !== data) {
  31. setSeenData(data);
  32. setTimersData(data);
  33. }
  34. useEffect(() => {
  35. // A logged / cleared timer disappears from the list without a refetch.
  36. const subscription = emitter.addListener(EVENTS.taskTimer, (event) => {
  37. if (event.action === 'log' || event.action === 'clear') {
  38. setTimersData((prev) => removeTimer(prev, event.id));
  39. }
  40. });
  41. return () => subscription.remove();
  42. }, []);
  43. const sections = getSections(timersData).filter((section) => section?.timers?.length > 0);
  44. const globalActions = timersData?.actions?.items || [];
  45. const runGlobalAction = async (item) => {
  46. const requestUrl = item?.data?.request_url;
  47. if (!requestUrl) return;
  48. await fetcher('/api.php?r=' + requestUrl);
  49. setTimersData((prev) => ({ ...prev, sections: [] }));
  50. };
  51. return (
  52. <BlockWrapper {...blockWrapperProps}>
  53. <View className="w-full gap-6">
  54. {globalActions.length > 0 && sections.length > 0 ? (
  55. <Row className="flex-wrap items-center justify-end gap-2 px-2">
  56. {globalActions.map((item) => (
  57. <NeoButton
  58. key={item.name}
  59. controlSize="small"
  60. label={item.title || item.name}
  61. onPress={() => runGlobalAction(item)}
  62. />
  63. ))}
  64. </Row>
  65. ) : null}
  66. {sections.length === 0 ? (
  67. <NoContent endpoint={{ request_url: data?.request_url, params: {} }} />
  68. ) : (
  69. sections.map((section, index) => (
  70. <View key={section.section_title || `section-${index}`} className="gap-2">
  71. {section.section_title ? (
  72. <Text className="px-2 text-base font-semibold tracking-tight text-muted-foreground">
  73. {section.section_title}
  74. </Text>
  75. ) : null}
  76. <BrowseSimple
  77. data={{
  78. data: section.timers,
  79. unit: 'general-content-list',
  80. module: 'bx_tasks_timer',
  81. }}
  82. blockWrapperProps={{ block: { designbox_id: null } }}
  83. />
  84. </View>
  85. ))
  86. )}
  87. </View>
  88. </BlockWrapper>
  89. );
  90. }