diff --git a/package.json b/package.json index 5935bc2..116ac7f 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,11 @@ "version": "0.1.0", "private": true, "scripts": { - "build": "next build", - "postinstall": "prisma generate", + "build:1-generate": "prisma generate", + "build:2-next": "cross-env NODE_ENV=production next build", + "build:3-server": "tsc --project tsconfig.server.json", + "build:4-migrate": "prisma migrate deploy", + "build": "run-s build:*", "lint": "next lint", "start": "next start", "dev:wss": "cross-env PORT=3001 tsx watch src/server/wssDevServer.ts --tsconfig tsconfig.server.json ", @@ -19,48 +22,48 @@ "@dnd-kit/sortable": "^7.0.2", "@heroicons/react": "^2.0.18", "@next-auth/prisma-adapter": "^1.0.7", - "@prisma/client": "^4.16.2", - "@tanstack/react-query": "^4.29.19", - "@trpc/client": "^10.33.0", - "@trpc/next": "^10.33.0", - "@trpc/react-query": "^10.33.0", - "@trpc/server": "^10.33.0", - "antd": "^5.6.3", + "@prisma/client": "5.1.1", + "@tanstack/react-query": "^4.33.0", + "@trpc/client": "^10.37.1", + "@trpc/next": "^10.37.1", + "@trpc/react-query": "^10.37.1", + "@trpc/server": "^10.37.1", + "antd": "^5.8.0", "aos": "^2.3.4", "dayjs": "^1.11.9", - "next": "^13.4.7", - "next-auth": "^4.22.1", - "node-fetch": "^3.3.1", + "next": "^13.4.19", + "next-auth": "^4.23.1", + "node-fetch": "^3.3.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "recharts": "^2.7.2", - "superjson": "^1.12.4", + "recharts": "^2.7.3", + "superjson": "^1.13.1", "tsx": "^3.12.7", "ws": "^8.13.0", - "zod": "^3.21.4", - "zustand": "^4.3.8" + "zod": "^3.22.2", + "zustand": "^4.4.1" }, "devDependencies": { "@faker-js/faker": "^8.0.2", - "@playwright/test": "^1.35.1", - "@types/node": "^20.3.3", - "@types/react": "^18.2.14", - "@types/react-dom": "^18.2.6", + "@playwright/test": "^1.37.1", + "@types/node": "^20.5.1", + "@types/react": "^18.2.20", + "@types/react-dom": "^18.2.7", "@types/ws": "^8.5.5", - "@typescript-eslint/eslint-plugin": "^5.60.1", - "@typescript-eslint/parser": "^5.60.1", + "@typescript-eslint/eslint-plugin": "^6.4.0", + "@typescript-eslint/parser": "^6.4.0", "classnames": "^2.3.2", "cross-env": "^7.0.3", - "eslint": "^8.44.0", - "eslint-config-next": "^13.4.7", - "eslint-plugin-prettier": "^4.2.1", + "eslint": "^8.47.0", + "eslint-config-next": "^13.4.19", + "eslint-plugin-prettier": "^5.0.0", "npm-run-all": "^4.1.5", - "postcss": "^8.4.24", + "postcss": "^8.4.28", "postcss-loader": "^7.3.3", "postcss-nested": "^6.0.1", "postcss-simple-vars": "^7.0.1", - "prisma": "^4.16.2", - "sass": "^1.63.6", + "prisma": "5.1.1", + "sass": "^1.66.1", "ts-node": "^10.9.1", "typescript": "^5.1.6" }, diff --git a/prisma/migrations/20230716054349_latest/migration.sql b/prisma/migrations/20230716054349_latest/migration.sql new file mode 100644 index 0000000..75a7771 --- /dev/null +++ b/prisma/migrations/20230716054349_latest/migration.sql @@ -0,0 +1,63 @@ +/* + Warnings: + + - You are about to drop the `Example` table. If the table is not empty, all the data it contains will be lost. + +*/ +-- AlterTable +ALTER TABLE "Issue" ADD COLUMN "sprintId" TEXT; + +-- AlterTable +ALTER TABLE "Project" ADD COLUMN "workspaceId" TEXT; + +-- AlterTable +ALTER TABLE "User" ADD COLUMN "workspaceId" TEXT; + +-- DropTable +DROP TABLE "Example"; + +-- CreateTable +CREATE TABLE "Workspace" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "shortName" TEXT NOT NULL, + "description" TEXT, + "website" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "createdById" TEXT NOT NULL, + "color" TEXT NOT NULL, + + CONSTRAINT "Workspace_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Sprint" ( + "id" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "title" TEXT NOT NULL, + "startDate" TIMESTAMP(3), + "endDate" TIMESTAMP(3), + "projectId" TEXT NOT NULL, + "goal" TEXT, + "isCompleted" BOOLEAN NOT NULL DEFAULT false, + "hasStarted" BOOLEAN NOT NULL DEFAULT false, + + CONSTRAINT "Sprint_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "User" ADD CONSTRAINT "User_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Workspace" ADD CONSTRAINT "Workspace_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Project" ADD CONSTRAINT "Project_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Issue" ADD CONSTRAINT "Issue_sprintId_fkey" FOREIGN KEY ("sprintId") REFERENCES "Sprint"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Sprint" ADD CONSTRAINT "Sprint_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "Project"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b523b3e..4ee431f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -3,8 +3,10 @@ generator client { } datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_PRISMA_URL") // uses connection pooling + directUrl = env("DATABASE_URL_NON_POOLING") // uses a direct connection + shadowDatabaseUrl = env("DATABASE_URL_NON_POOLING") } model Account { @@ -39,19 +41,19 @@ model User { email String? @unique emailVerified Boolean? @default(false) image String? + workspaceId String? accounts Account[] checkListItems CheckListItem[] comments Comment[] createdIssues Issue[] @relation("createdBy") + defaultAssigneeProjects Project[] @relation("defaultAssignee") leadProjects Project[] sessions Session[] - issue Issue[] + Workspace Workspace? @relation(fields: [workspaceId], references: [id]) userActions UserActions[] - projects Project[] @relation("projectMembers") - defaultAssigneeProjects Project[] @relation("defaultAssignee") workspace Workspace[] @relation("createdBy") - Workspace Workspace? @relation(fields: [workspaceId], references: [id]) - workspaceId String? + issue Issue[] @relation("IssueToUser") + projects Project[] @relation("projectMembers") } model VerificationToken { @@ -70,11 +72,11 @@ model Workspace { website String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - members User[] - projects Project[] - createdBy User @relation("createdBy", fields: [createdById], references: [id]) createdById String color String + projects Project[] + members User[] + createdBy User @relation("createdBy", fields: [createdById], references: [id]) } model Project { @@ -85,16 +87,16 @@ model Project { viewType ViewType @default(KANBAN_BOARD) status ProjectStatus @default(ACTIVE) projectLeadId String + defaultAssigneeId String? + workspaceId String? + labels Label[] + defaultAssignee User? @relation("defaultAssignee", fields: [defaultAssigneeId], references: [id]) projectLead User @relation(fields: [projectLeadId], references: [id]) + Workspace Workspace? @relation(fields: [workspaceId], references: [id]) + sprints Sprint[] userActions UserActions[] workflows WorkFlow[] members User[] @relation("projectMembers") - defaultAssigneeId String? - defaultAssignee User? @relation("defaultAssignee", fields: [defaultAssigneeId], references: [id]) - labels Label[] - Workspace Workspace? @relation(fields: [workspaceId], references: [id]) - workspaceId String? - sprints Sprint[] } model WorkFlow { @@ -109,15 +111,15 @@ model WorkFlow { } model Label { - id String @id @default(cuid()) - title String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - color String - issueId String? - issue Issue? @relation(fields: [issueId], references: [id]) - Project Project? @relation(fields: [projectId], references: [id]) - projectId String? + id String @id @default(cuid()) + title String + description String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + color String + projectId String? + issues Issue[] + Project Project? @relation(fields: [projectId], references: [id]) } model Issue { @@ -132,16 +134,16 @@ model Issue { flagged Boolean @default(false) dueDate DateTime? issueId String? + sprintId String? checkLists CheckList[] comments Comment[] createdBy User @relation("createdBy", fields: [createdById], references: [id]) issue Issue? @relation("linkedIssues", fields: [issueId], references: [id]) linkedIssues Issue[] @relation("linkedIssues") + sprint Sprint? @relation(fields: [sprintId], references: [id]) workFlow WorkFlow @relation(fields: [workFlowId], references: [id], onDelete: Cascade) labels Label[] - assignees User[] - sprint Sprint? @relation(fields: [sprintId], references: [id]) - sprintId String? + assignees User[] @relation("IssueToUser") } model Comment { @@ -189,6 +191,21 @@ model UserActions { user User @relation(fields: [userId], references: [id], onDelete: Cascade) } +model Sprint { + id String @id @default(cuid()) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + title String + startDate DateTime? + endDate DateTime? + projectId String + goal String? + isCompleted Boolean @default(false) + hasStarted Boolean @default(false) + issues Issue[] + project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) +} + enum ProjectType { KANBAN SCRUM @@ -245,18 +262,3 @@ enum Action { ADDED_A_CHECKLIST_TO_ISSUE REMOVED_A_CHECKLIST_FROM_ISSUE } - -model Sprint { - id String @id @default(cuid()) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - title String - startDate DateTime? - endDate DateTime? - projectId String - goal String? - project Project @relation(fields: [projectId], references: [id], onDelete: Cascade) - issues Issue[] - isCompleted Boolean @default(false) - hasStarted Boolean @default(false) -} \ No newline at end of file diff --git a/src/components/Backlog/BacklogItem.tsx b/src/components/Backlog/BacklogItem.tsx new file mode 100644 index 0000000..a274fa8 --- /dev/null +++ b/src/components/Backlog/BacklogItem.tsx @@ -0,0 +1,160 @@ +import type { MenuProps } from 'antd'; +import { Button, List, Select, Dropdown, message, Modal, theme } from 'antd'; +import { MoreOutlined } from '@ant-design/icons'; +import type { Issue } from '@prisma/client'; +import { api, type RouterOutputs } from '../../utils/api'; +import { useSprintStore } from '../../store/sprint.store'; + +import { QuestionCircleOutlined } from '@ant-design/icons'; + +interface Props { + item: Issue; + sprintId: string; + workflows: RouterOutputs['workflow']['getAllProjectWorkflows']; + isLoadingWorkflow: boolean; +} + +const { useToken } = theme; + +const BacklogItem = (props: Props) => { + const items: MenuProps['items'] = [ + { + key: 'flag', + label: props.item.flagged ? 'Remove Flag' : 'Add Flag', + }, + { + key: 'delete', + label: 'Delete', + danger: true, + }, + ]; + const { removeSprintIssue, deleteBacklogIssue, updateIssue } = useSprintStore(); + + const { mutate: deleteSprintIssue, isLoading: isDeleting } = + api.issue.deleteIssueById.useMutation({ + onSuccess: (issue) => { + message.success('Issue delete successfully'); + if ( + props.workflows.find((workflow) => workflow.id === issue.workFlowId) + ?.title === 'Backlog' + ) { + deleteBacklogIssue(issue); + } + removeSprintIssue(props.sprintId, issue); + }, + onError: () => { + message.error('Error Deleting Issue'); + }, + }); + + const handleChange = (value: string) => { + console.log(`selected ${value}`); + }; + + const { mutate: addFlag } = api.issue.addFlag.useMutation({ + onSuccess: () => { + updateIssue({ + ...props.item, + flagged: true, + }); + }, + onError: () => { + message.error('Error Flagging Issue'); + }, + }); + + const { mutate: removeFlag } = api.issue.removeFlag.useMutation({ + onSuccess: () => { + updateIssue({ + ...props.item, + flagged: false, + }); + }, + onError: () => { + message.error('Error Unflagging Issue'); + }, + }); + + const handleMenuClick = (event: any, id: string) => { + switch (event.key) { + case 'delete': + Modal.error({ + icon: , + title: 'Delete Issue?', + content: + "You're about to permanently delete this issue, its comments and attachments, and all of its data.", + okText: 'Delete', + cancelText: 'Cancel', + maskClosable: true, + okType: 'danger', + onOk() { + deleteSprintIssue({ id }); + }, + onCancel() { }, + closable: true, + }); + break; + case 'flag': + if (props.item.flagged) { + removeFlag({ issueId: id }); + } else { + addFlag({ issueId: id }); + } + break; + default: + break; + } + }; + const { token } = useToken(); + + return ( + workflow.id === props.item.workFlowId + )?.title + } + bordered={false} + style={{ width: 120 }} + onChange={handleChange} + options={ + props.workflows.map((workflow) => ({ + label: workflow.title, + value: workflow.id, + })) || [] + } + key={'select'} + />, + { + handleMenuClick(e, props.item.id); + }, + }} + trigger={['click']} + key="btn" + > + + { + setIsModalOpen(false); + }, + }} + sprint={props.sprint} + /> + + ); + } + + return ( + <> + + { + setIsModalOpen(false); + }} + bodyStyle={{paddingTop: 8, paddingBottom: 8}} + onOk={() => { + form.submit(); + }} + okText="Start" + confirmLoading={isStartingSprint} + > + {props.issueCount} issues will be included in this sprint. +
{ + if (props.sprint.hasStarted) { + message.error('Sprint has already started'); + } + startSprint({ + id: props.sprint.id, + title: values.name, + startDate: values.startDateTime?.toDate()!, + endDate: values.endDateTime?.toDate()!, + goal: values.goal ?? null, + }); + }} + form={form} + > + + + + + + + + { + if (duration !== 'custom') { + form.setFieldsValue({ + endDateTime: value?.add(parseInt(duration), 'week'), + }); + } + }} + /> + + + + + + + +
+
+ + ); +}; +export default SprintModal; + +interface CompletedSprintModalProps { + modalProps: ModalProps; + sprint: Sprint; +} + +const CompleteSprintModal = (props: CompletedSprintModalProps) => { + const {mutate: completeSprint, isLoading: compeletingSprint} = + api.sprint.completeSprint.useMutation(); + + //TODO: CONNECT TO NEW SPRINT VIA API + + // const {mutate: moveIssueToAnotherSprint, isLoading: isMoving} = + // api.sprint.moveMultipleIssueToSprint.useMutation(); + + const [form] = Form.useForm(); + + const {workflows} = useProjectStore(); + const {sprints} = useSprintStore(); + + const getIssueStatusCount = () => { + const issueStatus = { + completedIssueCount: 0, + openIssueCount: 0, + }; + + props.sprint.issues.forEach((issue) => { + if ( + issue.workFlowId === + workflows.find((workflow) => workflow.title === 'Done')?.id + ) { + issueStatus.completedIssueCount += 1; + } else { + issueStatus.openIssueCount += 1; + } + }); + return issueStatus; + }; + + const {completedIssueCount, openIssueCount} = getIssueStatusCount(); + + return ( + { + completeSprint({ + id: props.sprint.id, + }); + }} + confirmLoading={compeletingSprint} + > + + This sprint contains: +
    +
  • {completedIssueCount} completed issues
  • +
  • {openIssueCount} open issues
  • +
+
+ {openIssueCount === 0 ? ( + Thats all of them - well done! + ) : ( +
{ + if (props.sprint.hasStarted) { + message.error('Sprint has already started'); + } + }} + form={form} + > + + + +
+ )} +
+ ); +}; diff --git a/src/components/BorderedContainer.tsx b/src/components/BorderedContainer.tsx new file mode 100644 index 0000000..6ae3de6 --- /dev/null +++ b/src/components/BorderedContainer.tsx @@ -0,0 +1,11 @@ +import {theme} from 'antd'; +import React from 'react'; + +const {useToken} = theme; + +const BorderedContainer = (props:React.DetailedHTMLProps, HTMLDivElement>) => { + const {token} = useToken(); + return
{props.children}
; +}; + +export default BorderedContainer; diff --git a/src/components/Checklist/Checklist.tsx b/src/components/Checklist/Checklist.tsx index dd9ddfe..95cd45f 100644 --- a/src/components/Checklist/Checklist.tsx +++ b/src/components/Checklist/Checklist.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {CheckCircleIcon, TrashIcon} from '@heroicons/react/24/outline'; -import {Button, Progress, Popconfirm, Checkbox, Skeleton} from 'antd'; +import {Button, Progress, Popconfirm, Checkbox, Skeleton, Typography} from 'antd'; import {QuestionCircleOutlined} from '@ant-design/icons'; import CheckListEditor from './CheckListEditor'; @@ -9,6 +9,8 @@ import {api} from '../../utils/api'; import type {CheckList, CheckListItem} from '@prisma/client'; import type {CheckboxChangeEvent} from 'antd/es/checkbox'; +const {Text} =Typography; + const Checklist = (props: CheckList) => { const checkListItemsQuery = api.issue.getChecklistItemsInChecklist.useQuery({ checklistId: props.id, @@ -69,7 +71,7 @@ const Checklist = (props: CheckList) => {
-

{props.title}

+ {props.title}
{/* //TODO : HANDLE HIDE TOGGLE CHECKED ITEM */} diff --git a/src/components/Container/Container.module.scss b/src/components/Container/Container.module.scss index b16aeb0..d82266f 100644 --- a/src/components/Container/Container.module.scss +++ b/src/components/Container/Container.module.scss @@ -10,7 +10,6 @@ border-radius: 5px; min-height: 200px; transition: background-color 350ms ease; - background-color: rgba(246, 246, 246, 1); font-size: 1em; ul { @@ -80,7 +79,7 @@ margin-bottom: 0px; align-items: center; justify-content: space-between; - background-color: #fff; + // background-color: #fff; border-top-left-radius: 5px; border-top-right-radius: 5px; border-radius: 5px; diff --git a/src/components/Container/Container.tsx b/src/components/Container/Container.tsx index c0375ed..bf4eb1c 100644 --- a/src/components/Container/Container.tsx +++ b/src/components/Container/Container.tsx @@ -1,15 +1,14 @@ -import React, {forwardRef, useRef, useState} from 'react'; +import React, { forwardRef } from 'react'; import classNames from 'classnames'; import styles from './Container.module.scss'; import Handle from '../Handle/Handle'; import Remove from '../Remove/Remove'; -import {Button, Input, Modal} from 'antd'; -import type {InputRef} from 'antd/lib/input/Input'; -import {PlusIcon} from '@heroicons/react/24/outline'; -import {api} from '../../utils/api'; -import {useSession} from 'next-auth/react'; +import { Button, theme, Typography } from 'antd'; +import { PlusIcon } from '@heroicons/react/24/outline'; +import { api } from '../../utils/api'; import { useProjectStore } from '../../store/project.store'; +import IssueModal from '../Issue/IssueModal'; export interface ContainerProps { children: React.ReactNode; @@ -28,6 +27,9 @@ export interface ContainerProps { hasAdd?: boolean; } +const { useToken } = theme; +const { Text } = Typography; + const Container = forwardRef( function Container( { @@ -48,10 +50,9 @@ const Container = forwardRef( ...props }: ContainerProps, ref - ){ - const inputRef = useRef(null); + ) { const addIssueToWorkflow = useProjectStore((state) => state.addIssueToWorkflow); - const {mutate: createIssue} = api.issue.createIssue.useMutation({ + const { mutate: createIssue } = api.issue.createIssue.useMutation({ onSuccess: (data) => { addIssueToWorkflow(data.workFlowId, data); }, @@ -59,25 +60,17 @@ const Container = forwardRef( console.log('error'); }, }); - const handleAdd = () => { - setModal1Open(true); - // TODO: When modal is open,set focus on input - // inputRef.current!.focus(); - }; - - const session = useSession(); - const handleEnterClick = (e: React.KeyboardEvent) => { + const handleEnterClick = (value: string) => { createIssue({ - title: e.currentTarget.value, + title: value, workflowId: label!, - createdById: session.data?.user?.id!, index: 0, }); - setModal1Open(false); }; - const [modal1Open, setModal1Open] = useState(false); + const { token } = useToken(); + return (
( ...style, '--columns': columns, height: '100%', + backgroundColor: token.colorBorderSecondary, } as React.CSSProperties } className={classNames( @@ -102,8 +96,8 @@ const Container = forwardRef( tabIndex={onClick ? 0 : undefined} > {label ? ( -
-

{label}

+
+ {label}
{onRemove ? : undefined} @@ -112,38 +106,16 @@ const Container = forwardRef( ) : null} {placeholder ? children :
    {children}
} {hasAdd && ( - <> - {/* TODO: Reduce the padding in the modal body and optimize the modal */} - setModal1Open(false)} - footer={null} - mask={false} - closable={false} - width={'100%'} - wrapClassName="bottom-0 p-0" - style={{verticalAlign: 'bottom', padding: 0}} - centered - > - - + } - onClick={handleAdd} + > Add New - + } /> )}
); diff --git a/src/components/Issue/IssueModal.tsx b/src/components/Issue/IssueModal.tsx new file mode 100644 index 0000000..e60dd04 --- /dev/null +++ b/src/components/Issue/IssueModal.tsx @@ -0,0 +1,81 @@ +import React, {useRef, useState} from 'react'; +import type {InputRef, ModalProps} from 'antd'; +import {Modal, Input, Button} from 'antd'; + +import {PlusOutlined} from '@ant-design/icons'; +import {createPortal} from 'react-dom'; + +interface IssueModalProps extends ModalProps { + onEnterPress: (value: string) => void; + renderer?: React.ReactNode; +} + +const IssueModal = (props: IssueModalProps) => { + const inputRef = useRef(null); + + const [showModal, setShowModal] = useState(false); + const handleClick=()=>{ + setShowModal(true); + setTimeout(() => { + inputRef.current!.focus(); + }, 300); + } + + return ( + <> + { + props.renderer ? ( +
+ {props.renderer} +
+ ) : ( + + )} + {showModal && + createPortal( + { + setShowModal(false); + }} + > + { + props.onEnterPress(e.currentTarget.value); + setShowModal(false); + }} + autoFocus={true} + /> + , + document.body + )} + + ); +}; + +export default IssueModal; diff --git a/src/components/Item/Item.module.scss b/src/components/Item/Item.module.scss index 03721b3..d85aab7 100644 --- a/src/components/Item/Item.module.scss +++ b/src/components/Item/Item.module.scss @@ -1,6 +1,3 @@ -$border-color: #efefef; -$text-color: #333; -$handle-color: rgba(0, 0, 0, 0.25); $box-shadow-border: 0 0 0 calc(1px / var(--scale-x, 1)) rgba(63, 63, 68, 0.05); $box-shadow-common: 0 1px calc(3px / var(--scale-x, 1)) 0 rgba(34, 33, 81, 0.15); $box-shadow: $box-shadow-border, $box-shadow-common; @@ -54,7 +51,6 @@ $focused-outline-color: #4c9ffe; flex-grow: 1; align-items: center; padding: 8px 12px; - background-color: #fff; box-shadow: $box-shadow; outline: none; border-radius: calc(4px / var(--scale-x, 1)); @@ -64,8 +60,6 @@ $focused-outline-color: #4c9ffe; -webkit-tap-highlight-color: transparent; - color: $text-color; - transform: scale(var(--scale, 1)); transition: box-shadow 200ms cubic-bezier(0.18, 0.67, 0.6, 1.22); diff --git a/src/components/Item/Item.tsx b/src/components/Item/Item.tsx index aa1fd67..0da02bc 100644 --- a/src/components/Item/Item.tsx +++ b/src/components/Item/Item.tsx @@ -1,9 +1,8 @@ -import React, { Suspense, useEffect, useState } from 'react'; +import React, {Suspense, useEffect, useState} from 'react'; import classNames from 'classnames'; -import { Button } from 'antd'; +import {Button, theme} from 'antd'; import CustomDivider from '../CustomDivider/CustomDivider'; -import LabelIndicator from '../LabelIndicator/LabelIndicator'; import { EllipsisHorizontalIcon, @@ -12,17 +11,22 @@ import { PaperClipIcon, } from '@heroicons/react/24/outline'; -import type { Issue } from '@prisma/client'; -import type { DraggableSyntheticListeners } from '@dnd-kit/core'; -import type { Transform } from '@dnd-kit/utilities'; +import type {Issue} from '@prisma/client'; +import type {DraggableSyntheticListeners} from '@dnd-kit/core'; +import type {Transform} from '@dnd-kit/utilities'; import styles from './Item.module.scss'; import dynamic from 'next/dynamic'; +import Handle from '../Handle/Handle'; +import Remove from '../Remove/Remove'; +import LabelIndicator from '../Label/LabelIndicator/LabelIndicator'; const ItemDetailsModal = dynamic(() => import('./ItemDetailsModal'), { ssr: true, }); +const {useToken} = theme; + export interface Props { dragOverlay?: boolean; disabled?: boolean; @@ -90,6 +94,8 @@ const Item = React.memo( }; }, [dragOverlay]); + const {token} = useToken(); + return (
  • -
    -
    - - - + <> +
    +
    + + + +
    + + {onRemove ? ( + + ) : null} + {handle ? : null} + +
    - -
    -

    {item.title}

    - -
    - - - -
    +

    {item.title}

    + +
    + + + +
    +
    Loading...
  • }> - = ( } = theme.useToken(); const {mutate: createCheckList, isLoading: isCreatingChecklist} = - api.issue.createChecklist.useMutation(); + api.issue.createChecklist.useMutation({ + onSuccess: () => { + checkListQuery.refetch(); + }, + }); const checkListQuery = api.issue.getChecklistsInIssue.useQuery({ issueId: props.item.id, @@ -109,10 +118,19 @@ const ItemDetailsModal: React.FC = ( } }; - //TODO: HANDLE DUE DATE CHECKBOX INTEGRATION + //TODO: HANDLE DUE DATE CHECKBOX INTEGRATION const [isDueDateChecked, setIsDueDateChecked] = useState(false); - + //TODO:&& HANDLE OVERDUE STATE ON UI + + //handling adding and removing flag + + const {mutate: addFlag, isLoading: addingFlag} = + api.issue.addFlag.useMutation(); + + const {mutate: removeFlag, isLoading: removingFlag} = + api.issue.addFlag.useMutation(); + return ( <> = ( width={800} open={props.open} onClose={props.onCancel} + bodyStyle={{paddingTop: 8}} > = ( style={{backgroundColor: colorBgElevated}} > + {props.item.flagged ? ( + } color="warning"> + Flagged + + ) : ( + <> + )}
    Labels @@ -162,7 +188,7 @@ const ItemDetailsModal: React.FC = ( {isDueDateChecked ? ( } - color={ "success"} + color={'success'} className="ml-2" > Complete @@ -176,26 +202,6 @@ const ItemDetailsModal: React.FC = ( ) : ( <> )} -
    - - -
    Description