Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"dependencies": {
"@assembly-js/app-bridge": "^1.1.0",
"@assembly-js/node-sdk": "^3.19.1",
"@cyntler/react-doc-viewer": "^1.17.0",
"@emotion/react": "^11.11.3",
Expand Down
32 changes: 28 additions & 4 deletions src/app/api/tasks/public/public.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,32 @@ export const PublicTaskDtoSchema = z.object({
})
export type PublicTaskDto = z.infer<typeof PublicTaskDtoSchema>

const viewersAssociationExclusivitySchema = z
.object({
viewers: AssociationsSchema.optional(),
association: AssociationsSchema.optional(),
isShared: z.boolean().optional(),
})
.superRefine((val, ctx) => {
const hasViewers = val.viewers !== undefined
const hasAssociation = val.association !== undefined
const hasIsShared = val.isShared !== undefined

if (hasViewers && (hasAssociation || hasIsShared)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message:
'viewers cannot be used together with association or isShared. Use either viewers alone, or association/isShared together.',
path: ['viewers'],
})
}
})
.transform(({ viewers, association, isShared, ...rest }) => ({
...rest,
association: viewers ?? association,
isShared: viewers ? true : isShared,
}))

export const publicTaskCreateDtoSchemaFactory = (token: string) => {
return z
.object({
Expand All @@ -61,9 +87,8 @@ export const publicTaskCreateDtoSchemaFactory = (token: string) => {
internalUserId: z.string().uuid().optional(),
clientId: z.string().uuid().optional(),
companyId: z.string().uuid().optional(),
association: AssociationsSchema, //right now, we only need the feature to have max of 1 viewer per task
isShared: z.boolean().optional(),
})
.and(viewersAssociationExclusivitySchema)
.superRefine(async (data, ctx) => {
const { name, templateId, internalUserId, clientId, status } = data
let { companyId } = data
Expand Down Expand Up @@ -148,9 +173,8 @@ export const PublicTaskUpdateDtoSchema = z
internalUserId: z.string().uuid().nullish(),
clientId: z.string().uuid().nullish(),
companyId: z.string().uuid().nullish(),
association: AssociationsSchema,
isShared: z.boolean().optional(),
})
.and(viewersAssociationExclusivitySchema)
.superRefine(validateUserIds)

export type PublicTaskUpdateDto = z.infer<typeof PublicTaskUpdateDtoSchema>
5 changes: 4 additions & 1 deletion src/app/api/tasks/tasksShared.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,10 @@ export abstract class TasksSharedService extends BaseService {
throw new APIError(httpStatus.BAD_REQUEST, 'Invalid companyId for the provided association.')
}
} else {
await this.copilot.getCompany(association.companyId)
const company = await this.copilot.getCompany(association.companyId)
if (company.isPlaceholder) {
throw new APIError(httpStatus.BAD_REQUEST, 'Invalid companyId for the provided association.')
}
}
} catch (err) {
if (err instanceof APIError) {
Expand Down
4 changes: 0 additions & 4 deletions src/app/api/view-settings/viewSettings.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ export class ViewSettingsService extends BaseService {
if (filterOptions && !filterOptions.association) {
viewSettings.filterOptions = { ...filterOptions, [FilterOptions.ASSOCIATION]: emptyAssignee }
}
if (filterOptions && !filterOptions.isShared) {
viewSettings.filterOptions = { ...filterOptions, [FilterOptions.IS_SHARED]: emptyAssignee }
}

return viewSettings
}
Expand Down Expand Up @@ -84,7 +81,6 @@ export class ViewSettingsService extends BaseService {
filterOptions: {
[FilterOptions.ASSIGNEE]: emptyAssignee,
[FilterOptions.ASSOCIATION]: emptyAssignee,
[FilterOptions.IS_SHARED]: emptyAssignee,
[FilterOptions.CREATOR]: emptyAssignee,
[FilterOptions.KEYWORD]: '',
[FilterOptions.TYPE]: '',
Expand Down
22 changes: 13 additions & 9 deletions src/app/detail/ui/NewTaskCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import dayjs from 'dayjs'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useSelector } from 'react-redux'
import { Tapwrite } from 'tapwrite'
import { useAssociationLabelForWorkspace } from '@/hooks/useWorkspaceLabel'

interface SubTaskFields {
title: string
Expand Down Expand Up @@ -72,7 +73,7 @@ export const NewTaskCard = ({
[UserIds.COMPANY_ID]: null,
}

const { tokenPayload } = useSelector(selectAuthDetails)
const { tokenPayload, workspace } = useSelector(selectAuthDetails)
const [subTaskFields, setSubTaskFields] = useState<SubTaskFields>({
title: '',
description: '',
Expand Down Expand Up @@ -154,6 +155,8 @@ export const NewTaskCard = ({
const [taskAssociationValue, setTaskAssociationValue] = useState<IAssigneeCombined | null>(previewTaskAssociation)
const [isShared, setIsShared] = useState(!!previewTaskAssociation)

const { associationLabel } = useAssociationLabelForWorkspace({ workspace, associationValue: taskAssociationValue })

const applyTemplate = useCallback(
(id: string, templateTitle: string) => {
const controller = new AbortController()
Expand Down Expand Up @@ -271,6 +274,13 @@ export const NewTaskCard = ({
handleFieldChange('userIds', newUserIds)
}

const handleAssociationChange = (inputValue: InputValue[]) => {
const newUserIds = getSelectedViewerIds(inputValue)
const selectedAssignee = getSelectorAssignee(assignee, inputValue)
setTaskAssociationValue(selectedAssignee || null)
handleFieldChange('associations', newUserIds)
}

const baseAssociationCondition = assigneeValue && assigneeValue.type === FilterByOptions.IUS
const showShareToggle = baseAssociationCondition && taskAssociationValue
const showAssociation = !assigneeValue || baseAssociationCondition
Expand Down Expand Up @@ -446,12 +456,7 @@ export const NewTaskCard = ({
hideIusList
disabled={!!previewMode}
name="Set related to"
onChange={(inputValue) => {
const newUserIds = getSelectedViewerIds(inputValue)
const selectedAssignee = getSelectorAssignee(assignee, inputValue)
setTaskAssociationValue(selectedAssignee || null)
handleFieldChange('associations', newUserIds)
}}
onChange={handleAssociationChange}
initialValue={taskAssociationValue || undefined}
buttonContent={
<SelectorButton
Expand Down Expand Up @@ -484,8 +489,7 @@ export const NewTaskCard = ({
</Stack>
{showShareToggle && (
<CopilotToggle
label="Share with client"
disabled={!previewMode}
label={`Share with ${associationLabel}`}
onChange={() => {
setIsShared(!isShared)
handleFieldChange('isShared', !isShared)
Expand Down
38 changes: 29 additions & 9 deletions src/app/detail/ui/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import { useSelector } from 'react-redux'
import { z } from 'zod'
import { CopilotToggle } from '@/components/inputs/CopilotToggle'
import { SelectorFieldType } from '@/types/common'
import { useAssociationLabelForWorkspace } from '@/hooks/useWorkspaceLabel'

type StyledTypographyProps = {
display?: string
Expand Down Expand Up @@ -84,7 +85,7 @@ export const Sidebar = ({
}) => {
const { activeTask, workflowStates, assignee, previewMode } = useSelector(selectTaskBoard)
const { showSidebar, showConfirmAssignModal, fromNotificationCenter } = useSelector(selectTaskDetails)
const { tokenPayload } = useSelector(selectAuthDetails)
const { tokenPayload, workspace } = useSelector(selectAuthDetails)

const [isHydrated, setIsHydrated] = useState(false)

Expand All @@ -106,11 +107,10 @@ export const Sidebar = ({

const [taskAssociationValue, setTaskAssociationValue] = useState<IAssigneeCombined | null>(null)
const [selectedAssociationUser, setSelectedAssociationUser] = useState<Associations | undefined>()

const [isTaskShared, setIsTaskShared] = useState(false)

const baseAssociationCondition = assigneeValue && assigneeValue.type === FilterByOptions.IUS
const showShareToggle = baseAssociationCondition && taskAssociationValue
const showShareToggle = baseAssociationCondition && taskAssociationValue && (!disabled || !!previewMode)
const showAssociation = !assigneeValue || baseAssociationCondition

const { renderingItem: _statusValue, updateRenderingItem: updateStatusValue } = useHandleSelectorComponent({
Expand Down Expand Up @@ -145,6 +145,8 @@ export const Sidebar = ({
}
}, [assignee, activeTask])

const { associationLabel } = useAssociationLabelForWorkspace({ workspace, associationValue: taskAssociationValue })

const windowWidth = useWindowWidth()
const isMobile = windowWidth < 800 && windowWidth !== 0

Expand Down Expand Up @@ -333,11 +335,11 @@ export const Sidebar = ({
<CopilotPopSelector
name="Set assignee"
onChange={handleAssigneeChange}
disabled={disabled || fromNotificationCenter}
disabled={(disabled && !previewMode) || fromNotificationCenter}
initialValue={assigneeValue}
buttonContent={
<SelectorButton
disabled={disabled || fromNotificationCenter}
disabled={(disabled && !previewMode) || fromNotificationCenter}
height={'30px'}
startIcon={<CopilotAvatar size="xs" currentAssignee={assigneeValue} />}
buttonContent={
Expand All @@ -359,7 +361,7 @@ export const Sidebar = ({
}
/>
</Box>
{assigneeValue && assigneeValue.type === FilterByOptions.IUS && (
{showAssociation && (
<Box
sx={{
width: 'fit-content',
Expand Down Expand Up @@ -400,6 +402,24 @@ export const Sidebar = ({
/>
</Box>
)}
{showShareToggle && (
<Box
sx={{
width: 'fit-content',
height: '30px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<CopilotToggle
label={`Share with ${associationLabel}`}
disabled={(disabled && !previewMode) || fromNotificationCenter} // allow task share in preview mode
onChange={handleTaskShared}
checked={isTaskShared}
/>
</Box>
)}
<StyledModal
open={showConfirmAssignModal || showAssociationConfirmationModal}
onClose={() =>
Expand Down Expand Up @@ -561,11 +581,11 @@ export const Sidebar = ({
<CopilotPopSelector
name="Set assignee"
onChange={handleAssigneeChange}
disabled={disabled || fromNotificationCenter}
disabled={(disabled && !previewMode) || fromNotificationCenter}
initialValue={assigneeValue}
buttonContent={
<SelectorButton
disabled={disabled || fromNotificationCenter}
disabled={(disabled && !previewMode) || fromNotificationCenter}
padding="0px"
startIcon={<CopilotAvatar size="xs" currentAssignee={assigneeValue} />}
outlined={true}
Expand Down Expand Up @@ -653,7 +673,7 @@ export const Sidebar = ({
<>
<Divider sx={{ borderColor: (theme) => theme.color.borders.border, height: '1px' }} />
<CopilotToggle
label="Share with client"
label={`Share with ${associationLabel}`}
disabled={(disabled && !previewMode) || fromNotificationCenter} // allow task share in preview mode
onChange={handleTaskShared}
checked={isTaskShared}
Expand Down
5 changes: 4 additions & 1 deletion src/app/ui/NewTaskForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { marked } from 'marked'
import { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react'
import { useSelector } from 'react-redux'
import { Tapwrite } from 'tapwrite'
import { useAssociationLabelForWorkspace } from '@/hooks/useWorkspaceLabel'

interface NewTaskFormInputsProps {
isEditorReadonly?: boolean
Expand All @@ -81,6 +82,7 @@ export const NewTaskForm = ({ handleCreate, handleClose }: NewTaskFormProps) =>
const { workflowStates, assignee, previewMode, filterOptions, urlActionParams, token, previewClientCompany } =
useSelector(selectTaskBoard)
const [actionParamPayload, setActionParamPayload] = useState<PublicTaskCreateDto | null>(null)
const { workspace } = useSelector(selectAuthDetails)

const todoWorkflowState = workflowStates.find((el) => el.key === 'todo') || workflowStates[0]
const actionParamWorkflowState = actionParamPayload
Expand Down Expand Up @@ -122,6 +124,7 @@ export const NewTaskForm = ({ handleCreate, handleClose }: NewTaskFormProps) =>
) ?? null)
: null,
)
const { associationLabel } = useAssociationLabelForWorkspace({ workspace, associationValue: taskAssociationsValue })

// this function handles the action param passed in the url and fill the values in the form
const handleUrlActionParam = useCallback(async () => {
Expand Down Expand Up @@ -434,7 +437,7 @@ export const NewTaskForm = ({ handleCreate, handleClose }: NewTaskFormProps) =>
}}
>
<CopilotToggle
label="Share with client"
label={`Share with ${associationLabel}`}
onChange={() => {
const localSharedState = store.getState().createTask.isShared
store.dispatch(setCreateTaskFields({ targetField: 'isShared', value: !localSharedState }))
Expand Down
3 changes: 1 addition & 2 deletions src/components/buttons/FilterChip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@ export const FilterChip = ({ type, assignee }: FilterChipProps) => {
const hideClientsAndCompanies =
type === FilterType.Creator || (filterOptions.type === FilterOptionsKeywords.TEAM && type === FilterType.Assignee)
const hideIus =
[FilterType.Association, FilterType.IsShared].includes(type) ||
(filterOptions.type === FilterOptionsKeywords.CLIENTS && type === FilterType.Assignee)
type === FilterType.Association || (filterOptions.type === FilterOptionsKeywords.CLIENTS && type === FilterType.Assignee)

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export const filterOptionsMap = {
[FilterType.Assignee]: FilterOptions.ASSIGNEE,
[FilterType.Creator]: FilterOptions.CREATOR,
[FilterType.Association]: FilterOptions.ASSOCIATION,
[FilterType.IsShared]: FilterOptions.IS_SHARED,
}

export const FilterAssigneeSection = ({ filterMode, setAnchorEl }: FilterAssigneeSectionProps) => {
Expand All @@ -36,8 +35,7 @@ export const FilterAssigneeSection = ({ filterMode, setAnchorEl }: FilterAssigne
const hideClientsAndCompanies =
filterMode === FilterType.Creator || (type === FilterOptionsKeywords.TEAM && filterMode === FilterType.Assignee)
const hideIus =
[FilterType.Association, FilterType.IsShared].includes(filterMode) ||
(type === FilterOptionsKeywords.CLIENTS && filterMode === FilterType.Assignee)
filterMode === FilterType.Association || (type === FilterOptionsKeywords.CLIENTS && filterMode === FilterType.Assignee)

const handleChange = (inputValue: InputValue[]) => {
const newUserIds = getSelectedUserIds(inputValue)
Expand Down
27 changes: 17 additions & 10 deletions src/components/inputs/FilterSelector/FilterTypeSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,22 @@ interface FilterTypeSectionProps {
filterModes: FilterType[]
}

type FilterSetType = (typeof FilterType)[keyof typeof FilterType]

export const FilterTypeSection = ({ setFilterMode, filterModes }: FilterTypeSectionProps) => {
const {
filterOptions: { type },
} = useSelector(selectTaskBoard)

const disabled = type === FilterOptionsKeywords.CLIENTS || FilterOptionsKeywords.UNASSIGNED ? [FilterType.IsShared] : []
const removed = type.length > 20 ? [FilterType.Assignee] : []
const disabledFilter = new Set<FilterSetType>()
if (type === FilterOptionsKeywords.CLIENTS) {
disabledFilter.add(FilterType.Association)
}

const removedFilter = new Set<FilterSetType>()
if (type.length > 20) {
removedFilter.add(FilterType.Assignee)
}

return (
<Stack
Expand All @@ -31,8 +40,8 @@ export const FilterTypeSection = ({ setFilterMode, filterModes }: FilterTypeSect
rowGap={'2px'}
>
{filterModes.map((filterMode) => {
const isDisabled = disabled.includes(filterMode)
const isRemoved = removed.includes(filterMode)
const isDisabled = disabledFilter.has(filterMode)
const isRemoved = removedFilter.has(filterMode)
if (isRemoved) return null

return (
Expand Down Expand Up @@ -67,13 +76,11 @@ export const FilterTypeSection = ({ setFilterMode, filterModes }: FilterTypeSect
<Box>
<CopilotTooltip
content={
<div>
<div>
<strong>Shared with</strong> is only available
</div>
<div>for tasks assigned to internal users.</div>
</div>
<>
<strong>Related to</strong> is only available for unassigned tasks or tasks assigned to internal users.
</>
}
allowMaxWidth
>
<InfoIcon />
</CopilotTooltip>
Expand Down
Loading
Loading