Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance: add functionality to remove or delete answer collections, withdraw access requests, improve modification logic #4448

Merged
merged 10 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,30 @@ import { useQuery } from '@apollo/client'
import { GetAnswerCollectionsDocument } from '@klicker-uzh/graphql/dist/ops'
import { H2 } from '@uzh-bf/design-system'
import { useTranslations } from 'next-intl'
import { useState } from 'react'
import AnswerCollectionList from './answerCollections/AnswerCollectionList'
import CollectionDeletionErrorToast from './answerCollections/CollectionDeletionErrorToast'
import CollectionDeletionSuccessToast from './answerCollections/CollectionDeletionSuccessToast'
import CollectionRemovalErrorToast from './answerCollections/CollectionRemovalErrorToast'
import CollectionRemovalSuccessToast from './answerCollections/CollectionRemovalSuccessToast'
import CollectionSharingRequests from './answerCollections/CollectionSharingRequests'
import CreateAddCollection from './answerCollections/CreateAddCollection'
import RequestCancellationErrorToast from './answerCollections/RequestCancellationErrorToast'
import RequestCancellationSuccessToast from './answerCollections/RequestCancellationSuccessToast'
import SharedAnswerCollectionList from './SharedAnswerCollectionList'

function AnswerCollections() {
const t = useTranslations()
const { data, loading } = useQuery(GetAnswerCollectionsDocument)

// action toast states
const [deletionSuccess, setDeletionSuccess] = useState(false)
const [deletionFailure, setDeletionFailure] = useState(false)
const [removalSuccess, setRemovalSuccess] = useState(false)
const [removalFailure, setRemovalFailure] = useState(false)
const [cancellationSuccess, setCancellationSuccess] = useState(false)
const [cancellationFailure, setCancellationFailure] = useState(false)

return (
<div className="h-full w-full">
<H2>{t('manage.resources.answerCollections')}</H2>
Expand All @@ -22,11 +37,47 @@ function AnswerCollections() {
<AnswerCollectionList
collections={data?.getAnswerCollections?.answerCollections}
loading={loading}
setDeletionSuccess={setDeletionSuccess}
setDeletionFailure={setDeletionFailure}
setRemovalSuccess={setRemovalSuccess}
setRemovalFailure={setRemovalFailure}
setCancellationSuccess={setCancellationSuccess}
setCancellationFailure={setCancellationFailure}
/>
<SharedAnswerCollectionList
sharedCollections={data?.getAnswerCollections?.sharedCollections}
requestedCollections={data?.getAnswerCollections?.requestedCollections}
loading={loading}
setDeletionSuccess={setDeletionSuccess}
setDeletionFailure={setDeletionFailure}
setRemovalSuccess={setRemovalSuccess}
setRemovalFailure={setRemovalFailure}
setCancellationSuccess={setCancellationSuccess}
setCancellationFailure={setCancellationFailure}
/>
<CollectionDeletionSuccessToast
open={deletionSuccess}
onClose={() => setDeletionSuccess(false)}
/>
<CollectionDeletionErrorToast
open={deletionFailure}
onClose={() => setDeletionFailure(false)}
/>
<CollectionRemovalSuccessToast
open={removalSuccess}
onClose={() => setRemovalSuccess(false)}
/>
<CollectionRemovalErrorToast
open={removalFailure}
onClose={() => setRemovalFailure(false)}
/>
<RequestCancellationSuccessToast
open={cancellationSuccess}
onClose={() => setCancellationSuccess(false)}
/>
<RequestCancellationErrorToast
open={cancellationFailure}
onClose={() => setCancellationFailure(false)}
/>
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,30 @@ import { AnswerCollection } from '@klicker-uzh/graphql/dist/ops'
import Loader from '@klicker-uzh/shared-components/src/Loader'
import { UserNotification } from '@uzh-bf/design-system'
import { useTranslations } from 'next-intl'
import { Dispatch, SetStateAction } from 'react'
import AnswerCollectionCollapsible from './answerCollections/AnswerCollectionCollapsible'
import AnswerCollectionItem from './answerCollections/AnswerCollectionItem'

function SharedAnswerCollectionList({
sharedCollections,
requestedCollections,
loading,
setDeletionSuccess,
setDeletionFailure,
setRemovalSuccess,
setRemovalFailure,
setCancellationSuccess,
setCancellationFailure,
}: {
sharedCollections?: AnswerCollection[]
requestedCollections?: AnswerCollection[]
loading: boolean
setDeletionSuccess: Dispatch<SetStateAction<boolean>>
setDeletionFailure: Dispatch<SetStateAction<boolean>>
setRemovalSuccess: Dispatch<SetStateAction<boolean>>
setRemovalFailure: Dispatch<SetStateAction<boolean>>
setCancellationSuccess: Dispatch<SetStateAction<boolean>>
setCancellationFailure: Dispatch<SetStateAction<boolean>>
}) {
const t = useTranslations()

Expand All @@ -27,6 +40,7 @@ function SharedAnswerCollectionList({
return (
<AnswerCollectionCollapsible
title={t('manage.resources.sharedAnswerCollections')}
className={{ root: 'mb-4' }}
>
<UserNotification
type="info"
Expand All @@ -40,20 +54,33 @@ function SharedAnswerCollectionList({
return (
<AnswerCollectionCollapsible
title={t('manage.resources.sharedAnswerCollections')}
className={{ root: 'mb-4' }}
>
<div className="mt-2 flex flex-col gap-2">
<div className="mt-2 flex flex-col">
{sharedCollections?.map((collection) => (
<AnswerCollectionItem
key={`shared-collection-item-${collection.id}`}
collection={collection}
accessGranted={true}
setDeletionSuccess={setDeletionSuccess}
setDeletionFailure={setDeletionFailure}
setRemovalSuccess={setRemovalSuccess}
setRemovalFailure={setRemovalFailure}
setCancellationSuccess={setCancellationSuccess}
setCancellationFailure={setCancellationFailure}
/>
))}
{requestedCollections?.map((collection) => (
<AnswerCollectionItem
key={`requested-collection-item-${collection.id}`}
collection={collection}
accessGranted={false}
setDeletionSuccess={setDeletionSuccess}
setDeletionFailure={setDeletionFailure}
setRemovalSuccess={setRemovalSuccess}
setRemovalFailure={setRemovalFailure}
setCancellationSuccess={setCancellationSuccess}
setCancellationFailure={setCancellationFailure}
/>
))}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import { useState } from 'react'

function AnswerCollectionCollapsible({
title,
className,
children,
}: {
title: string | React.ReactNode
className?: { root?: string }
children: React.ReactNode
}) {
const [open, setOpen] = useState(true)

return (
<div className="mb-4">
<div className={className?.root}>
<Button
basic
onClick={() => setOpen((prev) => !prev)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ function AnswerCollectionEditModal({
collection,
open,
onClose,
onDelete,
}: {
collection: AnswerCollection
open: boolean
onClose: () => void
onDelete: () => void
}) {
const t = useTranslations()
const [successToast, setSuccessToast] = useState(false)
Expand All @@ -33,6 +35,10 @@ function AnswerCollectionEditModal({
<AnswerCollectionMetaForm
collection={collection}
setSuccessToast={setSuccessToast}
onDelete={() => {
onDelete()
onClose()
}}
/>
<div className="mt-3 flex flex-col gap-1">
<H3 className={{ root: 'mb-0' }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,45 @@ import {
} from '@klicker-uzh/graphql/dist/ops'
import { Button, H4 } from '@uzh-bf/design-system'
import { useTranslations } from 'next-intl'
import { useState } from 'react'
import { Dispatch, SetStateAction, useState } from 'react'
import { twMerge } from 'tailwind-merge'
import AnswerCollectionEditModal from './AnswerCollectionEditModal'
import AnswerCollectionViewingModal from './AnswerCollectionViewingModal'
import CollectionAccessLabel from './CollectionAccessLabel'
import CollectionDeletionModal from './CollectionDeletionModal'
import CollectionRemovalModal from './CollectionRemovalModal'
import RequestCancellationModal from './RequestCancellationModal'

function AnswerCollectionItem({
collection,
editable = false,
accessGranted = false,
setDeletionSuccess,
setDeletionFailure,
setRemovalSuccess,
setRemovalFailure,
setCancellationSuccess,
setCancellationFailure,
}: {
collection: AnswerCollection
editable?: boolean
accessGranted?: boolean
setDeletionSuccess: Dispatch<SetStateAction<boolean>>
setDeletionFailure: Dispatch<SetStateAction<boolean>>
setRemovalSuccess: Dispatch<SetStateAction<boolean>>
setRemovalFailure: Dispatch<SetStateAction<boolean>>
setCancellationSuccess: Dispatch<SetStateAction<boolean>>
setCancellationFailure: Dispatch<SetStateAction<boolean>>
}) {
const t = useTranslations()

// modal states
const [editModal, setEditModal] = useState(false)
const [deletionModal, setDeletionModal] = useState(false)
const [viewingModal, setViewingModal] = useState(false)
const [removalModal, setRemovalModal] = useState(false)
const [cancellationModal, setCancellationModal] = useState(false)

const collectionAccessMap: Record<CollectionAccess, React.ReactNode> = {
[CollectionAccess.Private]: (
<CollectionAccessLabel
Expand Down Expand Up @@ -55,17 +76,17 @@ function AnswerCollectionItem({
if (editable) {
setEditModal(true)
}

// allow viewing of shared collections (not for requested ones)
if (accessGranted) {
else if (accessGranted) {
setViewingModal(true)
}
// allow cancelling a pending request
else {
setCancellationModal(true)
}
}}
className={{
root: twMerge(
'flex flex-row justify-between rounded border border-solid px-2 py-0.5 shadow-sm',
!editable && !accessGranted && 'cursor-default'
),
root: 'mb-2 flex flex-row justify-between rounded border border-solid px-2 py-0.5 shadow-sm',
}}
data={{ cy: `answer-collection-${collection.name}` }}
>
Expand Down Expand Up @@ -110,26 +131,62 @@ function AnswerCollectionItem({
<div>{t('manage.resources.viewCollection')}</div>
</div>
) : (
<div className="text-primary-100 flex flex-row items-center gap-2">
<FontAwesomeIcon icon={faClock} />
<div>{t('manage.resources.requestedAccess')}</div>
<div className="flex flex-col items-end gap-0.5 py-0.5">
<div className="text-primary-100 flex flex-row items-center gap-2">
<FontAwesomeIcon icon={faClock} />
<div>{t('manage.resources.requestedAccess')}</div>
</div>
<div className="flex flex-row items-center gap-1.5 text-sm">
<FontAwesomeIcon icon={faHandPointer} />
<div>{t('manage.resources.clickToCancelRequest')}</div>
</div>
</div>
)}
</Button>
{editable ? (
<AnswerCollectionEditModal
collection={collection}
open={editModal}
onClose={() => setEditModal(false)}
/>
<>
<AnswerCollectionEditModal
collection={collection}
open={editModal}
onClose={() => setEditModal(false)}
onDelete={() => setDeletionModal(true)}
/>
<CollectionDeletionModal
collection={collection}
deletionModal={deletionModal}
setDeletionModal={setDeletionModal}
setDeletionSuccess={setDeletionSuccess}
setDeletionFailure={setDeletionFailure}
/>
</>
) : null}
{accessGranted ? (
<AnswerCollectionViewingModal
collection={collection}
open={viewingModal}
onClose={() => setViewingModal(false)}
/>
) : null}
<>
<AnswerCollectionViewingModal
collection={collection}
open={viewingModal}
onClose={() => setViewingModal(false)}
onRemove={() => setRemovalModal(true)}
/>
<CollectionRemovalModal
collection={collection}
removalModal={removalModal}
setRemovalModal={setRemovalModal}
setRemovalSuccess={setRemovalSuccess}
setRemovalFailure={setRemovalFailure}
/>
</>
) : (
<>
<RequestCancellationModal
collection={collection}
cancellationModal={cancellationModal}
setCancellationModal={setCancellationModal}
setCancellationSuccess={setCancellationSuccess}
setCancellationFailure={setCancellationFailure}
/>
</>
)}
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,54 @@ import { AnswerCollection } from '@klicker-uzh/graphql/dist/ops'
import Loader from '@klicker-uzh/shared-components/src/Loader'
import { UserNotification } from '@uzh-bf/design-system'
import { useTranslations } from 'next-intl'
import { Dispatch, SetStateAction } from 'react'
import AnswerCollectionCollapsible from './AnswerCollectionCollapsible'
import AnswerCollectionItem from './AnswerCollectionItem'

function AnswerCollectionList({
collections,
loading,
setDeletionSuccess,
setDeletionFailure,
setRemovalSuccess,
setRemovalFailure,
setCancellationSuccess,
setCancellationFailure,
}: {
collections?: AnswerCollection[]
loading: boolean
setDeletionSuccess: Dispatch<SetStateAction<boolean>>
setDeletionFailure: Dispatch<SetStateAction<boolean>>
setRemovalSuccess: Dispatch<SetStateAction<boolean>>
setRemovalFailure: Dispatch<SetStateAction<boolean>>
setCancellationSuccess: Dispatch<SetStateAction<boolean>>
setCancellationFailure: Dispatch<SetStateAction<boolean>>
}) {
const t = useTranslations()

return (
<AnswerCollectionCollapsible
title={t('manage.resources.createdAnswerCollections')}
className={{ root: 'mb-4' }}
>
{loading ? <Loader /> : null}
{collections && collections.length === 0 ? (
<UserNotification type="info" className={{ root: 'mt-1.5' }}>
{t('manage.resources.noAnswerCollections')}
</UserNotification>
) : (
<div className="mb-6 mt-2 flex flex-col gap-2">
<div className="mt-2 flex flex-col">
{collections?.map((collection) => (
<AnswerCollectionItem
editable
key={`answer-collection-${collection.id}`}
collection={collection}
setDeletionSuccess={setDeletionSuccess}
setDeletionFailure={setDeletionFailure}
setRemovalSuccess={setRemovalSuccess}
setRemovalFailure={setRemovalFailure}
setCancellationSuccess={setCancellationSuccess}
setCancellationFailure={setCancellationFailure}
/>
))}
</div>
Expand Down
Loading
Loading