diff --git a/apps/admin-server/src/pages/projects/[project]/settings/voting.tsx b/apps/admin-server/src/pages/projects/[project]/settings/voting.tsx index ab833b524..b8c36e1c1 100644 --- a/apps/admin-server/src/pages/projects/[project]/settings/voting.tsx +++ b/apps/admin-server/src/pages/projects/[project]/settings/voting.tsx @@ -39,8 +39,8 @@ const formSchema = z.object({ 'likes', 'count', 'budgeting', - 'countPerTheme', - 'budgetingPerTheme', + 'countPerTag', + 'budgetingPerTag', ]).optional(), minResources: z.coerce.number().gt(-1).optional(), maxResources: z.coerce.number().gt(-1).optional(), @@ -261,10 +261,10 @@ useEffect(() => { Likes Aantallen Begroten - + Aantal per tag(groep) - + Begroten per tag(groep) @@ -273,14 +273,14 @@ useEffect(() => { )} /> - {(fieldValue === 'countPerTheme' || fieldValue === 'count') && ( + {(fieldValue === 'countPerTag' || fieldValue === 'count') && ( ( - Wat is de minimum hoeveelheid inzendingen waar iemand op kan stemmen? + Wat is de minimum hoeveelheid inzendingen{form.watch("voteType") === "countPerTag" ? " per tag(groep) " : " "}waar iemand op kan stemmen? @@ -291,14 +291,14 @@ useEffect(() => { )} /> )} - {(fieldValue === 'countPerTheme' || fieldValue === 'count') && ( + {(fieldValue === 'countPerTag' || fieldValue === 'count') && ( ( - Wat is de maximum hoeveelheid inzendingen waar iemand op kan stemmen? + Wat is de maximum hoeveelheid inzendingen{form.watch("voteType") === "countPerTag" ? " per tag(groep) " : " "}waar iemand op kan stemmen? @@ -309,15 +309,15 @@ useEffect(() => { )} /> )} - {(fieldValue === 'budgetingPerTheme' || fieldValue === 'budgeting') && ( + {(fieldValue === 'budgetingPerTag' || fieldValue === 'budgeting') && ( ( - Wat is het minimum budget? - + Wat is het minimum budget{form.watch("voteType") === "budgetingPerTag" ? " per tag(groep)" : " "}? + @@ -327,15 +327,15 @@ useEffect(() => { )} /> )} - {(fieldValue === 'budgetingPerTheme' || fieldValue === 'budgeting') && ( + {(fieldValue === 'budgetingPerTag' || fieldValue === 'budgeting') && ( ( - Wat is het maximum budget? - + Wat is het maximum budget{form.watch("voteType") === "budgetingPerTag" ? " per tag(groep)" : " "}? + diff --git a/apps/admin-server/src/pages/projects/[project]/widgets/begrootmodule/[id]/display.tsx b/apps/admin-server/src/pages/projects/[project]/widgets/begrootmodule/[id]/display.tsx index d8e2f5990..d45b6fe6c 100644 --- a/apps/admin-server/src/pages/projects/[project]/widgets/begrootmodule/[id]/display.tsx +++ b/apps/admin-server/src/pages/projects/[project]/widgets/begrootmodule/[id]/display.tsx @@ -216,7 +216,7 @@ export default function BegrootmoduleDisplay( items={[{ value: 1 }, { value: 2 }, { value: 3 }]} /> - { (voteType === 'countPerTheme' || voteType === 'budgetingPerTheme') && ( + { (voteType === 'countPerTag' || voteType === 'budgetingPerTag') && ( <> Kies jouw favoriete inzendingen per thema!
  1. Selecteer hieronder een thema om de inzendingen voor dat thema te bekijken
  2. -
  3. Kies jouw favoriete inzendingen voor dat thema${voteType === 'budgetingPerTheme' ? 'binnen het beschikbare budget' : ''}
  4. +
  5. Kies jouw favoriete inzendingen voor dat thema${voteType === 'budgetingPerTag' ? 'binnen het beschikbare budget' : ''}
  6. Ga naar het volgende thema om hetzelfde te doen
  7. Klaar en tevreden? In stap 3 vul je ter controle de stemcode in. Tot slot verstuur je in stap 4 je stem
`, @@ -84,7 +84,7 @@ export default function BegrootmoduleExplanation( onSubmit={form.handleSubmit(onSubmit)} className="lg:w-1/2 grid grid-cols-1 gap-4"> - { (voteType === 'countPerTheme' || voteType === 'budgetingPerTheme') && ( + { (voteType === 'countPerTag' || voteType === 'budgetingPerTag') && ( = req.project.config.votes.minBudget && budget <= req.project.config.votes.maxBudget )) { err = createError(400, 'Budget klopt niet'); } - if (!( req.votes.length >= req.project.config.votes.minResources && req.votes.length <= req.project.config.votes.maxResources )) { - err = createError(400, 'Aantal resources klopt niet'); - } if (err) { if (transaction) { return transaction.rollback() @@ -357,10 +354,10 @@ router.route('/*') } }) - // validaties voor voteType=countPerTheme + // validaties voor voteType=countPerTag .post(function(req, res, next) { let transaction = res.locals.transaction - if (req.project.config.votes.voteType != 'countPerTheme') return next(); + if (req.project.config.votes.voteType != 'countPerTag') return next(); let themes = req.project.config.votes.themes || []; let totalNoOfVotes = 0; req.votes.forEach((vote) => { @@ -400,10 +397,10 @@ router.route('/*') } }) - // validaties voor voteType=budgetingPerTheme + // validaties voor voteType=budgetingPerTag .post(function(req, res, next) { let transaction = res.locals.transaction - if (req.project.config.votes.voteType != 'budgetingPerTheme') return next(); + if (req.project.config.votes.voteType != 'budgetingPerTag') return next(); let themes = req.project.config.votes.themes || []; req.votes.forEach((vote) => { let resource = req.resources.find(resource => resource.id == vote.resourceId); @@ -458,9 +455,9 @@ router.route('/*') break; case 'count': - case 'countPerTheme': + case 'countPerTag': case 'budgeting': - case 'budgetingPerTheme': + case 'budgetingPerTag': req.votes.map( vote => actions.push({ action: 'create', vote: vote}) ); req.existingVotes.map( vote => actions.push({ action: 'delete', vote: vote}) ); break; diff --git a/packages/stem-begroot/src/stem-begroot.tsx b/packages/stem-begroot/src/stem-begroot.tsx index fda069766..5b46aa593 100644 --- a/packages/stem-begroot/src/stem-begroot.tsx +++ b/packages/stem-begroot/src/stem-begroot.tsx @@ -24,6 +24,17 @@ import '@utrecht/design-tokens/dist/root.css'; import { Button, Heading } from '@utrecht/component-library-react'; import useTags from "@openstad-headless/admin-server/src/hooks/use-tag"; +type TagTypeSingle = { + min: number; + max: number; + current: number; + selectedResources: Array; +} + +export type TagType = { + [key: string]: TagTypeSingle; +} + export type StemBegrootWidgetProps = BaseProps & ProjectSettingProps & { step0?: string; @@ -99,7 +110,7 @@ function StemBegroot({ type: '' }); - const startingStep = props?.votes?.voteType === "countPerTheme" || props?.votes?.voteType === "budgetPerTheme" ? -1 : 0; + const startingStep = props?.votes?.voteType === "countPerTag" || props?.votes?.voteType === "budgetingPerTag" ? -1 : 0; const [openDetailDialog, setOpenDetailDialog] = React.useState(false); const [resourceDetailIndex, setResourceDetailIndex] = useState(0); @@ -107,8 +118,8 @@ function StemBegroot({ const [lastStep, setLastStep] = useState(0); const { data: currentUser } = datastore.useCurrentUser({ ...props }); const [navAfterLogin, setNavAfterLogin] = useState(); - const [shouldReloadSelectedResources, setReloadSelectedResources] = - useState(false); + // const [shouldReloadSelectedResources, setReloadSelectedResources] = + // useState(false); const [activeTagTab, setActiveTagTab] = useState(''); @@ -124,6 +135,8 @@ function StemBegroot({ .filter((t) => t && !isNaN(+t.trim())) .map((t) => Number.parseInt(t)); + const [tagCounter, setTagCounter] = useState>([]); + const [tags, setTags] = useState(tagIdsToLimitResourcesTo); const [sort, setSort] = useState(props.defaultSorting || undefined); const [search, setSearch] = useState(); @@ -143,15 +156,28 @@ function StemBegroot({ // Replace with type when available from datastore const [selectedResources, setSelectedResources] = useState([]); - const selectedBudgets: Array = selectedResources.map( - (r) => r.budget || 0 - ); const session = new SessionStorage({ projectId: props.projectId }); - const budgetUsed: number = selectedResources.reduce( - (total, cv) => total + cv.budget, - 0 - ); + + const selectedBudgets: Array = (() => { + if (props.votes.voteType === "budgetingPerTag") { + const activeTag = tagCounter.find(tagObj => tagObj[activeTagTab]); + if (!activeTag) return []; + + return activeTag[activeTagTab].selectedResources.map((r) => r.budget || 0); + } + + return selectedResources.map((r) => r.budget || 0) + })(); + + const budgetUsed = (() => { + if (props.votes.voteType === "budgetingPerTag") { + const activeTag = tagCounter.find(tagObj => tagObj[activeTagTab]); + return activeTag ? activeTag[activeTagTab].current : 0; + } + + return selectedResources.reduce((total, resource) => total + resource.budget, 0); + })(); const usedBudgetList = ( { - let pending = session.get('osc-resource-vote-pending'); - if ( - pending && - resources?.records?.length > 0 && - selectedResources.length === 0 - ) { - setReloadSelectedResources(true); + if (props.votes.voteType === "countPerTag" || props.votes.voteType === "budgetingPerTag") { + const pendingPerTag = session.get('osc-resource-vote-pending-per-tag'); + + console.log( 'pendingPerTag', pendingPerTag ); + + if (pendingPerTag) { + setTagCounter((prevTagCounter) => + prevTagCounter.map((tagObj) => { + const tagName = Object.keys(tagObj)[0]; + if (pendingPerTag[tagName]) { + const selectedResourceIds = Object.keys(pendingPerTag[tagName]).map(Number); + + const resourcesThatArePending: Array = resources?.records?.filter( + (r: any) => selectedResourceIds && selectedResourceIds.includes(r.id) + ) || []; + + return { + [tagName]: { + ...tagObj[tagName], + selectedResources: resourcesThatArePending, + current: selectedResourceIds.length, + }, + }; + } + + return tagObj; + }) + ); + } + } else { + let pending = session.get('osc-resource-vote-pending'); + if ( + pending && + resources?.records?.length > 0 && + selectedResources.length === 0 + ) { + setSelectedResources(resources.records.filter((r: any) => pending[r.id])); + } } }, [resources?.records]); - // if shouldReloadSelectedResources reload the selectedresources from the pendings - useEffect(() => { - let pending = session.get('osc-resource-vote-pending'); - if (shouldReloadSelectedResources) { - const resourcesThatArePending: Array = - resources?.records?.filter((r: any) => pending && r.id in pending) || - []; - setSelectedResources(resourcesThatArePending); - setReloadSelectedResources(false); - } - }, [shouldReloadSelectedResources]); - // Force the logged in user to skip step 2: first time entering 'stemcode' useEffect(() => { - let pending = session.get('osc-resource-vote-pending'); + let pending; + + if (props.votes.voteType === "countPerTag" || props.votes.voteType === "budgetingPerTag") { + pending = session.get('osc-resource-vote-pending-per-tag'); + } else { + pending = session.get('osc-resource-vote-pending'); + } if ( pending && @@ -219,17 +270,34 @@ function StemBegroot({ ) { setCurrentStep(3); } - }, [currentUser, currentStep, selectedResources]); + }, [currentUser, currentStep, selectedResources, tagCounter]); function prepareForVote(e: React.MouseEvent | null) { if (e) e.stopPropagation(); - const resourcesToVoteFor: { [key: string]: any } = {}; - (selectedResources.length ? selectedResources : []).forEach( - (resource: any) => { - resourcesToVoteFor[resource.id] = 'yes'; - } - ); - session.set('osc-resource-vote-pending', resourcesToVoteFor); + + if (props.votes.voteType !== 'countPerTag' && props.votes.voteType !== 'budgetingPerTag') { + const resourcesToVoteFor: { [key: string]: any } = {}; + (selectedResources.length ? selectedResources : []).forEach( + (resource: any) => { + resourcesToVoteFor[resource.id] = 'yes'; + } + ); + session.set('osc-resource-vote-pending', resourcesToVoteFor); + } else { + const resourcesToVoteForPerTag: { [tag: string]: { [key: string]: any } } = {}; + + tagCounter.forEach((tagObj) => { + const tagName = Object.keys(tagObj)[0]; + const selectedResourcesForTag = tagObj[tagName].selectedResources; + + resourcesToVoteForPerTag[tagName] = {}; + selectedResourcesForTag.forEach((resource: any) => { + resourcesToVoteForPerTag[tagName][resource.id] = 'yes'; + }); + }); + + session.set('osc-resource-vote-pending-per-tag', resourcesToVoteForPerTag); + } } async function doVote(resources: Array) { @@ -248,8 +316,16 @@ function StemBegroot({ } } - const isInSelected = (resource: { id: number }) => - selectedResources.find((r) => r.id === resource.id); + const isInSelected = (resource: { id: number }) => { + if (props.votes.voteType === "countPerTag" || props.votes.voteType === "budgetingPerTag") { + const activeTag = tagCounter.find(tagObj => tagObj[activeTagTab]); + if (!activeTag) return false; + + return activeTag[activeTagTab].selectedResources.some((res) => res.id === resource.id); + } + + return selectedResources.some((r) => r.id === resource.id); + }; const getOriginalResourceUrl = (resource: { extraData: { originalId: number | string }; @@ -268,6 +344,23 @@ function StemBegroot({ // For now only support budgeting and count const resourceSelectable = (resource: { id: number; budget: number }) => { + if (props.votes.voteType === "countPerTag" || props.votes.voteType === "budgetingPerTag") { + const activeTag = tagCounter.find(tagObj => tagObj[activeTagTab]); + if (!activeTag) return false; + + if (activeTag[activeTagTab].selectedResources.some((r) => r.id === resource.id)) { + return true; + } + + if (props.votes.voteType === "countPerTag") { + return activeTag[activeTagTab].current < activeTag[activeTagTab].max; + } else if (props.votes.voteType === "budgetingPerTag") { + return ( + activeTag[activeTagTab].current + resource.budget <= activeTag[activeTagTab].max + ); + } + } + if (props.votes.voteType === 'budgeting') { return ( isInSelected(resource) || @@ -283,7 +376,7 @@ function StemBegroot({ }; const createItemBtnString = (resource: { id: number; budget: number }) => { - if (props.votes.voteType === 'budgeting') { + if (props.votes.voteType === 'budgeting' || props.votes.voteType === "budgetingPerTag") { return !isInSelected(resource) && !(resource.budget <= props.votes.maxBudget - budgetUsed) ? notEnoughBudgetText @@ -311,6 +404,39 @@ function StemBegroot({ ? allTags.filter((tag: { type: string }) => tag.type === props.tagTypeTag).map((tag: {name: string}) => tag.name) : props?.tagTypeTagGroup || []; + useEffect(() => { + if (props?.votes?.voteType === "countPerTag" || props?.votes?.voteType === "budgetingPerTag") { + if (tagsToDisplay.length > 0) { + const tagCounter: Array = tagsToDisplay.map((tag: string) => { + return { + [tag]: { + min: 1, + max: props?.votes?.voteType === "countPerTag" ? props.votes.maxResources || 1 : props.votes.maxBudget || 1, + current: 0, + selectedResources: [] + } + } + }); + + setTagCounter(tagCounter); + } + } + }, [tagsToDisplay]); + + useEffect(() => { + if (props?.votes?.voteType === "countPerTag" || props?.votes?.voteType === "budgetingPerTag") { + if (activeTagTab) { + const activeTag = tagCounter.find(tagObj => tagObj[activeTagTab]); + if (activeTag) { + setSelectedResources(activeTag[activeTagTab].selectedResources); + } + } + } + }, [activeTagTab]); + + const [filteredResources, setFilteredResources] = useState([]); + const resourcesToUse = filteredResources.length ? filteredResources : resources?.records || []; + return ( <> { - session.remove('osc-resource-vote-pending'); + if (props.votes.voteType === "countPerTag" || props.votes.voteType === "budgetingPerTag") { + session.remove('osc-resource-vote-pending-per-tag'); + + if (activeTagTab) { + const activeTag = tagCounter.find(tagObj => tagObj[activeTagTab]); + if (activeTag) { + const selectedResources = activeTag[activeTagTab].selectedResources; + const resourceInTagList = selectedResources.find((r) => r.id === resource.id); + + if (resourceInTagList) { + activeTag[activeTagTab].selectedResources = selectedResources.filter((r) => r.id !== resource.id); + activeTag[activeTagTab].current -= props.votes.voteType === "budgetingPerTag" ? resource.budget : 1; + } else { + activeTag[activeTagTab].selectedResources.push(resource); + activeTag[activeTagTab].current += props.votes.voteType === "budgetingPerTag" ? resource.budget : 1; + } - const resourceInBudgetList = selectedResources.find( - (r) => r.id === resource.id - ); + setTagCounter([...tagCounter]); + } + } + } else { + session.remove('osc-resource-vote-pending'); - if (resourceInBudgetList) { - setSelectedResources( - selectedResources.filter((r) => r.id !== resource.id) + const resourceInBudgetList = selectedResources.find( + (r) => r.id === resource.id ); - } else { - setSelectedResources([...selectedResources, resource]); + + if (resourceInBudgetList) { + setSelectedResources( + selectedResources.filter((r) => r.id !== resource.id) + ); + } else { + setSelectedResources([...selectedResources, resource]); + } } }} resourceDetailIndex={resourceDetailIndex} @@ -353,7 +501,7 @@ function StemBegroot({ /> - {props.votes.voteType === 'budgeting' ? + {(props.votes.voteType === 'budgeting' || props?.votes?.voteType === 'budgetingPerTag' ) ? <> {usedBudgetList} @@ -396,32 +544,83 @@ function StemBegroot({ allResourceInList={resources?.records} selectedResources={selectedResources} maxNrOfResources={props.votes.maxResources || 0} - typeIsBudgeting={props.votes.voteType === 'budgeting'} - onSelectedResourceRemove={(r) => { + typeIsBudgeting={props.votes.voteType === 'budgeting' || props.votes.voteType === 'budgetingPerTag'} + tagsToDisplay={tagsToDisplay} + activeTagTab={activeTagTab} + setActiveTagTab={setActiveTagTab} + typeIsPerTag={props?.votes?.voteType === "countPerTag" || props?.votes?.voteType === "budgetingPerTag"} + onSelectedResourceRemove={(resource: {id: number, budget: number}) => { session.remove('osc-resource-vote-pending'); + session.remove('osc-resource-vote-pending-per-tag'); + + if (props?.votes?.voteType === "countPerTag" || props?.votes?.voteType === "budgetingPerTag") { + setTagCounter(prevTagCounter => { + return prevTagCounter.map(tagObj => { + if (tagObj[activeTagTab]) { + const updatedSelectedResources = tagObj[activeTagTab].selectedResources.filter((selectedResource: {id: number}) => selectedResource.id !== resource.id); + const updatedCurrentCount = tagObj[activeTagTab].current - (props.votes.voteType === "budgetingPerTag" ? resource.budget : 1); + + return { + [activeTagTab]: { + ...tagObj[activeTagTab], + selectedResources: updatedSelectedResources, + current: updatedCurrentCount + } + }; + } + return tagObj; + }); + }); + } + setSelectedResources( - selectedResources.filter((resource) => resource.id !== r.id) + selectedResources.filter((r) => r.id !== resource.id) ); }} decideCanAddMore={() => { - let notUsedResources = resources?.records.filter( - (allR: { id: number }) => - !selectedResources.find( - (selectedR) => allR.id === selectedR.id - ) - ); + if (props?.votes?.voteType === "countPerTag" || props?.votes?.voteType === "budgetingPerTag") { + const activeTagData = tagCounter.find(tagObj => tagObj[activeTagTab]); + if (!activeTagData) return false; - const canAddMore = - props.votes.voteType === 'budgeting' + const activeTag = activeTagData[activeTagTab]; + const maxLimit = activeTag.max; + const currentCount = activeTag.current; + + if (props.votes.voteType === "countPerTag") { + return currentCount < maxLimit; + } + + if (props.votes.voteType === "budgetingPerTag") { + let notUsedResources = filteredResources.filter( + (allR: { id: number }) => + !selectedResources.find( + (selectedR) => allR.id === selectedR.id + ) + ); + + return notUsedResources.some( + (r: { budget: number }) => + r.budget <= (maxLimit - currentCount) + ); + } + } else { + let notUsedResources = resources?.records.filter( + (allR: { id: number }) => + !selectedResources.find( + (selectedR) => allR.id === selectedR.id + ) + ); + + return props.votes.voteType === 'budgeting' ? notUsedResources.some( (r: { budget: number }) => r.budget < props.votes.maxBudget - budgetUsed ) : Math.max( - props.votes.maxResources - selectedResources.length, - 0 - ) > 0; - return canAddMore; + props.votes.maxResources - selectedResources.length, + 0 + ) > 0; + } }} /> @@ -440,7 +639,9 @@ function StemBegroot({ selectedResources={selectedResources} maxBudget={props.votes.maxBudget} maxNrOfResources={props.votes.maxResources || 0} - typeIsBudgeting={props.votes.voteType === 'budgeting'} + typeIsBudgeting={props.votes.voteType === 'budgeting' || props.votes.voteType === 'budgetingPerTag'} + typeIsPerTag={props?.votes?.voteType === "countPerTag" || props?.votes?.voteType === "budgetingPerTag"} + tagCounter={tagCounter} showInfoMenu={props.showInfoMenu} /> @@ -471,7 +672,7 @@ function StemBegroot({ ) : null}
- {currentStep > 0 && currentStep < 4 ? ( + {currentStep > 0 && currentStep < 3 ? ( ) : null}
@@ -577,17 +850,34 @@ function StemBegroot({ resourceListColumns={resourceListColumns || 3} onResourcePrimaryClicked={(resource) => { session.remove('osc-resource-vote-pending'); + session.remove('osc-resource-vote-pending-per-tag'); + + let newTagCounter = [...tagCounter]; + + if (props.votes.voteType === "countPerTag" || props.votes.voteType === "budgetingPerTag") { + newTagCounter = newTagCounter.map((tagObj) => { + if (tagObj[activeTagTab]) { + if (isInSelected(resource)) { + tagObj[activeTagTab].current -= props.votes.voteType === "budgetingPerTag" ? resource.budget : 1; + tagObj[activeTagTab].selectedResources = tagObj[activeTagTab].selectedResources.filter((selectedResource: {id: number}) => selectedResource.id !== resource.id); + } else { + tagObj[activeTagTab].current += props.votes.voteType === "budgetingPerTag" ? resource.budget : 1; + tagObj[activeTagTab].selectedResources.push(resource); + } + } + return tagObj; + }); - const resourceIndex = selectedResources.findIndex( - (r) => r.id === resource.id - ); - - if (resourceIndex === -1) { - setSelectedResources([...selectedResources, resource]); + setTagCounter(newTagCounter); } else { - const resources = [...selectedResources]; - resources.splice(resourceIndex, 1); - setSelectedResources(resources); + const resourceIndex = selectedResources.findIndex((r) => r.id === resource.id); + if (resourceIndex === -1) { + setSelectedResources([...selectedResources, resource]); + } else { + const updatedResources = [...selectedResources]; + updatedResources.splice(resourceIndex, 1); + setSelectedResources(updatedResources); + } } }} statusIdsToLimitResourcesTo={statusIdsToLimitResourcesTo || []} @@ -595,6 +885,11 @@ function StemBegroot({ sort={sort} allTags={allTags} tags={tags} + activeTagTab={activeTagTab} + setFilteredResources={setFilteredResources} + filteredResources={filteredResources} + voteType={props?.votes?.voteType || 'likes'} + typeSelector={typeSelector} /> diff --git a/packages/stem-begroot/src/step-1/begroot-budget-list/stem-begroot-budget-list.tsx b/packages/stem-begroot/src/step-1/begroot-budget-list/stem-begroot-budget-list.tsx index cbd614c84..052481f4d 100644 --- a/packages/stem-begroot/src/step-1/begroot-budget-list/stem-begroot-budget-list.tsx +++ b/packages/stem-begroot/src/step-1/begroot-budget-list/stem-begroot-budget-list.tsx @@ -1,5 +1,5 @@ import './stem-begroot-budget-list.css'; -import React from 'react'; +import React, { Dispatch, SetStateAction } from 'react'; import { BudgetStatusPanel } from '../../reuseables/budget-status-panel'; import { IconButton, Image, Spacer } from '@openstad-headless/ui/src'; import "@utrecht/component-library-css"; @@ -21,6 +21,10 @@ export const StemBegrootBudgetList = ({ panelTitle, budgetChosenTitle, budgetRemainingTitle, + tagsToDisplay, + activeTagTab = '', + typeIsPerTag = false, + setActiveTagTab }: { allResourceInList: Array selectedResources: Array; @@ -30,12 +34,16 @@ export const StemBegrootBudgetList = ({ introText?: string; showInfoMenu?: boolean; decideCanAddMore: () => boolean; - onSelectedResourceRemove: (resource: { id: number }) => void; + onSelectedResourceRemove: (resource: { id: number, budget: number }) => void; step1Title: string; resourceCardTitle: string; panelTitle?: string; budgetChosenTitle?: string; budgetRemainingTitle?: string; + tagsToDisplay?: Array; + activeTagTab?: string; + typeIsPerTag?: boolean; + setActiveTagTab?: Dispatch>; }) => { const budgetUsed = selectedResources.reduce( (total, cv) => total + cv.budget, @@ -136,7 +144,27 @@ export const StemBegrootBudgetList = ({ ) : null} -
+ + { (typeIsPerTag && !!tagsToDisplay) && ( +
+ {tagsToDisplay?.map((tag: string) => ( +
+ +
+ ))} +
+ )} + ); diff --git a/packages/stem-begroot/src/step-1/begroot-detail-dialog/stem-begroot-detail-dialog.tsx b/packages/stem-begroot/src/step-1/begroot-detail-dialog/stem-begroot-detail-dialog.tsx index 3e3fa78fd..c132613dd 100644 --- a/packages/stem-begroot/src/step-1/begroot-detail-dialog/stem-begroot-detail-dialog.tsx +++ b/packages/stem-begroot/src/step-1/begroot-detail-dialog/stem-begroot-detail-dialog.tsx @@ -60,13 +60,6 @@ export const StemBegrootResourceDetailDialog = ({ buttonText={{ next: 'Volgende inzending', previous: 'Vorige inzending' }} items={resources && resources.length > 0 ? resources : []} itemRenderer={(resource) => { - const theme = resource.tags - ?.filter((t: any) => t.type === 'theme') - ?.at(0); - const area = resource.tags - ?.filter((t: any) => t.type === 'area') - ?.at(0); - const canUseButton = resourceBtnEnabled(resource); const primaryButtonText = resourceBtnTextHandler(resource); const originalUrl = defineOriginalUrl(resource); @@ -98,7 +91,10 @@ export const StemBegrootResourceDetailDialog = ({ if (resourceImages.length === 0) { resourceImages = [{ url: defaultImage || '' }]; + + if (!defaultImage) { hasImages = 'resource-has-no-images'; + } } return ( @@ -112,7 +108,7 @@ export const StemBegrootResourceDetailDialog = ({ itemRenderer={(i) => { if (i.url) { return - } else { + } else if (resource.location) { return ; tags?: Array; header?: React.JSX.Element; + activeTagTab?: string; + typeSelector?: string; + voteType?: string; + setFilteredResources?: (resources: Array) => void; + filteredResources?: Array; }) => { // @ts-ignore const intTags = tags.map(tag => parseInt(tag, 10)); @@ -79,6 +89,16 @@ export const StemBegrootResourceList = ({ }); }) ) + ?.filter((resource: any) => { + if (voteType === 'countPerTag' || voteType === 'budgetingPerTag') { + if (typeSelector === 'tag') { + return resource.tags.some((tag: { name: string }) => tag.name === activeTagTab); + } else { + return resource.tags.some((tag: { type: string }) => tag.type === activeTagTab); + } + } + return true; + }) ?.filter((resource: any) => (!statusIdsToLimitResourcesTo || statusIdsToLimitResourcesTo.length === 0) || statusIdsToLimitResourcesTo.some((statusId) => resource.statuses && Array.isArray(resource.statuses) && resource.statuses.some((o: { id: number }) => o.id === statusId)) ) @@ -101,6 +121,12 @@ export const StemBegrootResourceList = ({ return 0; }); + if ( (voteType === 'countPerTag' || voteType === 'budgetingPerTag') && setFilteredResources) { + if (JSON.stringify(filtered) !== JSON.stringify(filteredResources)) { + setFilteredResources(filtered); + } + } + return ( ; @@ -18,6 +19,8 @@ type Props = { panelTitle?: string; budgetChosenTitle?: string; budgetRemainingTitle?: string; + typeIsPerTag?: boolean; + tagCounter?: Array; }; export const BegrotenSelectedOverview = ({ @@ -32,69 +35,151 @@ export const BegrotenSelectedOverview = ({ panelTitle, budgetChosenTitle, budgetRemainingTitle, + typeIsPerTag = false, + tagCounter = [] }: Props) => { + let resourcesToShow = selectedResources; + + if (typeIsPerTag) { + resourcesToShow = tagCounter.reduce((acc: Array, tagObj) => { + const tagKey = Object.keys(tagObj)[0]; + const tagResources = tagObj[tagKey].selectedResources; + return [...acc, ...tagResources]; + }, []); + } + return ( <> -
- {introText} - {showInfoMenu && ( - - )} -
- - -
- {step2Title} - - - {selectedResources.map((resource) => { - let defaultImage = ''; - - interface Tag { - name: string; - defaultResourceImage?: string; - } - - if (Array.isArray(resource?.tags)) { - const sortedTags = resource.tags.sort((a: Tag, b: Tag) => a.name.localeCompare(b.name)); - const tagWithImage = sortedTags.find((tag: Tag) => tag.defaultResourceImage); - defaultImage = tagWithImage?.defaultResourceImage || ''; - } - - const resourceImages = (Array.isArray(resource.images) && resource.images.length > 0) ? resource.images?.at(0)?.url : defaultImage; - const hasImages = !!resourceImages ? '' : 'resource-has-no-images'; - - return ( -
-
- -
- {resource.title} - {typeIsBudgeting ? ( - - €{resource.budget?.toLocaleString('nl-NL') || 0} - - ) : null} + {typeIsPerTag ? ( +
+ {introText} + + + {tagCounter.map((tagObj) => { + const tagName = Object.keys(tagObj)[0]; + const tagData = tagObj[tagName]; + + return ( + + + + +
+ {tagName.charAt(0).toUpperCase() + tagName.slice(1)} + + {tagData.selectedResources.map((resource) => { + let defaultImage = ''; + + interface Tag { + name: string; + defaultResourceImage?: string; + } + + if (Array.isArray(resource?.tags)) { + const sortedTags = resource.tags.sort((a: Tag, b: Tag) => a.name.localeCompare(b.name)); + const tagWithImage = sortedTags.find((tag: Tag) => tag.defaultResourceImage); + defaultImage = tagWithImage?.defaultResourceImage || ''; + } + + const resourceImages = (Array.isArray(resource.images) && resource.images.length > 0) ? resource.images?.at(0)?.url : defaultImage; + const hasImages = !!resourceImages ? '' : 'resource-has-no-images'; + + return ( +
+
+ +
+ {resource.title} + {typeIsBudgeting ? ( + + €{resource.budget?.toLocaleString('nl-NL') || 0} + + ) : null} +
+
+
+ ); + })} +
+ +
+ {showInfoMenu && ( + + )}
-
+ + ); + })} +
+ ) : ( +
+ {introText} + {showInfoMenu && ( + + )} + + + {resourcesToShow.map((resource) => { + let defaultImage = ''; + + interface Tag { + name: string; + defaultResourceImage?: string; + } + + if (Array.isArray(resource?.tags)) { + const sortedTags = resource.tags.sort((a: Tag, b: Tag) => a.name.localeCompare(b.name)); + const tagWithImage = sortedTags.find((tag: Tag) => tag.defaultResourceImage); + defaultImage = tagWithImage?.defaultResourceImage || ''; + } + + const resourceImages = (Array.isArray(resource.images) && resource.images.length > 0) ? resource.images?.at(0)?.url : defaultImage; + const hasImages = !!resourceImages ? '' : 'resource-has-no-images'; -
- ) - })} -
+ return ( +
+
+ +
+ {resource.title} + {typeIsBudgeting ? ( + + €{resource.budget?.toLocaleString('nl-NL') || 0} + + ) : null} +
+
+
+ ); + })} + + )} ); };