entity-poll.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { View, Row } from 'app/design/view';
  2. import Image, { POST_ENTRY_COVER_SIZES, POST_ENTRY_COVER_WIDTH_CAP } from 'app/ui/atoms/image';
  3. import { getCoverAspectClass } from 'app/lib/image/upload-settings';
  4. import { Text, H1 } from 'app/design/typography';
  5. import { appSetting, getYouTubeVideoId } from 'app/lib/util'
  6. import Video from 'app/ui/atoms/video';
  7. import Youtube from 'app/ui/molecules/content/youtube'
  8. import { fetcher } from 'app/lib/fetcher';
  9. import { useReducer } from 'react'
  10. import { useTranslation } from 'react-i18next'
  11. import RadioButton from 'app/ui/atoms/radiobutton';
  12. import { getColor } from 'app/components/elements/chart';
  13. import { NeoButton } from 'app/design/controls'
  14. import { BlockWrapper } from 'app/components/block-wrapper'
  15. function Results({ data }) {
  16. if (data) {
  17. const backgroundColor = appSetting('theme', 'profile_colors');
  18. const backgroundColor2 = backgroundColor.map(color => getColor(color));
  19. return (
  20. <View className="w-full ">
  21. {data.map((item2, index) => {
  22. return (
  23. <Row className="items-start w-full mt-2" key={'chk' + index}>
  24. <Row className="w-full min-h-12 border border-transparent rounded-lg overflow-hidden">
  25. <Row className='absolute w-full min-h-12 gap-x-0.5 '>
  26. <View className=" rounded-lg overflow-hidden " style={{ width: item2.width, backgroundColor: backgroundColor2[index] }}></View>
  27. </Row>
  28. <View className="min-h-12 rounded-lg" style={{ width: 12, backgroundColor: backgroundColor2[index] }}></View>
  29. <View className='w-full items-center justify-center py-1.5 px-2 '>
  30. <Text numberOfLines={10} className="flex-wrap w-full text-foreground font-semibold text-sm ">
  31. {item2.title}
  32. </Text>
  33. <Text className="font-medium flex-wrap w-full text-popover-foreground text-xs">
  34. {item2.width} ({item2.votes.count} votes)
  35. </Text>
  36. </View>
  37. </Row>
  38. </Row>
  39. )
  40. })}
  41. </View>
  42. );
  43. }
  44. }
  45. export function PollItem({ data, showTitle, onDelete, disabled = false, results_url = '/api.php?r=bx_polls/get_block_results' }) {
  46. const { t } = useTranslation();
  47. const initialState = {
  48. isShowResults: data.is_performed,
  49. isVoted: data.is_performed,
  50. results: data.results,
  51. value: data.value
  52. };
  53. function reducer(state, action) {
  54. switch (action.type) {
  55. case 'SHOW_RESULTS':
  56. return { ...state, isShowResults: true };
  57. case 'TOGGLE_RESULTS':
  58. return { ...state, isShowResults: !state.isShowResults };
  59. case 'VOTE':
  60. return { ...state, isShowResults: true, isVoted: true, value: action.value };
  61. case 'SET_RESULTS':
  62. return { ...state, results: action.payload };
  63. default:
  64. return state;
  65. }
  66. }
  67. const [state, dispatch] = useReducer(reducer, initialState);
  68. const vote = async (value, results_url) => {
  69. dispatch({ type: 'VOTE', value: value });
  70. const sRequest = `/api.php?r=system/do/TemplVoteServices&params[]={"s":"${data.object}","o":${value},"value":1}`;
  71. await fetcher(sRequest);
  72. const sRequest1 = `${results_url}/&params[]=${data.id}`;
  73. const sResponse1 = await fetcher(sRequest1);
  74. if (sResponse1.data)
  75. dispatch({ type: 'SET_RESULTS', payload: sResponse1.data });
  76. };
  77. const totalVotes = state?.results ? state.results.reduce((acc, item) => acc + item.votes.count, 0) : 0;
  78. return (
  79. <View className='w-full p-3 rounded-xl bg-muted/50 gap-1.5'>
  80. <Row className='items-center justify-between w-full gap-x-2 flex-wrap '>
  81. {showTitle && <Text className="text-foreground p-1 rounded-xl text-lg tracking-tight font-semibold">{data.title}</Text>}
  82. {(!data.is_hidden_results && totalVotes > 0) && (
  83. <NeoButton
  84. label={state.isShowResults ? "Show poll" : "Show results"}
  85. controlSize="small"
  86. classNames={{ root: 'self-center' }}
  87. onPress={() => dispatch({ type: 'TOGGLE_RESULTS' })}
  88. />
  89. )}
  90. {onDelete && (<NeoButton onPress={() => { onDelete(data.id) }} image="X" borderShape="circle" controlSize="small" accessibilityLabel={t('Delete')} expoUI={false} classNames={{ root: 'self-center' }} />)}
  91. </Row>
  92. {state.isShowResults && <Results data={state.results} />}
  93. {!!data.subentries && !state.isShowResults && data.subentries.map((item2, index) => (
  94. <Row key={`lbl-${index}`} className={` items-center border border-border/60 bg-card web:hover:border-ring web:hover:ring rounded-lg ${state.isVoted ? 'opacity-50 web:hover:ring-0' : ' web:active:bg-muted'}`}>
  95. <RadioButton
  96. value={item2.entry_id}
  97. status={item2.id == state.value ? 'checked' : 'unchecked'}
  98. title={item2.title}
  99. disabled={state.isVoted || disabled}
  100. onPress={() => vote(item2.id, results_url)}
  101. />
  102. </Row>
  103. ))}
  104. </View>
  105. );
  106. }
  107. export default function ElementEntityPoll({ data, block, blockWrapperProps }) {
  108. const videoId = data.video_embed && getYouTubeVideoId(data.video_embed) || null;
  109. return (
  110. <BlockWrapper {...blockWrapperProps}><View className="w-full">
  111. <View className="w-full">
  112. {(!!data.video?.src_mp4) && <View className='w-full aspect-video rounded-xl overflow-hidden lg:mt-6'>
  113. <Video poster={data.video.src_poster} src={data.video.src_mp4} cover={true} controls={true} muted={"muted"} />
  114. </View>}
  115. {(videoId) && <View className='w-full aspect-video rounded-xl overflow-hidden lg:mt-6'>
  116. <Youtube videoId={videoId} size={3} />
  117. </View>}
  118. {(!!data.image && !data.video) && (
  119. <View className={`w-full ${getCoverAspectClass(block?.module)} rounded-xl overflow-hidden lg:mt-6`}>
  120. <Image
  121. {...data.image}
  122. alt={data.title}
  123. className="u-cover"
  124. view="cover"
  125. sizes={POST_ENTRY_COVER_SIZES}
  126. optimizedWidthCap={POST_ENTRY_COVER_WIDTH_CAP}
  127. />
  128. </View>
  129. )}
  130. <View className={"mx-auto w-full"}>
  131. <H1 className="font-bold tracking-tight text-popover-foreground ">{data.title}</H1>
  132. </View>
  133. <PollItem data={data} />
  134. </View>
  135. </View>
  136. </BlockWrapper>
  137. );
  138. }