diff --git a/apps/mobile/app.template.json b/apps/mobile/app.template.json index a69f6c58c..f48f6bce9 100644 --- a/apps/mobile/app.template.json +++ b/apps/mobile/app.template.json @@ -1,91 +1,94 @@ -{"name": "$EXPO_PROJECT_SLUG", -"displayName": "$EXPO_PROJECT_NAME", -"expo": { - "name": "$EXPO_PROJECT_NAME", - "slug": "$EXPO_PROJECT_SLUG", - "version": "0.1.0", - "orientation": "portrait", - "icon": "./assets/images/ever-teams-logo.png", - "splash": { - "image": "./assets/images/splash-ever-teams.png", - "resizeMode": "cover", - "backgroundColor": "#ffffff" - }, - "owner": "$EXPO_PROJECT_OWNER", - "updates": { - "fallbackToCacheTimeout": 0, - "url": "https://u.expo.dev/$EXPO_PROJECT_ID" - }, - "jsEngine": "hermes", - "assetBundlePatterns": ["**/*"], - "plugins": [ - [ - "expo-media-library", - { - "photosPermission": "Allow $(PRODUCT_NAME) to access your photos.", - "savePhotosPermission": "Allow $(PRODUCT_NAME) to save photos.", - "isAccessMediaLocationEnabled": true - } - ], - "sentry-expo", - [ - "expo-build-properties", - { - "android": { - "enableProguardInReleaseBuilds": true, - "extraProguardRules": "-keep public class com.horcrux.svg.** {*;}", - "allowBackup": false - } - } - ] - ], - "android": { - "icon": "./assets/images/app-icon-android-legacy-ever-teams.png", - "package": "ever.team", - "adaptiveIcon": { - "foregroundImage": "./assets/images/app-icon-android-adaptive-foreground-ever.png", - "backgroundImage": "./assets/images/app-icon-android-adaptive-background.png" - }, - "splash": { - "image": "./assets/images/splash-ever-teams.png", - "resizeMode": "cover", - "backgroundColor": "#ffffff" - }, - "permissions": [ - "android.permission.READ_EXTERNAL_STORAGE", - "android.permission.WRITE_EXTERNAL_STORAGE", - "android.permission.ACCESS_MEDIA_LOCATION" - ] - }, - "ios": { - "icon": "./assets/images/app-icon-ios-ever-teams.png", - "supportsTablet": true, - "bundleIdentifier": "co.ever.teams", - "splash": { - "image": "./assets/images/splash-ever-teams.png", - "tabletImage": "./assets/images/splash-logo-ever-teams-ios-tablet.png", - "resizeMode": "cover", - "backgroundColor": "#ffffff" - }, - "infoPlist": { - "NSCameraUsageDescription": "This app uses the camera to scan barcodes on event tickets.", - "NSPhotoLibraryUsageDescription": "Allow $(PRODUCT_NAME) to access your photos.", - "NSPhotoLibraryAddUsageDescription": "Allow $(PRODUCT_NAME) to save photos." - } - }, - "web": { - "favicon": "./assets/images/app-icon-web-favicon.png", - "splash": { - "image": "./assets/images/splash-logo-web-ever-teams.png", - "resizeMode": "contain", - "backgroundColor": "#ffffff" - } - }, - "extra": { - "eas": { - "projectId": "$EXPO_PROJECT_ID" - } - }, - "runtimeVersion": "exposdk:48.0.0" -} +{ + "name": "$EXPO_PROJECT_SLUG", + "displayName": "$EXPO_PROJECT_NAME", + "expo": { + "name": "$EXPO_PROJECT_NAME", + "slug": "$EXPO_PROJECT_SLUG", + "version": "0.1.0", + "orientation": "portrait", + "icon": "./assets/images/ever-teams-logo.png", + "splash": { + "image": "./assets/images/splash-ever-teams.png", + "resizeMode": "cover", + "backgroundColor": "#ffffff" + }, + "owner": "$EXPO_PROJECT_OWNER", + "updates": { + "fallbackToCacheTimeout": 0, + "url": "https://u.expo.dev/$EXPO_PROJECT_ID" + }, + "jsEngine": "hermes", + "assetBundlePatterns": ["**/*"], + "plugins": [ + [ + "expo-media-library", + { + "photosPermission": "Allow $(PRODUCT_NAME) to access your photos.", + "savePhotosPermission": "Allow $(PRODUCT_NAME) to save photos.", + "isAccessMediaLocationEnabled": true + } + ], + "sentry-expo", + [ + "expo-build-properties", + { + "android": { + "enableProguardInReleaseBuilds": true, + "extraProguardRules": "-keep public class com.horcrux.svg.** {*;}", + "allowBackup": false, + "minSdkVersion": 23, + "targetSdkVersion": 34 + } + } + ] + ], + "android": { + "icon": "./assets/images/app-icon-android-legacy-ever-teams.png", + "package": "ever.team", + "adaptiveIcon": { + "foregroundImage": "./assets/images/app-icon-android-adaptive-foreground-ever.png", + "backgroundImage": "./assets/images/app-icon-android-adaptive-background.png" + }, + "splash": { + "image": "./assets/images/splash-ever-teams.png", + "resizeMode": "cover", + "backgroundColor": "#ffffff" + }, + "permissions": [ + "android.permission.READ_EXTERNAL_STORAGE", + "android.permission.WRITE_EXTERNAL_STORAGE", + "android.permission.ACCESS_MEDIA_LOCATION" + ] + }, + "ios": { + "icon": "./assets/images/app-icon-ios-ever-teams.png", + "supportsTablet": true, + "bundleIdentifier": "co.ever.teams", + "splash": { + "image": "./assets/images/splash-ever-teams.png", + "tabletImage": "./assets/images/splash-logo-ever-teams-ios-tablet.png", + "resizeMode": "cover", + "backgroundColor": "#ffffff" + }, + "infoPlist": { + "NSCameraUsageDescription": "This app uses the camera to scan barcodes on event tickets.", + "NSPhotoLibraryUsageDescription": "Allow $(PRODUCT_NAME) to access your photos.", + "NSPhotoLibraryAddUsageDescription": "Allow $(PRODUCT_NAME) to save photos." + } + }, + "web": { + "favicon": "./assets/images/app-icon-web-favicon.png", + "splash": { + "image": "./assets/images/splash-logo-web-ever-teams.png", + "resizeMode": "contain", + "backgroundColor": "#ffffff" + } + }, + "extra": { + "eas": { + "projectId": "$EXPO_PROJECT_ID" + } + }, + "runtimeVersion": "exposdk:48.0.0" + } } diff --git a/apps/mobile/yarn.lock b/apps/mobile/yarn.lock index 59df746a7..03cbdb298 100644 --- a/apps/mobile/yarn.lock +++ b/apps/mobile/yarn.lock @@ -6910,9 +6910,9 @@ fast-levenshtein@^2.0.6: integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== fast-loops@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/fast-loops/-/fast-loops-1.1.3.tgz#ce96adb86d07e7bf9b4822ab9c6fac9964981f75" - integrity sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g== + version "1.1.4" + resolved "https://registry.yarnpkg.com/fast-loops/-/fast-loops-1.1.4.tgz#61bc77d518c0af5073a638c6d9d5c7683f069ce2" + integrity sha512-8dbd3XWoKCTms18ize6JmQF1SFnnfj5s0B7rRry22EofgMu7B6LKHVh+XfFqFGsqnbH54xgeO83PzpKI+ODhlg== fast-xml-parser@^4.0.12: version "4.3.2" diff --git a/apps/web/app/[locale]/all-teams/component.tsx b/apps/web/app/[locale]/all-teams/component.tsx new file mode 100644 index 000000000..544c4f9b4 --- /dev/null +++ b/apps/web/app/[locale]/all-teams/component.tsx @@ -0,0 +1,65 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { useRecoilValue } from 'recoil'; +import { fullWidthState } from '@app/stores/fullWidth'; +import { withAuthentication } from 'lib/app/authenticator'; +import { Breadcrumb, Container } from 'lib/components'; +import { MainHeader, MainLayout } from 'lib/layout'; +import { useOrganizationAndTeamManagers } from '@app/hooks/features/useOrganizationTeamManagers'; +import { useEffect } from 'react'; +import { useTranslations } from 'next-intl'; +import TeamMemberHeader from 'lib/features/team-member-header'; +import { IssuesView } from '@app/constants'; +import { HeaderTabs } from '@components/pages/all-teams/header-tabs'; +import { allTeamsHeaderTabs } from '@app/stores/header-tabs'; +import AllTeamsMembers from 'lib/features/all-teams-members'; +import { MemberFilter } from 'lib/features/all-teams/all-team-members-filter'; + +function AllTeamsPage() { + const t = useTranslations(); + const fullWidth = useRecoilValue(fullWidthState); + const view = useRecoilValue(allTeamsHeaderTabs); + const { filteredTeams, userManagedTeams } = useOrganizationAndTeamManagers(); + + const breadcrumb = [ + { title: JSON.parse(t('pages.home.BREADCRUMB')), href: '/' }, + { title: t('common.ALL_TEAMS'), href: '/all-teams' } + ]; + + /* If the user is not a manager in any team or if he's + manager in only one team, then redirect him to the home page + */ + if (userManagedTeams.length < 2) return ; + + return ( + + + {/* Breadcrumb */} +
+ +
+
+ +
+ +
+
+ +
+ + + +
+ ); +} + +function RedirectUser() { + const router = useRouter(); + useEffect(() => { + router.push('/'); + }, [router]); + return <>; +} + +export default withAuthentication(AllTeamsPage, { displayName: 'AllManagedTeams' }); diff --git a/apps/web/app/[locale]/all-teams/page.tsx b/apps/web/app/[locale]/all-teams/page.tsx new file mode 100644 index 000000000..7eb229d44 --- /dev/null +++ b/apps/web/app/[locale]/all-teams/page.tsx @@ -0,0 +1,5 @@ +import AllTeamsPage from './component'; + +export default function Page() { + return ; +} diff --git a/apps/web/app/[locale]/profile/[memberId]/page.tsx b/apps/web/app/[locale]/profile/[memberId]/page.tsx index 6a500ece2..3e3dd3ebf 100644 --- a/apps/web/app/[locale]/profile/[memberId]/page.tsx +++ b/apps/web/app/[locale]/profile/[memberId]/page.tsx @@ -29,6 +29,8 @@ export type FilterTab = 'Tasks' | 'Screenshots' | 'Apps' | 'Visited Sites'; const Profile = React.memo(function ProfilePage({ params }: { params: { memberId: string } }) { const profile = useUserProfilePage(); + const [headerSize, setHeaderSize] = useState(10); + const { user } = useAuthenticateUser(); const { isTrackingEnabled, activeTeam, activeTeamManagers } = useOrganizationTeams(); const members = activeTeam?.members; @@ -102,7 +104,12 @@ const Profile = React.memo(function ProfilePage({ params }: { params: { memberId ) : ( - + setHeaderSize(size)} + > { const [user] = useRecoilState(userState); const { isTeamMember, activeTeam } = useOrganizationTeams(); const { isTeamManager } = useIsMemberManager(user); + const { teamInvitations } = useTeamInvitations(); + const [isFetchingTeamInvitations] = useRecoilState(fetchingTeamInvitationsState); + return (
{isTeamMember ? ( @@ -49,10 +52,11 @@ const Team = () => { {/* Invitations */} - {isTeamManager ? ( + {isTeamManager && !isFetchingTeamInvitations ? ( diff --git a/apps/web/app/helpers/plan-day-badge.ts b/apps/web/app/helpers/plan-day-badge.ts index b40497c5d..e5d9322da 100644 --- a/apps/web/app/helpers/plan-day-badge.ts +++ b/apps/web/app/helpers/plan-day-badge.ts @@ -16,7 +16,8 @@ export const planBadgeContent = (plans: IDailyPlan[], taskId: ITeamTask['id']): if (otherPlansWithTask.length > 0) { return 'Planned'; } else { - return `Planned ${formatDayPlanDate(plan.date, 'DD MMM YYYY')}`; + return `${formatDayPlanDate(plan.date, 'DD MMM YYYY')}`; + // return `Planned ${formatDayPlanDate(plan.date, 'DD MMM YYYY')}`; } // The task does not exist in any plan } else { diff --git a/apps/web/app/hooks/features/useOrganizationTeamManagers.ts b/apps/web/app/hooks/features/useOrganizationTeamManagers.ts new file mode 100644 index 000000000..edaab309e --- /dev/null +++ b/apps/web/app/hooks/features/useOrganizationTeamManagers.ts @@ -0,0 +1,44 @@ +import { useRecoilValue } from 'recoil'; +import { useAuthenticateUser } from './useAuthenticateUser'; +import { useOrganizationTeams } from './useOrganizationTeams'; +import { filterValue } from '@app/stores/all-teams'; + +export function useOrganizationAndTeamManagers() { + const { user } = useAuthenticateUser(); + const { teams } = useOrganizationTeams(); + const { value: filtered } = useRecoilValue(filterValue); + + const userManagedTeams = teams.filter((team) => + team.members.some((member) => member.employee?.user?.id === user?.id && member.role?.name === 'MANAGER') + ); + + const filteredTeams = + filtered === 'all' + ? userManagedTeams + : filtered === 'pause' + ? userManagedTeams.map((team) => ({ + ...team, + members: team.members.filter((member) => member.timerStatus === 'pause') + })) + : filtered === 'running' + ? userManagedTeams.map((team) => ({ + ...team, + members: team.members.filter((member) => member.timerStatus === 'running') + })) + : filtered === 'suspended' + ? userManagedTeams.map((team) => ({ + ...team, + members: team.members.filter((member) => member.timerStatus === 'suspended') + })) + : filtered === 'invited' + ? userManagedTeams.map((team) => ({ + ...team, + members: team.members.filter((member) => member.employee.acceptDate) + })) + : userManagedTeams; + + return { + userManagedTeams, + filteredTeams + }; +} diff --git a/apps/web/app/interfaces/IOrganizationTeam.ts b/apps/web/app/interfaces/IOrganizationTeam.ts index 2c12fa8e9..fdb5e455a 100644 --- a/apps/web/app/interfaces/IOrganizationTeam.ts +++ b/apps/web/app/interfaces/IOrganizationTeam.ts @@ -109,6 +109,12 @@ export interface OT_Role { isSystem: boolean; } +export interface ITeamsMembersFilter { + label: string; + value: ITimerStatusEnum | 'all' | 'invited'; + bg: string; +} + export enum RoleNameEnum { SUPER_ADMIN = 'SUPER_ADMIN', ADMIN = 'ADMIN', diff --git a/apps/web/app/services/client/api/daily-plan.ts b/apps/web/app/services/client/api/daily-plan.ts index e405a3b80..a99f1596e 100644 --- a/apps/web/app/services/client/api/daily-plan.ts +++ b/apps/web/app/services/client/api/daily-plan.ts @@ -14,7 +14,7 @@ export function getAllDayPlansAPI() { const organizationId = getOrganizationIdCookie(); const tenantId = getTenantIdCookie(); - const relations = ['employee', 'tasks']; + const relations = ['employee', 'tasks', 'employee.user']; const obj = { 'where[organizationId]': organizationId, diff --git a/apps/web/app/stores/all-teams.ts b/apps/web/app/stores/all-teams.ts new file mode 100644 index 000000000..3a9db262b --- /dev/null +++ b/apps/web/app/stores/all-teams.ts @@ -0,0 +1,7 @@ +import { ITeamsMembersFilter } from '@app/interfaces'; +import { atom } from 'recoil'; + +export const filterValue = atom({ + key: 'allTeamsFilterValue', + default: { label: 'All', value: 'all', bg: 'transparent' } +}); diff --git a/apps/web/app/stores/header-tabs.ts b/apps/web/app/stores/header-tabs.ts index 72f9d1f04..b5bfd189d 100644 --- a/apps/web/app/stores/header-tabs.ts +++ b/apps/web/app/stores/header-tabs.ts @@ -5,3 +5,13 @@ export const headerTabs = atom({ key: 'headerTabs', default: IssuesView.CARDS }); + +export const allTeamsHeaderTabs = atom({ + key: 'allTeamsHeaderTabs', + default: IssuesView.CARDS +}); + +export const dailyPlanViewHeaderTabs = atom({ + key: 'dailyPlanViewHeaderTabs', + default: IssuesView.CARDS +}); diff --git a/apps/web/components/pages/all-teams/header-tabs.tsx b/apps/web/components/pages/all-teams/header-tabs.tsx new file mode 100644 index 000000000..b3042ced4 --- /dev/null +++ b/apps/web/components/pages/all-teams/header-tabs.tsx @@ -0,0 +1,46 @@ +import { DottedLanguageObjectStringPaths, useTranslations } from 'next-intl'; +import { useRecoilState } from 'recoil'; +import { QueueListIcon, Squares2X2Icon } from '@heroicons/react/20/solid'; +import { IssuesView } from '@app/constants'; +import { allTeamsHeaderTabs } from '@app/stores/header-tabs'; +import { Tooltip } from 'lib/components'; +import { clsxm } from '@app/utils'; + +export function HeaderTabs() { + const t = useTranslations(); + const options = [ + { label: 'TEAMS', icon: QueueListIcon, view: IssuesView.CARDS }, + { label: 'COMBINE', icon: Squares2X2Icon, view: IssuesView.BLOCKS } + ]; + + const [view, setView] = useRecoilState(allTeamsHeaderTabs); + + return ( + <> + {options.map(({ label, icon: Icon, view: optionView }) => ( + + + + ))} + + ); +} diff --git a/apps/web/lib/components/accordian.tsx b/apps/web/lib/components/accordian.tsx index e0c4ab48f..47602ee1f 100644 --- a/apps/web/lib/components/accordian.tsx +++ b/apps/web/lib/components/accordian.tsx @@ -8,14 +8,15 @@ interface isProps { className: string; isDanger?: boolean; id?: string; + defaultOpen?: boolean; } -export const Accordian = ({ children, title, className, isDanger, id }: isProps) => { +export const Accordian = ({ children, title, className, isDanger, id, defaultOpen = true }: isProps) => { return (
- + {({ open }) => ( <> diff --git a/apps/web/lib/components/alert-popup.tsx b/apps/web/lib/components/alert-popup.tsx new file mode 100644 index 000000000..73442f787 --- /dev/null +++ b/apps/web/lib/components/alert-popup.tsx @@ -0,0 +1,40 @@ +import React from 'react' + +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@components/ui/popover" + + +interface IConfirmPopup { + buttonOpen: any, + open: boolean +} +/** + * + * + * @export + * @param {React.PropsWithChildren} { children, buttonOpen, open } + * @return {*} + */ +export function AlertPopup({ children, buttonOpen, open }: React.PropsWithChildren) { + return ( + + + {buttonOpen} + + +
+
+

Delete this plan

+

Are you sure you want to delete this plan?

+
+
+ {children} +
+
+
+
+ ) +} diff --git a/apps/web/lib/components/image-overlapper.tsx b/apps/web/lib/components/image-overlapper.tsx index ddd510b97..7f5223b7b 100644 --- a/apps/web/lib/components/image-overlapper.tsx +++ b/apps/web/lib/components/image-overlapper.tsx @@ -6,7 +6,7 @@ import { ITeamTask, ITimerStatus } from '@app/interfaces'; import Skeleton from 'react-loading-skeleton'; import { Tooltip } from './tooltip'; import { ScrollArea } from '@components/ui/scroll-bar'; -import { RiUserFill, RiUserAddFill } from "react-icons/ri"; +import { RiUserFill, RiUserAddFill } from 'react-icons/ri'; import { useModal } from '@app/hooks'; import { Modal, Divider } from 'lib/components'; import { useOrganizationTeams } from '@app/hooks'; @@ -14,7 +14,7 @@ import { useTranslations } from 'next-intl'; import { TaskAssignButton } from '../../lib/features/task/task-assign-button'; import { clsxm } from '@app/utils'; import { TaskAvatars } from 'lib/features'; -import { FaCheck } from "react-icons/fa6"; +import { FaCheck } from 'react-icons/fa6'; import TeamMember from 'lib/components/team-member'; import { IEmployee } from '@app/interfaces'; @@ -42,7 +42,7 @@ export default function ImageOverlapper({ arrowData = null, hasActiveMembers = false, assignTaskButtonCall = false, - hasInfo = '', + hasInfo = '' }: { images: ImageOverlapperProps[]; radius?: number; @@ -56,11 +56,11 @@ export default function ImageOverlapper({ hasInfo?: string; }) { // Split the array into two arrays based on the display number - const firstArray = images.slice(0, displayImageCount); - const widthCalculate = images.slice(0, 5); - const secondArray = images.slice(displayImageCount); - const isMoreThanDisplay = images.length > displayImageCount; - const imageLength = images.length; + const firstArray = images?.slice(0, displayImageCount); + const widthCalculate = images?.slice(0, 5); + const secondArray = images?.slice(displayImageCount); + const isMoreThanDisplay = images?.length > displayImageCount; + const imageLength = images?.length; const { isOpen, openModal, closeModal } = useModal(); const { activeTeam } = useOrganizationTeams(); const allMembers = activeTeam?.members || []; @@ -82,13 +82,12 @@ export default function ImageOverlapper({ const updatedUnassign = unassignedMembers.filter((el: IEmployee) => el.id != member.id); setUnassignedMembers(updatedUnassign); } - - } + }; const onCLickValidate = () => { setValidate(!validate); closeModal(); - } + }; const hasMembers = item?.members?.length > 0; const membersList = { assignedMembers, unassignedMembers }; @@ -100,59 +99,56 @@ export default function ImageOverlapper({ if ((!hasMembers && item) || hasActiveMembers || assignTaskButtonCall) { return (
- {hasInfo.length > 0 && showInfo && - (
+ {hasInfo.length > 0 && showInfo && ( +
{hasInfo}
- )} - { - iconType ? ( - - - ) : ( - <> - { - !hasMembers ? - ( -
- setShowInfo(true)} - onMouseOut={() => setShowInfo(false)} - /> -
- ) - : - ( -
- setShowInfo(true)} - onMouseOut={() => setShowInfo(false)} - /> -
- ) - } - - ) - } + )} + {iconType ? ( + + ) : ( + <> + {!hasMembers ? ( +
+ setShowInfo(true)} + onMouseOut={() => setShowInfo(false)} + /> +
+ ) : ( +
+ setShowInfo(true)} + onMouseOut={() => setShowInfo(false)} + /> +
+ )} + + )}
-
+
diff --git a/apps/web/lib/components/index.ts b/apps/web/lib/components/index.ts index f5f1215e6..90dfd5b2b 100644 --- a/apps/web/lib/components/index.ts +++ b/apps/web/lib/components/index.ts @@ -27,3 +27,4 @@ export * from './inputs/auth-code-input'; export * from './services/recaptcha'; export * from './copy-tooltip'; +export * from './alert-popup' diff --git a/apps/web/lib/components/kanban-card.tsx b/apps/web/lib/components/kanban-card.tsx index 0410fac13..98b38a2d9 100644 --- a/apps/web/lib/components/kanban-card.tsx +++ b/apps/web/lib/components/kanban-card.tsx @@ -24,7 +24,7 @@ function getStyle(provided: DraggableProvided, style: any) { }; } -function setCommentIconColor(commentType: 'tagged' | 'untagged') { +export function setCommentIconColor(commentType: 'tagged' | 'untagged') { let style; if (commentType === 'tagged') { @@ -77,7 +77,7 @@ export function TagList({ tags }: { tags: Tag[] }) { ); } -function Priority({ level }: { level: ITaskPriority }) { +export function Priority({ level }: { level: ITaskPriority }) { const levelSmallCase = level.toString().toLowerCase(); const levelIntoNumber = levelSmallCase === 'low' ? 1 : levelSmallCase === 'medium' ? 2 : levelSmallCase === 'high' ? 3 : 4; diff --git a/apps/web/lib/features/all-teams-members-block-view.tsx b/apps/web/lib/features/all-teams-members-block-view.tsx new file mode 100644 index 000000000..7c6935d64 --- /dev/null +++ b/apps/web/lib/features/all-teams-members-block-view.tsx @@ -0,0 +1,38 @@ +import { IOrganizationTeamList, OT_Member } from '@app/interfaces'; +import UserTeamBlockCard from './all-teams/users-teams-block/member-block'; + +interface Employee extends OT_Member { + teams: { team: IOrganizationTeamList; activeTaskId?: string | null }[]; +} + +export default function AllTeamsMembersBlockView({ teams }: { teams: IOrganizationTeamList[] }) { + const employees: Employee[] = teams.flatMap((team) => + team.members.map((member) => ({ + ...member, + teams: [{ team, activeTaskId: member.activeTaskId }] + })) + ); + + const groupedEmployees: Record = employees.reduce( + (acc, employee) => { + if (!acc[employee.employeeId]) { + acc[employee.employeeId] = { ...employee, teams: [] }; + } + acc[employee.employeeId].teams.push(...employee.teams); + return acc; + }, + {} as Record + ); + + const employeesArray: Employee[] = Object.values(groupedEmployees); + + return ( + <> + {employeesArray.length > 0 ? ( + employeesArray.map((employee) => ) + ) : ( +
There is no member for filtered value
+ )} + + ); +} diff --git a/apps/web/lib/features/all-teams-members-card-view.tsx b/apps/web/lib/features/all-teams-members-card-view.tsx new file mode 100644 index 000000000..e9f8e47e6 --- /dev/null +++ b/apps/web/lib/features/all-teams-members-card-view.tsx @@ -0,0 +1,44 @@ +import { IOrganizationTeamList } from '@app/interfaces'; +import UserTeamCard from './all-teams/users-teams-card/user-card'; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@components/ui/accordion'; +import { HorizontalSeparator } from 'lib/components'; + +export default function TeamsMembersCardView({ teams }: { teams: IOrganizationTeamList[] }) { + return ( +
+ t.name)}> + {teams.map((team) => { + return ( + + +
+ + {team.name} ({team.members.length}) + + + {/* */} +
+
+ + + {team.members.length > 0 ? ( + team.members.map((member) => { + return ; + }) + ) : ( +
+ There is no member for filtered value in the team{' '} + + {' '} + {team.name} + +
+ )} +
+
+ ); + })} +
+
+ ); +} diff --git a/apps/web/lib/features/all-teams-members.tsx b/apps/web/lib/features/all-teams-members.tsx new file mode 100644 index 000000000..a08034089 --- /dev/null +++ b/apps/web/lib/features/all-teams-members.tsx @@ -0,0 +1,49 @@ +import { useRecoilValue } from 'recoil'; +import { IssuesView } from '@app/constants'; +import { IOrganizationTeamList } from '@app/interfaces'; +import { fullWidthState } from '@app/stores/fullWidth'; +import { Container } from 'lib/components'; +import UserTeamCardSkeletonCard from '@components/shared/skeleton/UserTeamCardSkeleton'; +import InviteUserTeamCardSkeleton from '@components/shared/skeleton/InviteTeamCardSkeleton'; +import { UserCard } from '@components/shared/skeleton/TeamPageSkeleton'; +import TeamsMembersCardView from './all-teams-members-card-view'; +import AllTeamsMembersBlockView from './all-teams-members-block-view'; + +export default function AllTeamsMembers({ + teams, + view = IssuesView.CARDS +}: { + teams: IOrganizationTeamList[]; + view: IssuesView; +}) { + const fullWidth = useRecoilValue(fullWidthState); + let teamsMembersView; + + switch (true) { + case teams.length === 0: + teamsMembersView = ( + +
+ + +
+
+ + + +
+
+ ); + break; + case view === IssuesView.CARDS: + teamsMembersView = ; + break; + case view === IssuesView.BLOCKS: + teamsMembersView = ; + break; + default: + teamsMembersView = ; + } + + return teamsMembersView; +} diff --git a/apps/web/lib/features/all-teams/all-team-members-filter.tsx b/apps/web/lib/features/all-teams/all-team-members-filter.tsx new file mode 100644 index 000000000..8d1daa057 --- /dev/null +++ b/apps/web/lib/features/all-teams/all-team-members-filter.tsx @@ -0,0 +1,124 @@ +import { ITeamsMembersFilter } from '@app/interfaces'; +import { filterValue } from '@app/stores/all-teams'; +import { clsxm } from '@app/utils'; +import { Listbox, Transition } from '@headlessui/react'; +import { ChevronDownIcon, CircleIcon } from 'assets/svg'; +import { Card, Tooltip } from 'lib/components'; +import { Fragment, PropsWithChildren } from 'react'; +import { useRecoilState } from 'recoil'; + +export function MemberFilterOption({ + children, + label, + active = true, + checked = false, + showIcon = true, + icon, + bg +}: PropsWithChildren<{ + label: string; + active?: boolean; + checked?: boolean; + showIcon?: boolean; + icon?: React.ReactNode; + bg?: string; +}>) { + return ( +
+
+ {checked ? ( + + + + ) : ( + <>{showIcon && active && icon} + )} +
{label}
+
+ {children} +
+ ); +} + +export function MemberFilter() { + const options: ITeamsMembersFilter[] = [ + { label: 'All', value: 'all', bg: 'transparent' }, + { label: 'Working now', value: 'running', bg: '#1f973d33' }, + { label: 'Paused', value: 'pause', bg: '#e58484' }, + { label: 'Off', value: 'suspended', bg: '#6b7280' }, + { label: 'Invited', value: 'invited', bg: '#d1ad5b' } + ]; + const [value, setValue] = useRecoilState(filterValue); + + return ( + +
+ setValue(v)}> + {({ open }) => { + return ( + <> + + + + + } + bg={value.bg} + > + + + + + + + {options.map((item) => ( + +
  • + + + + } + /> +
  • +
    + ))} +
    +
    +
    + + ); + }} +
    +
    +
    + ); +} diff --git a/apps/web/lib/features/all-teams/users-teams-block/member-block.tsx b/apps/web/lib/features/all-teams/users-teams-block/member-block.tsx new file mode 100644 index 000000000..c2cffe9ae --- /dev/null +++ b/apps/web/lib/features/all-teams/users-teams-block/member-block.tsx @@ -0,0 +1,79 @@ +import { useTimer } from '@app/hooks'; +import { IOrganizationTeamList, ITimerStatusEnum, OT_Member } from '@app/interfaces'; +import { clsxm } from '@app/utils'; +import { Card, HorizontalSeparator } from 'lib/components'; +import { getTimerStatusValue } from 'lib/features/timer/timer-status'; +import { useMemo } from 'react'; +import MemberBoxInfo from './user-info'; +import { BlockCardMemberTodayWorked } from './user-team-today-worked'; +import UserTeamActiveBlockTaskInfo from './user-team-active-task'; +import UserTeamActiveTaskTimesBlock from './user-team-active-task-times'; +import UserTeamActiveTaskEstimateBlock from './user-team-task-estimate'; + +const cardColorType = { + running: ' border-green-300', + idle: ' border-[#F5BEBE]', + online: ' border-green-300', + pause: ' border-[#EFCF9E]', + suspended: ' border-[#DCD6D6]' +}; + +interface Member extends OT_Member { + teams: { team: IOrganizationTeamList; activeTaskId?: string | null }[]; +} + +export default function UserTeamBlockCard({ member }: { member: Member }) { + const { timerStatus } = useTimer(); + + const timerStatusValue: ITimerStatusEnum = useMemo(() => { + return getTimerStatusValue(timerStatus, member, true); + }, [timerStatus, member]); + + return ( +
    + +
    + + {/* total time */} +
    + + {/*
    {menu}
    */} +
    +
    + + + + <> + {member.teams.map((team) => ( +
    + <> +
    {team.team.name}
    + + + +
    +
    + +
    + +
    + +
    + ))} + +
    +
    + ); +} diff --git a/apps/web/lib/features/all-teams/users-teams-block/user-info.tsx b/apps/web/lib/features/all-teams/users-teams-block/user-info.tsx new file mode 100644 index 000000000..5910c8b5a --- /dev/null +++ b/apps/web/lib/features/all-teams/users-teams-block/user-info.tsx @@ -0,0 +1,8 @@ +import { useTeamMemberCard } from '@app/hooks'; +import { OT_Member } from '@app/interfaces'; +import { UserBoxInfo } from 'lib/features/team/user-team-block/user-info'; + +export default function MemberBoxInfo({ member }: { member: OT_Member }) { + const memberInfo = useTeamMemberCard(member); + return ; +} diff --git a/apps/web/lib/features/all-teams/users-teams-block/user-team-active-task-times.tsx b/apps/web/lib/features/all-teams/users-teams-block/user-team-active-task-times.tsx new file mode 100644 index 000000000..5a1cf6ae8 --- /dev/null +++ b/apps/web/lib/features/all-teams/users-teams-block/user-team-active-task-times.tsx @@ -0,0 +1,37 @@ +import { useTeamMemberCard, useTeamTasks } from '@app/hooks'; +import { ITeamTask, OT_Member } from '@app/interfaces'; +import { TaskTimes } from 'lib/features/task/task-times'; +import { useEffect, useState } from 'react'; + +export default function UserTeamActiveTaskTimesBlock({ + member, + activeTaskId +}: { + member: OT_Member; + activeTaskId: string; +}) { + const memberInfo = useTeamMemberCard(member); + + const { getTaskById } = useTeamTasks(); + + const [activeTask, setActiveTask] = useState(null); + + useEffect(() => { + getTaskById(activeTaskId || '') + .then((response) => setActiveTask(response.data)) + .catch((_) => console.log(_)); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + ); +} diff --git a/apps/web/lib/features/all-teams/users-teams-block/user-team-active-task.tsx b/apps/web/lib/features/all-teams/users-teams-block/user-team-active-task.tsx new file mode 100644 index 000000000..aa8126348 --- /dev/null +++ b/apps/web/lib/features/all-teams/users-teams-block/user-team-active-task.tsx @@ -0,0 +1,41 @@ +import { useTeamMemberCard, useTeamTasks, useTMCardTaskEdit } from '@app/hooks'; +import { ITeamTask, OT_Member } from '@app/interfaces'; +import { TaskBlockInfo } from 'lib/features/team/user-team-block/task-info'; +import { useEffect, useState } from 'react'; + +export default function UserTeamActiveBlockTaskInfo({ + member, + activeTaskId +}: { + member: OT_Member; + activeTaskId: string; +}) { + const memberInfo = useTeamMemberCard(member); + const [activeTask, setActiveTask] = useState(null); + const taskEdition = useTMCardTaskEdit(activeTask); + + const { getTaskById } = useTeamTasks(); + + useEffect(() => { + getTaskById(activeTaskId || '') + .then((response) => setActiveTask(response.data)) + .catch((_) => console.log(_)); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + <> + {activeTask?.id ? ( + + ) : ( +
    No active task
    + )} + + ); +} diff --git a/apps/web/lib/features/all-teams/users-teams-block/user-team-task-estimate.tsx b/apps/web/lib/features/all-teams/users-teams-block/user-team-task-estimate.tsx new file mode 100644 index 000000000..e5334182d --- /dev/null +++ b/apps/web/lib/features/all-teams/users-teams-block/user-team-task-estimate.tsx @@ -0,0 +1,37 @@ +import { useTeamMemberCard, useTeamTasks, useTMCardTaskEdit } from '@app/hooks'; +import { ITeamTask, OT_Member } from '@app/interfaces'; +import { TaskEstimateInfo } from 'lib/features/team/user-team-card/task-estimate'; +import { useEffect, useState } from 'react'; + +export default function UserTeamActiveTaskEstimateBlock({ + member, + activeTaskId +}: { + member: OT_Member; + activeTaskId: string; +}) { + const memberInfo = useTeamMemberCard(member); + const [activeTask, setActiveTask] = useState(null); + const taskEdition = useTMCardTaskEdit(activeTask); + + const { getTaskById } = useTeamTasks(); + + useEffect(() => { + getTaskById(activeTaskId || '') + .then((response) => setActiveTask(response.data)) + .catch((_) => console.log(_)); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + ); +} diff --git a/apps/web/lib/features/all-teams/users-teams-block/user-team-today-worked.tsx b/apps/web/lib/features/all-teams/users-teams-block/user-team-today-worked.tsx new file mode 100644 index 000000000..dbe12596a --- /dev/null +++ b/apps/web/lib/features/all-teams/users-teams-block/user-team-today-worked.tsx @@ -0,0 +1,33 @@ +import { secondsToTime } from '@app/helpers'; +import { useTaskStatistics, useTeamMemberCard } from '@app/hooks'; +import { OT_Member } from '@app/interfaces'; +import { timerSecondsState } from '@app/stores'; +import { clsxm } from '@app/utils'; +import { Text } from 'lib/components'; +import { useTranslations } from 'next-intl'; +import { useRecoilValue } from 'recoil'; + +export function BlockCardMemberTodayWorked({ member }: { member: OT_Member }) { + const t = useTranslations(); + const memberInfo = useTeamMemberCard(member); + + const seconds = useRecoilValue(timerSecondsState); + const { activeTaskTotalStat, addSeconds } = useTaskStatistics(seconds); + + const { h, m } = secondsToTime( + ((member?.totalTodayTasks && + member?.totalTodayTasks.reduce( + (previousValue, currentValue) => previousValue + currentValue.duration, + 0 + )) || + activeTaskTotalStat?.duration || + 0) + addSeconds + ); + + return ( +
    + {t('common.TOTAL_WORKED_TODAY')} + {memberInfo.isAuthUser ? `${h}h : ${m}m` : `0h : 0m`} +
    + ); +} diff --git a/apps/web/lib/features/all-teams/users-teams-card/member-infos.tsx b/apps/web/lib/features/all-teams/users-teams-card/member-infos.tsx new file mode 100644 index 000000000..a43b32dfd --- /dev/null +++ b/apps/web/lib/features/all-teams/users-teams-card/member-infos.tsx @@ -0,0 +1,8 @@ +import { useTeamMemberCard } from '@app/hooks'; +import { OT_Member } from '@app/interfaces'; +import { UserInfo } from 'lib/features/team/user-team-card/user-info'; + +export default function MemberInfo({ member }: { member: OT_Member }) { + const memberInfo = useTeamMemberCard(member); + return ; +} diff --git a/apps/web/lib/features/all-teams/users-teams-card/user-card.tsx b/apps/web/lib/features/all-teams/users-teams-card/user-card.tsx new file mode 100644 index 000000000..3957a7797 --- /dev/null +++ b/apps/web/lib/features/all-teams/users-teams-card/user-card.tsx @@ -0,0 +1,92 @@ +import { ITeamTask, OT_Member } from '@app/interfaces'; +import { clsxm } from '@app/utils'; +import { Transition } from '@headlessui/react'; +import { SixSquareGridIcon } from 'assets/svg'; +import { Card, VerticalSeparator } from 'lib/components'; +import MemberInfo from './member-infos'; +import UserTeamActiveTaskInfo from './user-team-active-task'; +import UserTeamActiveTaskTimes from './user-team-active-task-times'; +import UserTeamActiveTaskEstimate from './user-team-task-estimate'; +import UserTeamActiveTaskTodayWorked from './user-team-today-worked'; +import { UserTeamCardMenu } from 'lib/features/team/user-team-card/user-team-card-menu'; +import { useTeamMemberCard, useTeamTasks, useTMCardTaskEdit } from '@app/hooks'; +import { useEffect, useState } from 'react'; + +export default function UserTeamCard({ member }: { member: OT_Member }) { + return ( + + + + ); +} + +function UserActiveTaskMenu({ member }: { member: OT_Member }) { + const memberInfo = useTeamMemberCard(member); + const [activeTask, setActiveTask] = useState(null); + const taskEdition = useTMCardTaskEdit(activeTask); + + const { getTaskById } = useTeamTasks(); + + useEffect(() => { + getTaskById(member.activeTaskId || '') + .then((response) => setActiveTask(response.data)) + .catch((_) => console.log(_)); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return ( + <> + + + ); +} diff --git a/apps/web/lib/features/all-teams/users-teams-card/user-team-active-task-times.tsx b/apps/web/lib/features/all-teams/users-teams-card/user-team-active-task-times.tsx new file mode 100644 index 000000000..53545b014 --- /dev/null +++ b/apps/web/lib/features/all-teams/users-teams-card/user-team-active-task-times.tsx @@ -0,0 +1,30 @@ +import { useTeamMemberCard, useTeamTasks } from '@app/hooks'; +import { ITeamTask, OT_Member } from '@app/interfaces'; +import { TaskTimes } from 'lib/features/task/task-times'; +import { useEffect, useState } from 'react'; + +export default function UserTeamActiveTaskTimes({ member }: { member: OT_Member }) { + const memberInfo = useTeamMemberCard(member); + + const { getTaskById } = useTeamTasks(); + + const [activeTask, setActiveTask] = useState(null); + + useEffect(() => { + getTaskById(member.activeTaskId || '') + .then((response) => setActiveTask(response.data)) + .catch((_) => console.log(_)); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + ); +} diff --git a/apps/web/lib/features/all-teams/users-teams-card/user-team-active-task.tsx b/apps/web/lib/features/all-teams/users-teams-card/user-team-active-task.tsx new file mode 100644 index 000000000..ded3e360e --- /dev/null +++ b/apps/web/lib/features/all-teams/users-teams-card/user-team-active-task.tsx @@ -0,0 +1,35 @@ +import { useTeamMemberCard, useTeamTasks, useTMCardTaskEdit } from '@app/hooks'; +import { ITeamTask, OT_Member } from '@app/interfaces'; +import { TaskInfo } from 'lib/features/team/user-team-card/task-info'; +import { useEffect, useState } from 'react'; + +export default function UserTeamActiveTaskInfo({ member }: { member: OT_Member }) { + const memberInfo = useTeamMemberCard(member); + const [activeTask, setActiveTask] = useState(null); + const taskEdition = useTMCardTaskEdit(activeTask); + + const { getTaskById } = useTeamTasks(); + + useEffect(() => { + getTaskById(member.activeTaskId || '') + .then((response) => setActiveTask(response.data)) + .catch((_) => console.log(_)); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + <> + {activeTask?.id ? ( + + ) : ( +
    --
    + )} + + ); +} diff --git a/apps/web/lib/features/all-teams/users-teams-card/user-team-task-estimate.tsx b/apps/web/lib/features/all-teams/users-teams-card/user-team-task-estimate.tsx new file mode 100644 index 000000000..e61f1702e --- /dev/null +++ b/apps/web/lib/features/all-teams/users-teams-card/user-team-task-estimate.tsx @@ -0,0 +1,29 @@ +import { useTeamMemberCard, useTeamTasks, useTMCardTaskEdit } from '@app/hooks'; +import { ITeamTask, OT_Member } from '@app/interfaces'; +import { TaskEstimateInfo } from 'lib/features/team/user-team-card/task-estimate'; +import { useEffect, useState } from 'react'; + +export default function UserTeamActiveTaskEstimate({ member }: { member: OT_Member }) { + const memberInfo = useTeamMemberCard(member); + const [activeTask, setActiveTask] = useState(null); + const taskEdition = useTMCardTaskEdit(activeTask); + + const { getTaskById } = useTeamTasks(); + + useEffect(() => { + getTaskById(member.activeTaskId || '') + .then((response) => setActiveTask(response.data)) + .catch((_) => console.log(_)); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + ); +} diff --git a/apps/web/lib/features/all-teams/users-teams-card/user-team-today-worked.tsx b/apps/web/lib/features/all-teams/users-teams-card/user-team-today-worked.tsx new file mode 100644 index 000000000..c2319e801 --- /dev/null +++ b/apps/web/lib/features/all-teams/users-teams-card/user-team-today-worked.tsx @@ -0,0 +1,12 @@ +import { useTeamMemberCard } from '@app/hooks'; +import { OT_Member } from '@app/interfaces'; +import { TodayWorkedTime } from 'lib/features/task/task-times'; + +export default function UserTeamActiveTaskTodayWorked({ member }: { member: OT_Member }) { + const memberInfo = useTeamMemberCard(member); + return ( +
    + +
    + ); +} diff --git a/apps/web/lib/features/task/daily-plan/future-tasks.tsx b/apps/web/lib/features/task/daily-plan/future-tasks.tsx index 303aeedbe..ff4af85e1 100644 --- a/apps/web/lib/features/task/daily-plan/future-tasks.tsx +++ b/apps/web/lib/features/task/daily-plan/future-tasks.tsx @@ -5,10 +5,20 @@ import { TaskCard } from '../task-card'; import { Button } from '@components/ui/button'; import { useCanSeeActivityScreen, useDailyPlan } from '@app/hooks'; import { ReloadIcon } from '@radix-ui/react-icons'; +import { useRecoilValue } from 'recoil'; +import { dailyPlanViewHeaderTabs } from '@app/stores/header-tabs'; +import TaskBlockCard from '../task-block-card'; +import { clsxm } from '@app/utils'; +import { HorizontalSeparator } from 'lib/components'; +import { useState } from 'react'; +import { AlertPopup } from 'lib/components'; export function FutureTasks({ profile }: { profile: any }) { const { deleteDailyPlan, deleteDailyPlanLoading, futurePlans } = useDailyPlan(); const canSeeActivity = useCanSeeActivityScreen(); + const [popupOpen, setPopupOpen] = useState(false); + + const view = useRecoilValue(dailyPlanViewHeaderTabs); return (
    @@ -22,11 +32,14 @@ export function FutureTasks({ profile }: { profile: any }) { - -
    - {formatDayPlanDate(plan.date.toString())} ({plan.tasks?.length}) + +
    +
    + {formatDayPlanDate(plan.date.toString())} ({plan.tasks?.length}) +
    +
    @@ -34,38 +47,72 @@ export function FutureTasks({ profile }: { profile: any }) { {/* Plan tasks list */} -
      - {plan.tasks?.map((task) => ( - - ))} +
        + {plan.tasks?.map((task) => + view === 'CARDS' ? ( + + ) : ( + + ) + )}
      {/* Delete Plan */} {canSeeActivity ? (
      - + } > - {deleteDailyPlanLoading && ( - - )} - Delete this plan - + {/*button confirm*/} + + {/*button cancel*/} + +
      ) : ( <> diff --git a/apps/web/lib/features/task/daily-plan/outstanding-all.tsx b/apps/web/lib/features/task/daily-plan/outstanding-all.tsx index b6619791e..c0ca5b92d 100644 --- a/apps/web/lib/features/task/daily-plan/outstanding-all.tsx +++ b/apps/web/lib/features/task/daily-plan/outstanding-all.tsx @@ -1,50 +1,65 @@ import { EmptyPlans } from 'lib/features/user-profile-plans'; import { TaskCard } from '../task-card'; import { useDailyPlan } from '@app/hooks'; -import { TaskEstimatedcount } from '.'; +import { TaskEstimatedCount } from '.'; +import { useRecoilValue } from 'recoil'; +import { dailyPlanViewHeaderTabs } from '@app/stores/header-tabs'; +import TaskBlockCard from '../task-block-card'; +import { clsxm } from '@app/utils'; interface OutstandingAll { - profile: any, + profile: any; } export function OutstandingAll({ profile }: OutstandingAll) { - const { outstandingPlans } = useDailyPlan(); - const displayedTaskId = new Set(); - return ( -
      - - {outstandingPlans?.length > 0 ? ( - <> - {outstandingPlans?.map((plan) => ( - <> - {/* */} -
        - {plan?.tasks?.map((task) => { - //If the task is already displayed, skip it - if (displayedTaskId.has(task.id)) { return null; } - // Add the task to the Set to avoid displaying it again - displayedTaskId.add(task.id); - return - } - )} -
      - - ))} - - - ) : ( - - )} -
      - ); + const { outstandingPlans } = useDailyPlan(); + const view = useRecoilValue(dailyPlanViewHeaderTabs); + const displayedTaskId = new Set(); + return ( +
      + + {outstandingPlans?.length > 0 ? ( + <> + {outstandingPlans?.map((plan) => ( + <> + {/* */} +
        + {plan?.tasks?.map((task) => { + //If the task is already displayed, skip it + if (displayedTaskId.has(task.id)) { + return null; + } + // Add the task to the Set to avoid displaying it again + displayedTaskId.add(task.id); + return view === 'CARDS' ? ( + + ) : ( + + ); + })} +
      + + ))} + + ) : ( + + )} +
      + ); } diff --git a/apps/web/lib/features/task/daily-plan/outstanding-date.tsx b/apps/web/lib/features/task/daily-plan/outstanding-date.tsx index ce94aeb5b..9eaa232ac 100644 --- a/apps/web/lib/features/task/daily-plan/outstanding-date.tsx +++ b/apps/web/lib/features/task/daily-plan/outstanding-date.tsx @@ -3,12 +3,18 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@c import { EmptyPlans, PlanHeader } from 'lib/features/user-profile-plans'; import { TaskCard } from '../task-card'; import { useDailyPlan } from '@app/hooks'; +import { HorizontalSeparator } from 'lib/components'; +import TaskBlockCard from '../task-block-card'; +import { clsxm } from '@app/utils'; +import { useRecoilValue } from 'recoil'; +import { dailyPlanViewHeaderTabs } from '@app/stores/header-tabs'; -interface IOutstandingFieltreDate { - profile: any +interface IOutstandingFilterDate { + profile: any; } -export function OutstandingFieltreDate({ profile }: IOutstandingFieltreDate) { +export function OutstandingFilterDate({ profile }: IOutstandingFilterDate) { const { outstandingPlans } = useDailyPlan(); + const view = useRecoilValue(dailyPlanViewHeaderTabs); return (
      {outstandingPlans?.length > 0 ? ( @@ -21,11 +27,14 @@ export function OutstandingFieltreDate({ profile }: IOutstandingFieltreDate) { - -
      - {formatDayPlanDate(plan.date.toString())} ({plan.tasks?.length}) + +
      +
      + {formatDayPlanDate(plan.date.toString())} ({plan.tasks?.length}) +
      +
      @@ -33,21 +42,32 @@ export function OutstandingFieltreDate({ profile }: IOutstandingFieltreDate) { {/* Plan tasks list */} -
        - {plan.tasks?.map((task) => ( - - ))} +
          + {plan.tasks?.map((task) => + view === 'CARDS' ? ( + + ) : ( + + ) + )}
        diff --git a/apps/web/lib/features/task/daily-plan/outstanding.tsx b/apps/web/lib/features/task/daily-plan/outstanding.tsx index b1b441281..61c3a6898 100644 --- a/apps/web/lib/features/task/daily-plan/outstanding.tsx +++ b/apps/web/lib/features/task/daily-plan/outstanding.tsx @@ -1,6 +1,6 @@ interface IOutstanding { - filtre?: any; + filter?: any; } -export function Outstanding({ filtre }: IOutstanding) { - return (<>{filtre}); +export function Outstanding({ filter }: IOutstanding) { + return (<>{filter}); } diff --git a/apps/web/lib/features/task/daily-plan/past-tasks.tsx b/apps/web/lib/features/task/daily-plan/past-tasks.tsx index 07a358e95..249f3ebdd 100644 --- a/apps/web/lib/features/task/daily-plan/past-tasks.tsx +++ b/apps/web/lib/features/task/daily-plan/past-tasks.tsx @@ -3,9 +3,16 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@c import { EmptyPlans, PlanHeader } from 'lib/features/user-profile-plans'; import { TaskCard } from '../task-card'; import { useDailyPlan } from '@app/hooks'; +import { useRecoilValue } from 'recoil'; +import { dailyPlanViewHeaderTabs } from '@app/stores/header-tabs'; +import { HorizontalSeparator } from 'lib/components'; +import { clsxm } from '@app/utils'; +import TaskBlockCard from '../task-block-card'; export function PastTasks({ profile }: { profile: any }) { const { pastPlans } = useDailyPlan(); + + const view = useRecoilValue(dailyPlanViewHeaderTabs); return (
        {pastPlans?.length > 0 ? ( @@ -18,11 +25,14 @@ export function PastTasks({ profile }: { profile: any }) { - -
        - {formatDayPlanDate(plan.date.toString())} ({plan.tasks?.length}) + +
        +
        + {formatDayPlanDate(plan.date.toString())} ({plan.tasks?.length}) +
        +
        @@ -30,20 +40,31 @@ export function PastTasks({ profile }: { profile: any }) { {/* Plan tasks list */} -
          - {plan.tasks?.map((task) => ( - - ))} +
            + {plan.tasks?.map((task) => + view === 'CARDS' ? ( + + ) : ( + + ) + )}
          diff --git a/apps/web/lib/features/task/daily-plan/task-estimated-count.tsx b/apps/web/lib/features/task/daily-plan/task-estimated-count.tsx index c7b4de1e2..811f7cbf7 100644 --- a/apps/web/lib/features/task/daily-plan/task-estimated-count.tsx +++ b/apps/web/lib/features/task/daily-plan/task-estimated-count.tsx @@ -1,44 +1,44 @@ import { secondsToTime } from '@app/helpers'; import { IDailyPlan } from '@app/interfaces'; -import { VerticalSeparator } from 'lib/components' -import React from 'react' +import { VerticalSeparator } from 'lib/components'; -interface ITaskEstimatedcount { - outstandingPlans: any[] +interface ITaskEstimatedCount { + outstandingPlans: any[]; } -export function TaskEstimatedcount({ outstandingPlans }: ITaskEstimatedcount) { - const element = outstandingPlans?.map((plan: IDailyPlan) => plan.tasks?.map((task) => task)); - const { timesEstemated, totalTasks } = estimatedTotalTime(element || []); - const { h: hour, m: minute } = secondsToTime((timesEstemated || 0)); - return ( -
          -
          - Estimated: - {hour}h{minute}m -
          - -
          - Total tasks: - {totalTasks} -
          -
          - ) +export function TaskEstimatedCount({ outstandingPlans }: ITaskEstimatedCount) { + const element = outstandingPlans?.map((plan: IDailyPlan) => plan.tasks?.map((task) => task)); + const { timesEstimated, totalTasks } = estimatedTotalTime(element || []); + const { h: hour, m: minute } = secondsToTime(timesEstimated || 0); + return ( +
          +
          + Estimated: + + {hour}h{minute}m + +
          + +
          + Total tasks: + {totalTasks} +
          +
          + ); } - export function estimatedTotalTime(data: any) { - // Flatten the data and reduce to calculate the sum of estimates without duplicates - const uniqueTasks = data.flat().reduce((acc: any, task: any) => { - if (!acc[task.id]) { - acc[task.id] = task.estimate; - } - return acc; - }, {}); + // Flatten the data and reduce to calculate the sum of estimates without duplicates + const uniqueTasks = data.flat().reduce((acc: any, task: any) => { + if (!acc[task.id]) { + acc[task.id] = task.estimate; + } + return acc; + }, {}); - // Calculate the total of estimates - const timesEstemated = Object.values(uniqueTasks)?.reduce((total: number, estimate: any) => total + estimate, 0); - // Calculate the total of tasks - const totalTasks = Object.values(uniqueTasks)?.length; + // Calculate the total of estimates + const timesEstimated = Object.values(uniqueTasks)?.reduce((total: number, estimate: any) => total + estimate, 0); + // Calculate the total of tasks + const totalTasks = Object.values(uniqueTasks)?.length; - return { timesEstemated, totalTasks }; + return { timesEstimated, totalTasks }; } diff --git a/apps/web/lib/features/task/daily-plan/views-header-tabs.tsx b/apps/web/lib/features/task/daily-plan/views-header-tabs.tsx new file mode 100644 index 000000000..6f18de4ac --- /dev/null +++ b/apps/web/lib/features/task/daily-plan/views-header-tabs.tsx @@ -0,0 +1,47 @@ +import { IssuesView } from '@app/constants'; +import { dailyPlanViewHeaderTabs } from '@app/stores/header-tabs'; +import { clsxm } from '@app/utils'; +import { QueueListIcon, Squares2X2Icon, TableCellsIcon } from '@heroicons/react/20/solid'; +import { Tooltip } from 'lib/components'; +import { DottedLanguageObjectStringPaths, useTranslations } from 'next-intl'; +import { useRecoilState } from 'recoil'; + +export default function ViewsHeaderTabs() { + const t = useTranslations(); + const options = [ + { label: 'CARDS', icon: QueueListIcon, view: IssuesView.CARDS }, + { label: 'TABLE', icon: TableCellsIcon, view: IssuesView.TABLE }, + { label: 'BLOCKS', icon: Squares2X2Icon, view: IssuesView.BLOCKS } + ]; + + const [view, setView] = useRecoilState(dailyPlanViewHeaderTabs); + + return ( +
          + {options.map(({ label, icon: Icon, view: optionView }) => ( + + + + ))} +
          + ); +} diff --git a/apps/web/lib/features/task/task-block-card.tsx b/apps/web/lib/features/task/task-block-card.tsx new file mode 100644 index 000000000..3b6c7544b --- /dev/null +++ b/apps/web/lib/features/task/task-block-card.tsx @@ -0,0 +1,155 @@ +import { ITeamTask } from '@app/interfaces'; +import { TaskAllStatusTypes } from './task-all-status-type'; +import MenuKanbanCard from '@components/pages/kanban/menu-kanban-card'; +import { TaskInput } from './task-input'; +import { useRecoilState } from 'recoil'; +import { activeTeamTaskId } from '@app/stores'; +import Link from 'next/link'; +import { useAuthenticateUser, useOrganizationTeams, useTaskStatistics, useTeamMemberCard } from '@app/hooks'; +import ImageComponent, { ImageOverlapperProps } from '../../components/image-overlapper'; +import { TaskIssueStatus } from './task-issue'; +import { Priority, setCommentIconColor } from 'lib/components/kanban-card'; +import CircularProgress from '@components/ui/svgs/circular-progress'; +import { HorizontalSeparator } from 'lib/components'; +import { TaskStatus } from '@app/constants'; +import { secondsToTime } from '@app/helpers'; + +interface TaskItemProps { + task: ITeamTask; +} + +export default function TaskBlockCard(props: TaskItemProps) { + const { task } = props; + const [activeTask, setActiveTask] = useRecoilState(activeTeamTaskId); + const { activeTeam } = useOrganizationTeams(); + const { user } = useAuthenticateUser(); + const { getEstimation } = useTaskStatistics(0); + const members = activeTeam?.members || []; + const currentUser = members.find((m) => m.employee.userId === user?.id); + + let totalWorkedTasksTimer = 0; + activeTeam?.members?.forEach((member) => { + const totalWorkedTasks = member?.totalWorkedTasks?.find((i) => i.id === task?.id) || null; + if (totalWorkedTasks) { + totalWorkedTasksTimer += totalWorkedTasks.duration; + } + }); + + const memberInfo = useTeamMemberCard(currentUser); + + const taskAssignee: ImageOverlapperProps[] = task.members?.map((member: any) => { + return { + id: member.user.id, + url: member.user.imageUrl, + alt: member.user.firstName + }; + }); + + const progress = getEstimation(null, task, totalWorkedTasksTimer || 1, task.estimate || 0); + + const currentMember = activeTeam?.members.find((member) => member.id === memberInfo.member?.id || task?.id); + + const { h, m, s } = secondsToTime( + (currentMember?.totalWorkedTasks && + currentMember?.totalWorkedTasks?.length && + currentMember?.totalWorkedTasks + .filter((t) => t.id === task?.id) + .reduce((previousValue, currentValue) => previousValue + currentValue.duration, 0)) || + 0 + ); + + return ( +
          +
          +
          + + + + + + +
          +
          +
          + {activeTask?.id == task.id ? ( + <> +
          + { + // TODO: implement + console.log(e); + }} + onEnterKey={() => { + setActiveTask({ id: '' }); + }} + /> +
          + + ) : ( + +
          + {task.issueType && ( + + + + + + )} + #{task.number} + {task.title} + + {task.priority && } + +
          + + )} +
          + + +
          +
          + +
          +
          +
          + {task.status === TaskStatus.INPROGRESS ? ( +
          + Live: +

          + {h}h : {m}m : {s}s +

          +
          + ) : ( +
          + Worked: +

          + {h}h : {m}m : {s}s +

          +
          + )} +
          + + {task.issueType && ( +
          +
          +
          + )} +
          +
          +
          + ); +} diff --git a/apps/web/lib/features/task/task-card.tsx b/apps/web/lib/features/task/task-card.tsx index 0f093fce5..192c035fd 100644 --- a/apps/web/lib/features/task/task-card.tsx +++ b/apps/web/lib/features/task/task-card.tsx @@ -1,9 +1,10 @@ 'use client'; -import { secondsToTime } from '@app/helpers'; +import { secondsToTime, tomorrowDate } from '@app/helpers'; import { I_TeamMemberCardHook, I_UserProfilePage, + useAuthenticateUser, useCanSeeActivityScreen, useDailyPlan, useModal, @@ -17,6 +18,7 @@ import { } from '@app/hooks'; import ImageComponent, { ImageOverlapperProps } from 'lib/components/image-overlapper'; import { + DailyPlanStatusEnum, IClassName, IDailyPlan, IDailyPlanMode, @@ -39,7 +41,7 @@ import { } from 'lib/components'; import Link from 'next/link'; import { useRouter } from 'next/navigation'; -import { useCallback, useState } from 'react'; +import { useCallback, useState, useTransition } from 'react'; import { SetterOrUpdater, useRecoilValue } from 'recoil'; import { TaskEstimateInfo } from '../team/user-team-card/task-estimate'; import { TimerButton } from '../timer/timer-button'; @@ -53,6 +55,7 @@ import { SixSquareGridIcon, ThreeCircleOutlineVerticalIcon } from 'assets/svg'; import { CreateDailyPlanFormModal } from '../daily-plan/create-daily-plan-form-modal'; import { AddTaskToPlan } from '../daily-plan/add-task-to-plan'; import { AddWorkTimeAndEstimatesToPlan } from '../daily-plan/plans-work-time-and-estimate'; +import { ReloadIcon } from '@radix-ui/react-icons'; type Props = { active?: boolean; @@ -632,7 +635,41 @@ export function PlanTask({ employeeId?: string; chooseMember?: boolean; }) { + const t = useTranslations(); + const [isPending, startTransition] = useTransition(); const { closeModal, isOpen, openModal } = useModal(); + const { createDailyPlan } = useDailyPlan(); + const { user } = useAuthenticateUser(); + + const handleOpenModal = () => { + if (planMode === 'custom') { + openModal(); + } else if (planMode === 'today') { + startTransition(() => { + createDailyPlan({ + workTimePlanned: 0, + taskId, + date: new Date(), + status: DailyPlanStatusEnum.OPEN, + tenantId: user?.tenantId ?? '', + employeeId: employeeId, + organizationId: user?.employee.organizationId + }); + }); + } else { + startTransition(() => { + createDailyPlan({ + workTimePlanned: 0, + taskId, + date: tomorrowDate, + status: DailyPlanStatusEnum.OPEN, + tenantId: user?.tenantId ?? '', + employeeId: employeeId, + organizationId: user?.employee.organizationId + }); + }); + } + }; return ( <> @@ -641,7 +678,7 @@ export function PlanTask({ 'font-normal whitespace-nowrap transition-all', 'hover:font-semibold hover:transition-all cursor-pointer' )} - onClick={openModal} + onClick={handleOpenModal} > - {planMode === 'today' && 'Plan for today'} - {planMode === 'tomorow' && 'Plan for tomorow'} - {planMode === 'custom' && 'Plan for some day'} + {planMode === 'today' && ( + + {isPending ? ( + + ) : ( + t('dailyPlan.PLAN_FOR_TODAY') + )} + + )} + {planMode === 'tomorow' && ( + + {isPending ? ( + + ) : ( + t('dailyPlan.PLAN_FOR_TOMORROW') + )} + + )} + {planMode === 'custom' && t('dailyPlan.PLAN_FOR_SOME_DAY')} ); } export function AddTaskToPlanComponent({ task, employee }: { task: ITeamTask; employee?: OT_Member }) { + const t = useTranslations(); const { closeModal, isOpen, openModal } = useModal(); return ( - Add this task to a plan + {t('dailyPlan.ADD_TASK_TO_PLAN')} ); } export function RemoveTaskFromPlan({ task, plan, member }: { task: ITeamTask; member?: OT_Member; plan?: IDailyPlan }) { + const t = useTranslations(); const { removeTaskFromPlan } = useDailyPlan(); const data: IDailyPlanTasksUpdate = { taskId: task.id, employeeId: member?.employeeId }; const onClick = () => { @@ -689,7 +744,7 @@ export function RemoveTaskFromPlan({ task, plan, member }: { task: ITeamTask; me )} onClick={onClick} > - Remove from this plan + {t('dailyPlan.REMOVE_FROM_THIS_PLAN')} ); } diff --git a/apps/web/lib/features/team/team-item.tsx b/apps/web/lib/features/team/team-item.tsx index 98373b8da..636a64557 100644 --- a/apps/web/lib/features/team/team-item.tsx +++ b/apps/web/lib/features/team/team-item.tsx @@ -152,3 +152,44 @@ export function TeamItem({
        ); } + +export function AllTeamItem({ title, count }: { title: string; count: number }) { + return ( + +
        +
        +
        + {imgTitle(title)} +
        +
        +
        + + {title} + + + {count ? `(${count})` : ''} + +
        +
        + + ); +} diff --git a/apps/web/lib/features/team/team-outstanding-notifications.tsx b/apps/web/lib/features/team/team-outstanding-notifications.tsx index afd691602..991e45736 100644 --- a/apps/web/lib/features/team/team-outstanding-notifications.tsx +++ b/apps/web/lib/features/team/team-outstanding-notifications.tsx @@ -1,27 +1,37 @@ 'use client'; import { useAuthenticateUser, useDailyPlan } from '@app/hooks'; -import { IDailyPlan, IUser } from '@app/interfaces'; +import { IDailyPlan, IEmployee, IUser } from '@app/interfaces'; import { Cross2Icon, EyeOpenIcon } from '@radix-ui/react-icons'; import { Tooltip } from 'lib/components'; import { useTranslations } from 'next-intl'; import Link from 'next/link'; import { useEffect, useState } from 'react'; +interface IEmployeeWithOutstanding { + employeeId: string | undefined; + employee: IEmployee | undefined; +} + export function TeamOutstandingNotifications() { - const { getEmployeeDayPlans, outstandingPlans } = useDailyPlan(); + const { getAllDayPlans, dailyPlan, getEmployeeDayPlans, outstandingPlans } = useDailyPlan(); - const { user } = useAuthenticateUser(); + const { isTeamManager, user } = useAuthenticateUser(); useEffect(() => { + getAllDayPlans(); getEmployeeDayPlans(user?.employee.id || ''); - }, [getEmployeeDayPlans, user?.employee.id]); + }, [getAllDayPlans, getEmployeeDayPlans, user?.employee.id]); return ( - <> +
        {outstandingPlans && outstandingPlans.length > 0 && ( )} - + + {dailyPlan.items && dailyPlan.items.length > 0 && isTeamManager && ( + + )} +
        ); } @@ -93,3 +103,95 @@ function UserOutstandingNotification({ outstandingTasks, user }: { outstandingTa ); } + +function ManagerOutstandingUsersNotification({ outstandingTasks }: { outstandingTasks: IDailyPlan[] }) { + const t = useTranslations(); + + // Notification will be displayed 6 hours after the user closed it + const REAPPEAR_INTERVAL = 6 * 60 * 60 * 1000; // 6 hours in milliseconds; + const MANAGER_DISMISSAL_TIMESTAMP_KEY = 'manager-saw-outstanding-notif'; + + const [visible, setVisible] = useState(false); + + const employeeWithOutstanding = outstandingTasks + .filter((plan) => !plan.date?.toString()?.startsWith(new Date()?.toISOString().split('T')[0])) + + .filter((plan) => { + const planDate = new Date(plan.date); + const today = new Date(); + today.setHours(23, 59, 59, 0); + return planDate.getTime() <= today.getTime(); + }) + .map((plan) => ({ + ...plan, + tasks: plan.tasks?.filter((task) => task.status !== 'completed') + })) + .filter((plan) => plan.tasks?.length && plan.tasks.length > 0) + .map((plan) => ({ employeeId: plan.employeeId, employee: plan.employee })); + + const uniqueEmployees: IEmployeeWithOutstanding[] = employeeWithOutstanding.reduce( + (acc: IEmployeeWithOutstanding[], current) => { + const existingEmployee = acc.find((emp) => emp.employeeId === current.employeeId); + + if (!existingEmployee) { + acc.push({ + employeeId: current.employeeId, + employee: current.employee + }); + } + + return acc; + }, + [] + ); + + useEffect(() => { + const checkNotification = () => { + const alreadySeen = + window && parseInt(window?.localStorage.getItem(MANAGER_DISMISSAL_TIMESTAMP_KEY) || '0', 10); + const currentTime = new Date().getTime(); + + if (!alreadySeen || currentTime - alreadySeen > REAPPEAR_INTERVAL) { + setVisible(true); + } + }; + + checkNotification(); + const intervalId = setInterval(checkNotification, REAPPEAR_INTERVAL); + return () => clearInterval(intervalId); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const onClose = () => { + window && window?.localStorage.setItem(MANAGER_DISMISSAL_TIMESTAMP_KEY, new Date().getTime().toString()); + setVisible(false); + }; + return ( + <> + {visible && ( +
        +
        + {t('pages.home.OUTSTANDING_NOTIFICATIONS.SUBJECT')} {uniqueEmployees?.length} team member(s) + with uncompleted tasks, please see{' '} + + {uniqueEmployees?.map((em) => ( + + {em.employee?.fullName},{' '} + + ))} + +
        +
        + + + +
        +
        + )} + + ); +} diff --git a/apps/web/lib/features/team/teams-dropdown.tsx b/apps/web/lib/features/team/teams-dropdown.tsx index a4ac5bdb8..65d8a4a6b 100644 --- a/apps/web/lib/features/team/teams-dropdown.tsx +++ b/apps/web/lib/features/team/teams-dropdown.tsx @@ -7,12 +7,14 @@ import { PlusIcon } from '@heroicons/react/24/solid'; import { Button, Dropdown, Tooltip } from 'lib/components'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { CreateTeamModal } from './create-team-modal'; -import { TeamItem, mapTeamItems } from './team-item'; +import { AllTeamItem, TeamItem, mapTeamItems } from './team-item'; import { useTranslations } from 'next-intl'; +import { useOrganizationAndTeamManagers } from '@app/hooks/features/useOrganizationTeamManagers'; export const TeamsDropDown = ({ publicTeam }: { publicTeam?: boolean }) => { const { user } = useAuthenticateUser(); const { teams, activeTeam, setActiveTeam } = useOrganizationTeams(); + const { userManagedTeams } = useOrganizationAndTeamManagers(); const { timerStatus, stopTimer } = useTimer(); const t = useTranslations(); const { toast } = useToast(); @@ -72,6 +74,10 @@ export const TeamsDropDown = ({ publicTeam }: { publicTeam?: boolean }) => { // loading={teamsFetching} // TODO: Enable loading in future when we implement better data fetching library like TanStack publicTeam={publicTeam} > + {userManagedTeams.length > 1 && ( + + )} + {!publicTeam && ( (defaultTab || 'Today Tasks'); - const [currentOutstanding, setCurrentOutstanding] = useState(defaultOutstanding || 'ALL'); - + const [currentOutstanding, setCurrentOutstanding] = useState(defaultOutstanding || 'ALL'); const screenOutstanding = { - 'ALL': , - "DATE": - } + ALL: , + DATE: + }; const tabsScreens = { 'Today Tasks': , 'Future Tasks': , 'Past Tasks': , 'All Tasks': , - Outstanding: + Outstanding: }; - - useEffect(() => { window.localStorage.setItem('daily-plan-tab', currentTab); - }, [currentTab]); useEffect(() => { @@ -67,7 +68,7 @@ export function UserProfilePlans() { <> {profileDailyPlans?.items?.length > 0 ? (
        -
        +
        {Object.keys(tabsScreens).map((filter, i) => (
        @@ -96,25 +97,30 @@ export function UserProfilePlans() {
        ))}
        - {currentTab === 'Outstanding' && ( - - )} +
        + + {currentTab === 'Outstanding' && ( + + )} +
        {tabsScreens[currentTab]}
        @@ -131,12 +137,15 @@ function AllPlans({ profile, currentTab = 'All Tasks' }: { profile: any; current // Filter plans let filteredPlans: IDailyPlan[] = []; const { deleteDailyPlan, deleteDailyPlanLoading, sortedPlans, todayPlan } = useDailyPlan(); + const [popupOpen, setPopupOpen] = useState(false); filteredPlans = sortedPlans; if (currentTab === 'Today Tasks') filteredPlans = todayPlan; const canSeeActivity = useCanSeeActivityScreen(); + const view = useRecoilValue(dailyPlanViewHeaderTabs); + return (
        {filteredPlans?.length > 0 ? ( @@ -153,11 +162,14 @@ function AllPlans({ profile, currentTab = 'All Tasks' }: { profile: any; current - -
        - {formatDayPlanDate(plan.date.toString())} ({plan.tasks?.length}) + +
        +
        + {formatDayPlanDate(plan.date.toString())} ({plan.tasks?.length}) +
        +
        @@ -165,22 +177,33 @@ function AllPlans({ profile, currentTab = 'All Tasks' }: { profile: any; current {/* Plan tasks list */} -
          - {plan.tasks?.map((task) => ( - - ))} +
            + {plan.tasks?.map((task) => + view === 'CARDS' ? ( + + ) : ( + + ) + )}
          {/* Delete Plan */} @@ -188,17 +211,40 @@ function AllPlans({ profile, currentTab = 'All Tasks' }: { profile: any; current <> {canSeeActivity ? (
          - + } > - {deleteDailyPlanLoading && ( - - )} - Delete this plan - + {/*button confirm*/} + + {/*button cancel*/} + +
          ) : ( <> diff --git a/apps/web/lib/features/user-profile-tasks.tsx b/apps/web/lib/features/user-profile-tasks.tsx index 027b545fa..2de5c3a7a 100644 --- a/apps/web/lib/features/user-profile-tasks.tsx +++ b/apps/web/lib/features/user-profile-tasks.tsx @@ -63,11 +63,10 @@ export function UserProfileTask({ profile, tabFiltered }: Props) { isAuthUser={profile.isAuthUser} activeAuthTask={true} profile={profile} - taskBadgeClassName={` ${ - profile.activeUserTeamTask?.issueType === 'Bug' - ? '!px-[0.3312rem] py-[0.2875rem]' - : '!px-[0.375rem] py-[0.375rem]' - } rounded-sm`} + taskBadgeClassName={` ${profile.activeUserTeamTask?.issueType === 'Bug' + ? '!px-[0.3312rem] py-[0.2875rem]' + : '!px-[0.375rem] py-[0.375rem]' + } rounded-sm`} taskTitleClassName="mt-[0.0625rem]" /> )} @@ -94,11 +93,10 @@ export function UserProfileTask({ profile, tabFiltered }: Props) { activeAuthTask={false} viewType={tabFiltered.tab === 'unassigned' ? 'unassign' : 'default'} profile={profile} - taskBadgeClassName={`${ - task.issueType === 'Bug' - ? '!px-[0.3312rem] py-[0.2875rem]' - : '!px-[0.375rem] py-[0.375rem]' - } rounded-sm`} + taskBadgeClassName={`${task.issueType === 'Bug' + ? '!px-[0.3312rem] py-[0.2875rem]' + : '!px-[0.375rem] py-[0.375rem]' + } rounded-sm`} taskTitleClassName="mt-[0.0625rem]" /> diff --git a/apps/web/locales/ar.json b/apps/web/locales/ar.json index 031e4d8ba..17e85f377 100644 --- a/apps/web/locales/ar.json +++ b/apps/web/locales/ar.json @@ -11,6 +11,8 @@ "CARDS": "بطاقات", "TABLE": "جدول", "KANBAN": "كانبان", + "TEAMS": "فرق", + "COMBINE": "دمج", "TOMORROW": "غدا", "YESTERDAY": "أمس", "INVITE": "دعوة", @@ -93,6 +95,7 @@ "DESCRIPTION": "وصف", "CHILD_ISSUE_TASK": "تحديد القضية الفرعية", "CREATE_TEAM": "إنشاء فريق جديد", + "ALL_TEAMS": "جميع الفرق", "VERIFY_ACCOUNT_MSG": "يرجى التحقق من حسابك قبل البدء في استخدام التطبيق", "CREATE_ROLE": "إنشاء دور", "CREATE": "إنشاء", @@ -569,6 +572,13 @@ }, "CONFIRM_CLOSE_TASK": "يرجى تأكيد رغبتك في إغلاق المهمة" }, + "dailyPlan": { + "PLAN_FOR_TODAY": "خطة اليوم", + "PLAN_FOR_TOMORROW": "خطة الغد", + "PLAN_FOR_SOME_DAY": "خطة ليوم ما", + "ADD_TASK_TO_PLAN": "أضف هذه المهمة إلى الخطة", + "REMOVE_FROM_THIS_PLAN": "إزالة من هذه الخطة" + }, "form": { "NAME_PLACEHOLDER": "أدخل اسمك", "FIRST_NAME_PLACEHOLDER": "الاسم الأول", diff --git a/apps/web/locales/bg.json b/apps/web/locales/bg.json index a9eb65082..3b43adbba 100644 --- a/apps/web/locales/bg.json +++ b/apps/web/locales/bg.json @@ -12,6 +12,8 @@ "VERIFY": "провери", "INVITE": "Покани", "KANBAN": "Канбан", + "TEAMS": "Екипи", + "COMBINE": "Комбиниране", "CARDS": "Карти", "TABLE": "Таблица", "INVITED": "Поканен", @@ -92,6 +94,7 @@ "DESCRIPTION": "Описание", "CHILD_ISSUE_TASK": "Избери детски проблем", "CREATE_TEAM": "Създаване на нов отбор", + "ALL_TEAMS": "Всички екипи", "VERIFY_ACCOUNT_MSG": "Моля, потвърдете профила си, преди да започнете да използвате приложението", "CREATE_ROLE": "Създаване на роля", "CREATE": "Създаване", @@ -569,6 +572,13 @@ }, "CONFIRM_CLOSE_TASK": "Моля, потвърдете ако искате да затворите задачата" }, + "dailyPlan": { + "PLAN_FOR_TODAY": "План за днес", + "PLAN_FOR_TOMORROW": "План за утре", + "PLAN_FOR_SOME_DAY": "План за някой ден", + "ADD_TASK_TO_PLAN": "Добави тази задача към плана", + "REMOVE_FROM_THIS_PLAN": "Премахни от този план" + }, "form": { "NAME_PLACEHOLDER": "Въведете името си", "FIRST_NAME_PLACEHOLDER": "Собствено име", diff --git a/apps/web/locales/de.json b/apps/web/locales/de.json index 3920c8d1b..1ce0d9d1e 100644 --- a/apps/web/locales/de.json +++ b/apps/web/locales/de.json @@ -10,6 +10,8 @@ "CARDS": "Karten", "TABLE": "Tabelle", "KANBAN": "Kanban", + "TEAMS": "Teams", + "COMBINE": "Kombinieren", "YESTERDAY": "Gestern", "REJECTED": "Abgelehnt", "VERIFY": "Verifizieren", @@ -92,6 +94,7 @@ "DESCRIPTION": "Beschreibung", "CHILD_ISSUE_TASK": "Untergeordnetes Ticket auswählen", "CREATE_TEAM": "Neues Team erstellen", + "ALL_TEAMS": "Alle Teams", "VERIFY_ACCOUNT_MSG": "Bitte verifizieren Sie Ihr Konto, bevor Sie die App nutzen", "CREATE_ROLE": "Rolle erstellen", "CREATE": "Erstellen", @@ -569,6 +572,13 @@ }, "CONFIRM_CLOSE_TASK": "Bitte bestätigen Sie, ob Sie die Aufgabe schließen möchten" }, + "dailyPlan": { + "PLAN_FOR_TODAY": "Plan für heute", + "PLAN_FOR_TOMORROW": "Plan für morgen", + "PLAN_FOR_SOME_DAY": "Plan für irgendeinen Tag", + "ADD_TASK_TO_PLAN": "Diese Aufgabe zum Plan hinzufügen", + "REMOVE_FROM_THIS_PLAN": "Aus diesem Plan entfernen" + }, "form": { "NAME_PLACEHOLDER": "Ihren Namen eingeben", "FIRST_NAME_PLACEHOLDER": "Vorname", diff --git a/apps/web/locales/en.json b/apps/web/locales/en.json index 955d35e9d..1dc28e54d 100644 --- a/apps/web/locales/en.json +++ b/apps/web/locales/en.json @@ -13,6 +13,8 @@ "CARDS": "Cards", "TABLE": "Table", "KANBAN": "Kanban", + "TEAMS": "Teams", + "COMBINE": "Combine", "INVITE": "Invite", "INVITED": "Invited", "EXPIRE": "Expire", @@ -92,6 +94,7 @@ "DESCRIPTION": "Description", "CHILD_ISSUE_TASK": "Select Child Issue", "CREATE_TEAM": "Create New Team", + "ALL_TEAMS": "All Teams", "VERIFY_ACCOUNT_MSG": "Please Verify your account before start using the app", "CREATE_ROLE": "Create Role", "CREATE": "Create", @@ -569,6 +572,13 @@ }, "CONFIRM_CLOSE_TASK": "Please confirm if you want to close the task" }, + "dailyPlan": { + "PLAN_FOR_TODAY": "Plan for today", + "PLAN_FOR_TOMORROW": "Plan for tomorrow", + "PLAN_FOR_SOME_DAY": "Plan for some day", + "ADD_TASK_TO_PLAN": "Add this task to a plan", + "REMOVE_FROM_THIS_PLAN": "Remove from this plan" + }, "form": { "NAME_PLACEHOLDER": "Enter your name", "FIRST_NAME_PLACEHOLDER": "First Name", diff --git a/apps/web/locales/es.json b/apps/web/locales/es.json index a11845ac9..257403ef4 100644 --- a/apps/web/locales/es.json +++ b/apps/web/locales/es.json @@ -13,6 +13,8 @@ "CARDS": "Tarjetas", "TABLE": "Tabla", "KANBAN": "Kanban", + "TEAMS": "Equipos", + "COMBINE": "Combinar", "INVITE": "Invitar", "INVITED": "Invitado", "EXPIRE": "Expira", @@ -94,6 +96,7 @@ "DESCRIPTION": "Descripción", "CHILD_ISSUE_TASK": "Seleccionar problema secundario", "CREATE_TEAM": "Crear nuevo equipo", + "ALL_TEAMS": "Todos los equipos", "VERIFY_ACCOUNT_MSG": "Verifica tu cuenta antes de empezar a usar la aplicación", "CREATE_ROLE": "Crear rol", "CREATE": "Crear", @@ -569,6 +572,13 @@ }, "CONFIRM_CLOSE_TASK": "Por favor, confirma si deseas cerrar la tarea" }, + "dailyPlan": { + "PLAN_FOR_TODAY": "Plan para hoy", + "PLAN_FOR_TOMORROW": "Plan para mañana", + "PLAN_FOR_SOME_DAY": "Plan para algún día", + "ADD_TASK_TO_PLAN": "Agregar esta tarea a un plan", + "REMOVE_FROM_THIS_PLAN": "Eliminar de este plan" + }, "form": { "NAME_PLACEHOLDER": "Ingresa tu nombre", "FIRST_NAME_PLACEHOLDER": "Nombre", diff --git a/apps/web/locales/fr.json b/apps/web/locales/fr.json index 575f3ab38..27c226ab1 100644 --- a/apps/web/locales/fr.json +++ b/apps/web/locales/fr.json @@ -14,6 +14,8 @@ "CARDS": "Cartes", "KANBAN": "Kanban", "TABLE": "Tableau", + "TEAMS": "Équipes", + "COMBINE": "Combiné", "INVITED": "Invité", "EXPIRE": "Expirer", "EXPIRED": "Expiré", @@ -93,6 +95,7 @@ "DESCRIPTION": "Description", "CHILD_ISSUE_TASK": "Sélectionner un problème enfant", "CREATE_TEAM": "Créer une nouvelle équipe", + "ALL_TEAMS": "Toutes les équipes", "VERIFY_ACCOUNT_MSG": "Veuillez vérifier votre compte avant de commencer à utiliser l'application", "CREATE_ROLE": "Créer un rôle", "CREATE": "Créer", @@ -569,6 +572,13 @@ }, "CONFIRM_CLOSE_TASK": "Veuillez confirmer si vous voulez fermer la tâche" }, + "dailyPlan": { + "PLAN_FOR_TODAY": "Plan pour aujourd'hui", + "PLAN_FOR_TOMORROW": "Plan pour demain", + "PLAN_FOR_SOME_DAY": "Plan pour une date", + "ADD_TASK_TO_PLAN": "Ajouter cette tâche au plan", + "REMOVE_FROM_THIS_PLAN": "Retirer de ce plan" + }, "form": { "NAME_PLACEHOLDER": "Entrez votre nom", "FIRST_NAME_PLACEHOLDER": "Prénom", diff --git a/apps/web/locales/he.json b/apps/web/locales/he.json index 482415d4e..17de57399 100644 --- a/apps/web/locales/he.json +++ b/apps/web/locales/he.json @@ -12,6 +12,8 @@ "CARDS": "כרטיסים", "KANBAN": "קאנבאן", "TABLE": "טבלה", + "TEAMS": "קבוצות", + "COMBINE": "לשלב", "VERIFY": "אמת", "INVITE": "הזמן", "INVITED": "הוזמן", @@ -91,6 +93,7 @@ "DESCRIPTION": "תיאור", "CHILD_ISSUE_TASK": "בחר בעיה ילדה", "CREATE_TEAM": "צור צוות חדש", + "ALL_TEAMS": "כל הקבוצות", "VERIFY_ACCOUNT_MSG": "אנא אמת את החשבון שלך לפני שתתחיל להשתמש באפליקציה", "CREATE_ROLE": "צור תפקיד", "CREATE": "צור", @@ -569,6 +572,13 @@ }, "CONFIRM_CLOSE_TASK": "אנא אשר אם ברצונך לסגור את המשימה" }, + "dailyPlan": { + "PLAN_FOR_TODAY": "תוכנית להיום", + "PLAN_FOR_TOMORROW": "תוכנית למחר", + "PLAN_FOR_SOME_DAY": "תוכנית ליום כלשהו", + "ADD_TASK_TO_PLAN": "הוסף משימה זו לתוכנית", + "REMOVE_FROM_THIS_PLAN": "הסר מתוכנית זו" + }, "form": { "NAME_PLACEHOLDER": "הכנס את השם שלך", "FIRST_NAME_PLACEHOLDER": "שם פרטי", diff --git a/apps/web/locales/it.json b/apps/web/locales/it.json index 6a74c2a4e..93262916f 100644 --- a/apps/web/locales/it.json +++ b/apps/web/locales/it.json @@ -13,6 +13,8 @@ "CARDS": "Carte", "KANBAN": "Kanban", "TABLE": "Tabella", + "TEAMS": "Squadre", + "COMBINE": "Combinare", "INVITE": "Invita", "INVITED": "Invitato", "EXPIRE": "Scade", @@ -92,6 +94,7 @@ "DESCRIPTION": "Descrizione", "CHILD_ISSUE_TASK": "Seleziona Problema Figlio", "CREATE_TEAM": "Crea Nuovo Team", + "ALL_TEAMS": "Tutti i team", "VERIFY_ACCOUNT_MSG": "Verifica il tuo account prima di iniziare ad utilizzare l'app", "CREATE_ROLE": "Crea Ruolo", "CREATE": "Crea", @@ -569,6 +572,13 @@ }, "CONFIRM_CLOSE_TASK": "Conferma se desideri chiudere il compito" }, + "dailyPlan": { + "PLAN_FOR_TODAY": "Piano per oggi", + "PLAN_FOR_TOMORROW": "Piano per domani", + "PLAN_FOR_SOME_DAY": "Piano per un giorno", + "ADD_TASK_TO_PLAN": "Aggiungi questa attività al piano", + "REMOVE_FROM_THIS_PLAN": "Rimuovi da questo piano" + }, "form": { "NAME_PLACEHOLDER": "Inserisci il tuo nome", "FIRST_NAME_PLACEHOLDER": "Nome", diff --git a/apps/web/locales/nl.json b/apps/web/locales/nl.json index 379580428..fad47a7c3 100644 --- a/apps/web/locales/nl.json +++ b/apps/web/locales/nl.json @@ -11,6 +11,8 @@ "CARDS": "Kaarten", "TABLE": "Tafel", "KANBAN": "Kanban", + "TEAMS": "Teams", + "COMBINE": "Combineren", "REJECTED": "Afgewezen", "VERIFY": "verifiëren", "INVITE": "Uitnodigen", @@ -93,6 +95,7 @@ "DESCRIPTION": "Beschrijving", "CHILD_ISSUE_TASK": "Selecteer onderliggend probleem", "CREATE_TEAM": "Nieuw team maken", + "ALL_TEAMS": "Alle teams", "VERIFY_ACCOUNT_MSG": "Verifieer uw account voordat u de app gaat gebruiken", "CREATE_ROLE": "Rol maken", "CREATE": "Maken", @@ -569,6 +572,13 @@ }, "CONFIRM_CLOSE_TASK": "Bevestig als u de taak wilt sluiten" }, + "dailyPlan": { + "PLAN_FOR_TODAY": "Plan voor vandaag", + "PLAN_FOR_TOMORROW": "Plan voor morgen", + "PLAN_FOR_SOME_DAY": "Plan voor een dag", + "ADD_TASK_TO_PLAN": "Voeg deze taak toe aan een plan", + "REMOVE_FROM_THIS_PLAN": "Verwijder uit dit plan" + }, "form": { "NAME_PLACEHOLDER": "Voer uw naam in", "FIRST_NAME_PLACEHOLDER": "Voornaam", diff --git a/apps/web/locales/pl.json b/apps/web/locales/pl.json index 2c338afe1..1ec6b4080 100644 --- a/apps/web/locales/pl.json +++ b/apps/web/locales/pl.json @@ -14,6 +14,8 @@ "CARDS": "Karty", "KANBAN": "Kanban", "TABLE": "Tabela", + "TEAMS": "Zespoły", + "COMBINE": "Łączyć", "INVITED": "Zaproszony", "EXPIRE": "Wygasa", "EXPIRED": "Wygasło", @@ -92,6 +94,7 @@ "DESCRIPTION": "Opis", "CHILD_ISSUE_TASK": "Wybierz zadanie podrzędne", "CREATE_TEAM": "Utwórz nowy zespół", + "ALL_TEAMS": "Wszystkie zespoły", "VERIFY_ACCOUNT_MSG": "Zweryfikuj swoje konto przed rozpoczęciem korzystania z aplikacji", "CREATE_ROLE": "Utwórz rolę", "CREATE": "Utwórz", @@ -569,6 +572,13 @@ }, "CONFIRM_CLOSE_TASK": "Potwierdź, czy chcesz zamknąć zadanie" }, + "dailyPlan": { + "PLAN_FOR_TODAY": "Plan na dzisiaj", + "PLAN_FOR_TOMORROW": "Plan na jutro", + "PLAN_FOR_SOME_DAY": "Plan na pewien dzień", + "ADD_TASK_TO_PLAN": "Dodaj tę zadanie do planu", + "REMOVE_FROM_THIS_PLAN": "Usuń z tego planu" + }, "form": { "NAME_PLACEHOLDER": "Wprowadź swoje imię", "FIRST_NAME_PLACEHOLDER": "Imię", diff --git a/apps/web/locales/pt.json b/apps/web/locales/pt.json index fbf63bcfd..fef612fbe 100644 --- a/apps/web/locales/pt.json +++ b/apps/web/locales/pt.json @@ -16,6 +16,8 @@ "CARDS": "Cartões", "KANBAN": "Kanban", "TABLE": "Tabela", + "TEAMS": "Equipes", + "COMBINE": "Combinar", "EXPIRED": "Expirado", "REQUEST": "Solicitar", "REQUESTED": "Solicitado", @@ -92,6 +94,7 @@ "DESCRIPTION": "Descrição", "CHILD_ISSUE_TASK": "Selecionar Problema Filho", "CREATE_TEAM": "Criar Nova Equipe", + "ALL_TEAMS": "Todos os times", "VERIFY_ACCOUNT_MSG": "Por favor, verifique sua conta antes de começar a usar o aplicativo", "CREATE_ROLE": "Criar Função", "CREATE": "Criar", @@ -569,6 +572,13 @@ }, "CONFIRM_CLOSE_TASK": "Por favor, confirme se deseja fechar a tarefa" }, + "dailyPlan": { + "PLAN_FOR_TODAY": "Plano para hoje", + "PLAN_FOR_TOMORROW": "Plano para amanhã", + "PLAN_FOR_SOME_DAY": "Plano para algum dia", + "ADD_TASK_TO_PLAN": "Adicionar esta tarefa ao plano", + "REMOVE_FROM_THIS_PLAN": "Remover deste plano" + }, "form": { "NAME_PLACEHOLDER": "Digite seu nome", "FIRST_NAME_PLACEHOLDER": "Primeiro Nome", diff --git a/apps/web/locales/ru.json b/apps/web/locales/ru.json index 060ea4d32..51c727994 100644 --- a/apps/web/locales/ru.json +++ b/apps/web/locales/ru.json @@ -14,6 +14,8 @@ "CARDS": "Карты", "TABLE": "Таблица", "KANBAN": "Канбан", + "TEAMS": "Команды", + "COMBINE": "Объединить", "INVITED": "Приглашено", "EXPIRE": "Истекает", "EXPIRED": "Истекло", @@ -92,6 +94,7 @@ "DESCRIPTION": "Описание", "CHILD_ISSUE_TASK": "Выбрать дочерний вопрос", "CREATE_TEAM": "Создать новую команду", + "ALL_TEAMS": "Все команды", "VERIFY_ACCOUNT_MSG": "Пожалуйста, подтвердите свою учетную запись перед началом использования приложения", "CREATE_ROLE": "Создать роль", "CREATE": "Создать", @@ -569,6 +572,13 @@ }, "CONFIRM_CLOSE_TASK": "Пожалуйста, подтвердите, что вы хотите закрыть задачу" }, + "dailyPlan": { + "PLAN_FOR_TODAY": "План на сегодня", + "PLAN_FOR_TOMORROW": "План на завтра", + "PLAN_FOR_SOME_DAY": "План на какой-то день", + "ADD_TASK_TO_PLAN": "Добавить эту задачу в план", + "REMOVE_FROM_THIS_PLAN": "Убрать из этого плана" + }, "form": { "NAME_PLACEHOLDER": "Введите ваше имя", "FIRST_NAME_PLACEHOLDER": "Имя", diff --git a/apps/web/locales/zh.json b/apps/web/locales/zh.json index d9d68c603..64d4ea750 100644 --- a/apps/web/locales/zh.json +++ b/apps/web/locales/zh.json @@ -12,6 +12,8 @@ "CARDS": "卡片", "TABLE": "表格", "KANBAN": "看板", + "TEAMS": "团队", + "COMBINE": "组合", "VERIFY": "验证", "INVITE": "邀请", "INVITED": "已邀请", @@ -93,6 +95,7 @@ "DESCRIPTION": "描述", "CHILD_ISSUE_TASK": "选择子议题", "CREATE_TEAM": "创建新团队", + "ALL_TEAMS": "所有团队", "VERIFY_ACCOUNT_MSG": "使用应用前请先验证您的账户", "CREATE_ROLE": "创建角色", "CREATE": "创建", @@ -569,6 +572,13 @@ }, "CONFIRM_CLOSE_TASK": "请确认您是否要关闭此任务" }, + "dailyPlan": { + "PLAN_FOR_TODAY": "今日计划", + "PLAN_FOR_TOMORROW": "明日计划", + "PLAN_FOR_SOME_DAY": "未来计划", + "ADD_TASK_TO_PLAN": "添加此任务到计划", + "REMOVE_FROM_THIS_PLAN": "从计划中移除" + }, "form": { "NAME_PLACEHOLDER": "输入您的姓名", "FIRST_NAME_PLACEHOLDER": "名",