From 43a8c8ac40c4d236f946fbe6edb6df134fa216c8 Mon Sep 17 00:00:00 2001 From: Aleksandar Toplek Date: Sat, 25 Nov 2023 21:18:31 +0100 Subject: [PATCH 1/5] fix(doprocess): Run state not updated properly from completed to running --- .../processes/processes/RunsListEmptyPlaceholder.tsx | 2 +- web/apps/doprocess/src/lib/repo/processesRepository.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/web/apps/doprocess/components/processes/processes/RunsListEmptyPlaceholder.tsx b/web/apps/doprocess/components/processes/processes/RunsListEmptyPlaceholder.tsx index e715be0a4b..fe9ea54531 100644 --- a/web/apps/doprocess/components/processes/processes/RunsListEmptyPlaceholder.tsx +++ b/web/apps/doprocess/components/processes/processes/RunsListEmptyPlaceholder.tsx @@ -11,7 +11,7 @@ export function RunsListEmptyPlaceholder() { No runs - You do not have any process runs yet. You can start by creating a process. + You do not have any process runs in progress. You can start by creating a process. Processes diff --git a/web/apps/doprocess/src/lib/repo/processesRepository.ts b/web/apps/doprocess/src/lib/repo/processesRepository.ts index e797bad616..53bd51c9ef 100644 --- a/web/apps/doprocess/src/lib/repo/processesRepository.ts +++ b/web/apps/doprocess/src/lib/repo/processesRepository.ts @@ -274,12 +274,12 @@ export async function setTaskState(userId: string, processId: number, runId: num getTasks(userId, processId, runId), // TODO: (optimization) Can use only count of completed tasks getProcessRun(userId, processId, runId) ]); - const tasksCompleted = tasks.filter(t => t.state === 'completed'); - const newRunState = taskDefinitions.length === tasksCompleted.length ? 'completed' : 'running'; + const tasksCompletedCount = tasks.filter(t => t.state === 'completed').length; + const newRunState = taskDefinitions.length === tasksCompletedCount ? 'completed' : 'running'; if (run?.state !== newRunState) { await db .update(processRun) - .set({ state: 'completed', updatedBy: userId, updatedAt: new Date() }) + .set({ state: newRunState, updatedBy: userId, updatedAt: new Date() }) .where(and(eq(processRun.processId, processId), eq(processRun.id, runId))); } } From 4bc42f5941eef7f64664d42f0f172ce181330dd4 Mon Sep 17 00:00:00 2001 From: Aleksandar Toplek Date: Sat, 25 Nov 2023 21:22:33 +0100 Subject: [PATCH 2/5] fix(doprocess): Proper message for empty runs list with completed filter --- .../doprocess/components/processes/processes/RunsList.tsx | 2 +- .../processes/processes/RunsListEmptyPlaceholder.tsx | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/web/apps/doprocess/components/processes/processes/RunsList.tsx b/web/apps/doprocess/components/processes/processes/RunsList.tsx index 34b640184a..0abae1dbf8 100644 --- a/web/apps/doprocess/components/processes/processes/RunsList.tsx +++ b/web/apps/doprocess/components/processes/processes/RunsList.tsx @@ -24,7 +24,7 @@ export function RunsList({ processId }: { processId?: string }) { editable={Boolean(processId) && showComplated !== 'true'} itemCreateLabel="New process run" createForm={processId ? : undefined} - emptyPlaceholder={} + emptyPlaceholder={} /> diff --git a/web/apps/doprocess/components/processes/processes/RunsListEmptyPlaceholder.tsx b/web/apps/doprocess/components/processes/processes/RunsListEmptyPlaceholder.tsx index fe9ea54531..92589a1097 100644 --- a/web/apps/doprocess/components/processes/processes/RunsListEmptyPlaceholder.tsx +++ b/web/apps/doprocess/components/processes/processes/RunsListEmptyPlaceholder.tsx @@ -5,13 +5,17 @@ import { NavigatingButton } from '@signalco/ui/NavigatingButton'; import { KnownPages } from '../../../src/knownPages'; import { ViewEmptyPlaceholder } from './ViewEmptyPlaceholder'; -export function RunsListEmptyPlaceholder() { +export function RunsListEmptyPlaceholder({ showCompleted }: { showCompleted?: boolean }) { return ( No runs - You do not have any process runs in progress. You can start by creating a process. + {showCompleted ? ( + You do not have any completed process runs. + ) : ( + You do not have any process runs in progress. You can start by creating a process. + )} Processes From 698ff819856dd75df9c24149f4d168ebac005976 Mon Sep 17 00:00:00 2001 From: Aleksandar Toplek Date: Sat, 25 Nov 2023 22:01:23 +0100 Subject: [PATCH 3/5] feat(doprocess): Duplicate process and document options Closes #3939, Closes #3874 --- .../doprocess/app/(rest)/(marketing)/page.tsx | 4 +- web/apps/doprocess/app/api/documents/route.ts | 14 ++-- .../processes/[id]/task-definitions/route.ts | 2 +- web/apps/doprocess/app/api/processes/route.ts | 5 +- .../doprocess/components/layouts/Sidebar.tsx | 4 +- .../documents/DocumentDetailsToolbar.tsx | 13 +++- .../documents/DocumentDuplicateModal.tsx | 65 +++++++++++++++++++ .../processes/ProcessDetailsHeader.tsx | 20 ++++-- .../processes/ProcessDuplicateModal.tsx | 65 +++++++++++++++++++ .../processes/processes/ProcessesListItem.tsx | 4 +- .../processes/processes/RunsListItem.tsx | 4 +- .../processes/tasks/TaskListItem.tsx | 2 +- .../doprocess/src/hooks/useDocumentCreate.ts | 3 +- .../doprocess/src/hooks/useProcessCreate.ts | 3 +- .../src/lib/repo/documentsRepository.ts | 19 +++++- .../src/lib/repo/processesRepository.ts | 29 +++++++-- web/packages/ui-icons/src/lucide/index.ts | 3 +- 17 files changed, 227 insertions(+), 32 deletions(-) create mode 100644 web/apps/doprocess/components/processes/documents/DocumentDuplicateModal.tsx create mode 100644 web/apps/doprocess/components/processes/processes/ProcessDuplicateModal.tsx diff --git a/web/apps/doprocess/app/(rest)/(marketing)/page.tsx b/web/apps/doprocess/app/(rest)/(marketing)/page.tsx index 31bbcb33ef..4326e84c54 100644 --- a/web/apps/doprocess/app/(rest)/(marketing)/page.tsx +++ b/web/apps/doprocess/app/(rest)/(marketing)/page.tsx @@ -1,4 +1,4 @@ -import { ListChecks, Play, Share } from '@signalco/ui-icons'; +import { ListTodo, Play, Share } from '@signalco/ui-icons'; import { NavigatingButton } from '@signalco/ui/NavigatingButton'; import { KnownPages } from '../../../src/knownPages'; import { ImagePlaceholder } from '../../../components/images/ImagePlaceholder'; @@ -15,7 +15,7 @@ function FeaturesSection() {
- +

Document

Create and manage your documents in one place. diff --git a/web/apps/doprocess/app/api/documents/route.ts b/web/apps/doprocess/app/api/documents/route.ts index b4864c9fed..47009a2378 100644 --- a/web/apps/doprocess/app/api/documents/route.ts +++ b/web/apps/doprocess/app/api/documents/route.ts @@ -1,4 +1,4 @@ -import { documentCreate, documentGet, documentsGet } from '../../../src/lib/repo/documentsRepository'; +import { documentCreate, documentGet, documentsGet, documentSetData } from '../../../src/lib/repo/documentsRepository'; import { ensureUserId } from '../../../src/lib/auth/apiAuth'; export async function GET() { @@ -10,11 +10,17 @@ export async function GET() { export async function POST(request: Request) { const data = await request.json(); - const name = data != null && typeof data === 'object' && 'name' in data && typeof data.name === 'string' ? data.name : ''; + const name = data != null && typeof data === 'object' && 'name' in data && typeof data.name === 'string' ? data.name : null; + if (name == null) { + throw new Error('Missing name'); + } const { userId } = ensureUserId(); - const id = await documentCreate(userId, name); - const document = await documentGet(userId, Number(id)); + const basedOn = data != null && typeof data === 'object' && 'basedOn' in data && typeof data.basedOn === 'string' ? data.basedOn : undefined; + + const id = await documentCreate(userId, name, basedOn); + + const document = await documentGet(userId, id); return Response.json({ id: document?.publicId }); } diff --git a/web/apps/doprocess/app/api/processes/[id]/task-definitions/route.ts b/web/apps/doprocess/app/api/processes/[id]/task-definitions/route.ts index 90462cb6db..0c853234e5 100644 --- a/web/apps/doprocess/app/api/processes/[id]/task-definitions/route.ts +++ b/web/apps/doprocess/app/api/processes/[id]/task-definitions/route.ts @@ -34,6 +34,6 @@ export async function POST(request: Request, { params }: { params: { id: string return new Response(null, { status: 404 }); const id = await createTaskDefinition(userId, processId, text); - const taskDefinition = await getTaskDefinition(userId, processId, Number(id)); + const taskDefinition = await getTaskDefinition(userId, processId, id); return Response.json({ id: taskDefinition?.publicId }); } diff --git a/web/apps/doprocess/app/api/processes/route.ts b/web/apps/doprocess/app/api/processes/route.ts index 62617b40d2..c4775bdacd 100644 --- a/web/apps/doprocess/app/api/processes/route.ts +++ b/web/apps/doprocess/app/api/processes/route.ts @@ -14,7 +14,10 @@ export async function POST(request: Request) { const name = typeof req === 'object' && req != null && 'name' in req && typeof req.name === 'string' ? req.name.toString() : null; if (name == null) throw new Error('Missing name'); - const id = await createProcess(userId, name); + + const basedOn = typeof req === 'object' && req != null && 'basedOn' in req && typeof req.basedOn === 'string' ? req.basedOn.toString() : undefined; + const id = await createProcess(userId, name, basedOn); + const process = await getProcess(userId, Number(id)); return Response.json({ id: process?.publicId }); } diff --git a/web/apps/doprocess/components/layouts/Sidebar.tsx b/web/apps/doprocess/components/layouts/Sidebar.tsx index eb0e30f31d..3e44f0a9f9 100644 --- a/web/apps/doprocess/components/layouts/Sidebar.tsx +++ b/web/apps/doprocess/components/layouts/Sidebar.tsx @@ -6,7 +6,7 @@ import { ListItem } from '@signalco/ui-primitives/ListItem'; import { List } from '@signalco/ui-primitives/List'; import { IconButton } from '@signalco/ui-primitives/IconButton'; import { cx } from '@signalco/ui-primitives/cx'; -import { FileText, ListChecks, Play, Right } from '@signalco/ui-icons'; +import { FileText, ListTodo, Play, Right } from '@signalco/ui-icons'; import { KnownPages } from '../../src/knownPages'; export function Sidebar({ open, onOpenChange }: { open: boolean, onOpenChange?: (open: boolean) => void }) { @@ -14,7 +14,7 @@ export function Sidebar({ open, onOpenChange }: { open: boolean, onOpenChange?: const links = useMemo(() => [ { href: KnownPages.Runs, label: 'Runs', Icon: Play }, - { href: KnownPages.Processes, label: 'Processes', Icon: ListChecks }, + { href: KnownPages.Processes, label: 'Processes', Icon: ListTodo }, { href: KnownPages.Documents, label: 'Documents', Icon: FileText }, ], []); diff --git a/web/apps/doprocess/components/processes/documents/DocumentDetailsToolbar.tsx b/web/apps/doprocess/components/processes/documents/DocumentDetailsToolbar.tsx index 6e038d8df4..94a858c645 100644 --- a/web/apps/doprocess/components/processes/documents/DocumentDetailsToolbar.tsx +++ b/web/apps/doprocess/components/processes/documents/DocumentDetailsToolbar.tsx @@ -4,7 +4,7 @@ import { useState } from 'react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@signalco/ui-primitives/Menu'; import { IconButton } from '@signalco/ui-primitives/IconButton'; import { cx } from '@signalco/ui-primitives/cx'; -import { Delete, Embed, Globe, MoreHorizontal } from '@signalco/ui-icons'; +import { Delete, Duplicate, Embed, Globe, MoreHorizontal } from '@signalco/ui-icons'; import { Toolbar } from '../../shared/Toolbar'; import { ShareModal } from '../../shared/ShareModal'; import { SavingIndicator } from '../../shared/SavingIndicator'; @@ -13,6 +13,7 @@ import { ShareableEntity } from '../../../src/types/ShareableEntity'; import { KnownPages } from '../../../src/knownPages'; import { useDocumentUpdate } from '../../../src/hooks/useDocumentUpdate'; import { useDocument } from '../../../src/hooks/useDocument'; +import { DocumentDuplicateModal } from './DocumentDuplicateModal'; import { DocumentDeleteModal } from './DocumentDeleteModal'; type DocumentDetailsToolbarProps = { @@ -25,6 +26,7 @@ export function DocumentDetailsToolbar({ id, saving }: DocumentDetailsToolbarPro const [deleteModalOpen, setDeleteModalOpen] = useState(false); const [embedOpen, setEmbedOpen] = useState(false); const [sharePublicOpen, setSharePublicOpen] = useState(false); + const [duplicateOpen, setDuplicateOpen] = useState(false); const isPublic = document && document.sharedWithUsers.includes('public'); const documentUpdate = useDocumentUpdate(); @@ -60,6 +62,9 @@ export function DocumentDetailsToolbar({ id, saving }: DocumentDetailsToolbarPro + } onClick={() => setDuplicateOpen(true)}> + Duplicate... + {!isPublic && ( } onClick={() => setSharePublicOpen(true)}> Make public... @@ -88,6 +93,12 @@ export function DocumentDetailsToolbar({ id, saving }: DocumentDetailsToolbarPro src={`https://doprocess.app${KnownPages.Document(id)}/embedded`} open={embedOpen} onOpenChange={setEmbedOpen} /> + {document && ( + + )} ); } diff --git a/web/apps/doprocess/components/processes/documents/DocumentDuplicateModal.tsx b/web/apps/doprocess/components/processes/documents/DocumentDuplicateModal.tsx new file mode 100644 index 0000000000..cdf48ac1e4 --- /dev/null +++ b/web/apps/doprocess/components/processes/documents/DocumentDuplicateModal.tsx @@ -0,0 +1,65 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { Typography } from '@signalco/ui-primitives/Typography'; +import { Stack } from '@signalco/ui-primitives/Stack'; +import { Row } from '@signalco/ui-primitives/Row'; +import { Modal } from '@signalco/ui-primitives/Modal'; +import { Input } from '@signalco/ui-primitives/Input'; +import { Button } from '@signalco/ui-primitives/Button'; +import { Duplicate } from '@signalco/ui-icons'; +import { KnownPages } from '../../../src/knownPages'; +import { useDocumentCreate } from '../../../src/hooks/useDocumentCreate'; +import { DocumentDto } from '../../../app/api/dtos/dtos'; + +type DocumentDuplicateModalProps = { + open: boolean; + onOpenChange: (open: boolean) => void; + document: DocumentDto; +}; + +export function DocumentDuplicateModal({ + open, onOpenChange, document +}: DocumentDuplicateModalProps) { + const router = useRouter(); + const [name, setName] = useState(document.name + ' (copy)'); + const documentCreate = useDocumentCreate(); + + const handleDuplicate = async () => { + const response = await documentCreate.mutateAsync({ + name, + basedOn: document.id + }); + if (response?.id) { + onOpenChange(false); + router.push(KnownPages.Document(response.id)); + } + }; + + return ( + + + + + Duplicate document + + + This will create a new document with the same name and content. + + + You can edit the new document after it is created. + + setName(e.target.value)} + label="Name" + placeholder="example: Onboarding manual" /> + + + + + + + ); +} diff --git a/web/apps/doprocess/components/processes/processes/ProcessDetailsHeader.tsx b/web/apps/doprocess/components/processes/processes/ProcessDetailsHeader.tsx index ebecdc66e1..925295764b 100644 --- a/web/apps/doprocess/components/processes/processes/ProcessDetailsHeader.tsx +++ b/web/apps/doprocess/components/processes/processes/ProcessDetailsHeader.tsx @@ -8,10 +8,9 @@ import { Row } from '@signalco/ui-primitives/Row'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@signalco/ui-primitives/Menu'; import { IconButton } from '@signalco/ui-primitives/IconButton'; import { cx } from '@signalco/ui-primitives/cx'; -import { Delete, Embed, Globe, ListChecks, MoreHorizontal, Play } from '@signalco/ui-icons'; +import { Delete, Duplicate, Embed, Globe, ListTodo, MoreHorizontal, Play } from '@signalco/ui-icons'; import { Loadable } from '@signalco/ui/Loadable'; import { ShareModal } from '../../shared/ShareModal'; -import { SharedWithIndicator } from '../../shared/SharedWithIndicator'; import { ListHeader } from '../../shared/ListHeader'; import { EmbedModal } from '../../shared/EmbedModal'; import { ShareableEntity } from '../../../src/types/ShareableEntity'; @@ -23,6 +22,7 @@ import { TypographyProcessName } from './TypographyProcessName'; import { RunProgress } from './RunProgress'; import { ProcessRunCreateModal } from './ProcessRunCreateModal'; import { ProcessOrRunDeleteModal } from './ProcessOrRunDeleteModal'; +import { ProcessDuplicateModal } from './ProcessDuplicateModal'; export function ProcessDetailsHeader({ processId, runId, editable @@ -35,6 +35,7 @@ export function ProcessDetailsHeader({ const [deleteOpen, setDeleteOpen] = useState(false); const [embedOpen, setEmbedOpen] = useState(false); const [shareOpen, setShareOpen] = useState(false); + const [duplicateOpen, setDuplicateOpen] = useState(false); const isRun = Boolean(runId); @@ -55,7 +56,7 @@ export function ProcessDetailsHeader({ error={errorProcess}> : } + icon={isRun ? : } header={isRun ? () : ()} @@ -73,6 +74,9 @@ export function ProcessDetailsHeader({ + } onClick={() => setDuplicateOpen(true)}> + Duplicate... + {!isPublic && ( } onClick={() => setShareOpen(true)}> Make public... @@ -83,12 +87,12 @@ export function ProcessDetailsHeader({ {!isRun && ( } href={KnownPages.ProcessRuns(processId)}> - View process runs + View process runs )} } onClick={() => setDeleteOpen(true)}> - Delete... + Delete... @@ -129,6 +133,12 @@ export function ProcessDetailsHeader({ src={`https://doprocess.app${runId ? KnownPages.ProcessRun(processId, runId) : KnownPages.Process(processId)}/embedded`} open={embedOpen} onOpenChange={setEmbedOpen} /> + {process && ( + + )} ); } diff --git a/web/apps/doprocess/components/processes/processes/ProcessDuplicateModal.tsx b/web/apps/doprocess/components/processes/processes/ProcessDuplicateModal.tsx new file mode 100644 index 0000000000..14edb29324 --- /dev/null +++ b/web/apps/doprocess/components/processes/processes/ProcessDuplicateModal.tsx @@ -0,0 +1,65 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { Typography } from '@signalco/ui-primitives/Typography'; +import { Stack } from '@signalco/ui-primitives/Stack'; +import { Row } from '@signalco/ui-primitives/Row'; +import { Modal } from '@signalco/ui-primitives/Modal'; +import { Input } from '@signalco/ui-primitives/Input'; +import { Button } from '@signalco/ui-primitives/Button'; +import { Duplicate } from '@signalco/ui-icons'; +import { KnownPages } from '../../../src/knownPages'; +import { useProcessCreate } from '../../../src/hooks/useProcessCreate'; +import { ProcessDto } from '../../../app/api/dtos/dtos'; + +type ProcessDuplicateModalProps = { + open: boolean; + onOpenChange: (open: boolean) => void; + process: ProcessDto; +}; + +export function ProcessDuplicateModal({ + open, onOpenChange, process +}: ProcessDuplicateModalProps) { + const router = useRouter(); + const [name, setName] = useState(process.name + ' (copy)'); + const processCreate = useProcessCreate(); + + const handleDuplicate = async () => { + const response = await processCreate.mutateAsync({ + name, + basedOn: process.id + }); + if (response?.id) { + onOpenChange(false); + router.push(KnownPages.Process(response.id)); + } + }; + + return ( + + + + + Duplicate process + + + This will create a new process with the same name and tasks. + + + You can edit the new process after it is created. + + setName(e.target.value)} + label="Name" + placeholder="example: Onboarding process" /> + + + + + + + ); +} diff --git a/web/apps/doprocess/components/processes/processes/ProcessesListItem.tsx b/web/apps/doprocess/components/processes/processes/ProcessesListItem.tsx index 96c75f3a19..9f85ca134b 100644 --- a/web/apps/doprocess/components/processes/processes/ProcessesListItem.tsx +++ b/web/apps/doprocess/components/processes/processes/ProcessesListItem.tsx @@ -1,5 +1,5 @@ import { Row } from '@signalco/ui-primitives/Row'; -import { ListChecks, Navigate } from '@signalco/ui-icons'; +import { ListTodo, Navigate } from '@signalco/ui-icons'; import { SharedWithIndicator } from '../../shared/SharedWithIndicator'; import { ListItem } from '../../shared/ListItem'; import { KnownPages } from '../../../src/knownPages'; @@ -13,7 +13,7 @@ export function ProcessesListItem({ process }: ProcessListItemProps) { return ( } + startDecorator={} endDecorator={( diff --git a/web/apps/doprocess/components/processes/processes/RunsListItem.tsx b/web/apps/doprocess/components/processes/processes/RunsListItem.tsx index 96404874b1..8a9e6fd69b 100644 --- a/web/apps/doprocess/components/processes/processes/RunsListItem.tsx +++ b/web/apps/doprocess/components/processes/processes/RunsListItem.tsx @@ -1,7 +1,7 @@ import { Typography } from '@signalco/ui-primitives/Typography'; import { Stack } from '@signalco/ui-primitives/Stack'; import { Row } from '@signalco/ui-primitives/Row'; -import { ListChecks, Navigate, Play } from '@signalco/ui-icons'; +import { ListTodo, Navigate, Play } from '@signalco/ui-icons'; import { SharedWithIndicator } from '../../shared/SharedWithIndicator'; import { ListItem } from '../../shared/ListItem'; import { KnownPages } from '../../../src/knownPages'; @@ -24,7 +24,7 @@ export function RunsListItem({ run }: RunsListItemProps) { {run.name}

- + {process.data && ( diff --git a/web/apps/doprocess/components/processes/tasks/TaskListItem.tsx b/web/apps/doprocess/components/processes/tasks/TaskListItem.tsx index d4486fdcd1..cd1185d961 100644 --- a/web/apps/doprocess/components/processes/tasks/TaskListItem.tsx +++ b/web/apps/doprocess/components/processes/tasks/TaskListItem.tsx @@ -73,7 +73,7 @@ export function TaskListItem({ selected, taskDefinition, runId, task, taskIndex, style={style} {...attributes} className={cx( - 'relative peer group w-full gap-2 px-3 pr-12', + 'relative peer group w-full gap-2 px-3 pr-12 text-base', isComplated && 'text-muted-foreground line-through', )} selected={selected} diff --git a/web/apps/doprocess/src/hooks/useDocumentCreate.ts b/web/apps/doprocess/src/hooks/useDocumentCreate.ts index 985bfc1676..e219a999fe 100644 --- a/web/apps/doprocess/src/hooks/useDocumentCreate.ts +++ b/web/apps/doprocess/src/hooks/useDocumentCreate.ts @@ -3,6 +3,7 @@ import { documentsKey } from './useDocuments'; type DocumentCreateArgs = { name: string; + basedOn?: string; } async function fetchPostDocument(data: object) { @@ -21,7 +22,7 @@ export function useDocumentCreate(): UseMutationResult<{ } | undefined, Error, DocumentCreateArgs, unknown> { const client = useQueryClient(); return useMutation({ - mutationFn: ({ name }: DocumentCreateArgs) => fetchPostDocument(({ name })), + mutationFn: ({ ...rest }: DocumentCreateArgs) => fetchPostDocument(({ ...rest })), onSuccess: () => { client.invalidateQueries({ queryKey: documentsKey() }); } diff --git a/web/apps/doprocess/src/hooks/useProcessCreate.ts b/web/apps/doprocess/src/hooks/useProcessCreate.ts index 3321c9ac60..62fe113436 100644 --- a/web/apps/doprocess/src/hooks/useProcessCreate.ts +++ b/web/apps/doprocess/src/hooks/useProcessCreate.ts @@ -3,6 +3,7 @@ import { processesKey } from './useProcesses'; type ProcessCreateArgs = { name: string; + basedOn?: string; } async function fetchPostProcess(data: object) { @@ -18,7 +19,7 @@ export function useProcessCreate(): UseMutationResult<{ } | undefined, Error, ProcessCreateArgs, unknown> { const client = useQueryClient(); return useMutation({ - mutationFn: ({ name }: ProcessCreateArgs) => fetchPostProcess(({ name })), + mutationFn: ({ ...rest }: ProcessCreateArgs) => fetchPostProcess(({ ...rest })), onSuccess: () => { client.invalidateQueries({ queryKey: processesKey() }); } diff --git a/web/apps/doprocess/src/lib/repo/documentsRepository.ts b/web/apps/doprocess/src/lib/repo/documentsRepository.ts index c56b66dd75..fe29d3959f 100644 --- a/web/apps/doprocess/src/lib/repo/documentsRepository.ts +++ b/web/apps/doprocess/src/lib/repo/documentsRepository.ts @@ -21,13 +21,26 @@ async function isDocumentSharedWithUser(userId: string, documentId: number) { return (firstOrDefault(await db.select({ count: sql`count(*)` }).from(document).where(and(eq(document.id, documentId), documentSharedWithUser(userId))))?.count ?? 0) > 0; } -export async function documentCreate(userId: string, name: string) { - return (await db.insert(document).values({ +export async function documentCreate(userId: string, name: string, basedOn?: string) { + const id = Number((await db.insert(document).values({ name, publicId: await publicIdNext(document), sharedWithUsers: [userId], createdBy: userId - })).insertId; + })).insertId); + + // Copy content to new document (if basedOn is provided) + if (basedOn) { + const basedOnId = await getDocumentIdByPublicId(basedOn); + if (basedOnId) { + const basedOnDocument = await documentGet(userId, basedOnId); + if (basedOnDocument && typeof basedOnDocument.data === 'string') { + await documentSetData(userId, id, basedOnDocument.data); + } + } + } + + return id; } export async function documentsGet(userId: string) { diff --git a/web/apps/doprocess/src/lib/repo/processesRepository.ts b/web/apps/doprocess/src/lib/repo/processesRepository.ts index 53bd51c9ef..cdbda8538d 100644 --- a/web/apps/doprocess/src/lib/repo/processesRepository.ts +++ b/web/apps/doprocess/src/lib/repo/processesRepository.ts @@ -58,13 +58,32 @@ export async function getProcess(userId: string | null, processId: number) { return firstOrDefault(await db.select().from(process).where(and(processSharedWithUser(userId), eq(process.id, processId)))); } -export async function createProcess(userId: string, name: string) { - return (await db.insert(process).values({ +export async function createProcess(userId: string, name: string, basedOn?: string) { + const id = Number((await db.insert(process).values({ name: name, publicId: await publicIdNext(process), sharedWithUsers: [userId], createdBy: userId - })).insertId; + })).insertId); + + // TODO: Copy task definitions to new process (if basedOn is provided) + if (basedOn) { + const basedOnId = await getProcessIdByPublicId(basedOn); + if (basedOnId) { + const basedOnProcess = await getProcess(userId, basedOnId); + if (basedOnProcess) { + const taskDefinitions = await getTaskDefinitions(userId, basedOnId); + if (taskDefinitions.length) { + await Promise.all(taskDefinitions.map(async td => { + const taskDefinitionId = await createTaskDefinition(userId, id, td.text ?? ''); + await changeTaskDefinitionType(userId, id, taskDefinitionId, td.type, td.typeData ?? ''); + })); + } + } + } + } + + return id; } export async function renameProcess(userId: string, processId: number, name: string) { @@ -183,13 +202,13 @@ export async function createTaskDefinition(userId: string, processId: number, te throw new Error('Not found'); // TODO: Check permissions - return (await db.insert(taskDefinition).values({ + return Number((await db.insert(taskDefinition).values({ processId: processId, publicId: await publicIdNext(taskDefinition, 6), order: await getTaskDefinitionLastOrder(userId, processId), text: text, createdBy: userId - })).insertId; + })).insertId); } export async function changeTaskDefinitionText(userId: string, processId: number, id: number, text: string) { diff --git a/web/packages/ui-icons/src/lucide/index.ts b/web/packages/ui-icons/src/lucide/index.ts index d28d6ec8b9..805bfcd37b 100644 --- a/web/packages/ui-icons/src/lucide/index.ts +++ b/web/packages/ui-icons/src/lucide/index.ts @@ -25,6 +25,7 @@ export { ChevronLeft as Left, ChevronsRight as Right, Copy, + Copy as Duplicate, Check, CircleSlashed as Disabled, CircleEqual, @@ -33,7 +34,7 @@ export { Square as Stop, LayoutList, LayoutGrid, - ListChecks, + ListTodo, Pin, PinOff, LogOut, From c8931d8c0f6d71ed76b83a38fe3182a6a4fc8f8e Mon Sep 17 00:00:00 2001 From: Aleksandar Toplek Date: Sat, 25 Nov 2023 22:10:30 +0100 Subject: [PATCH 4/5] Fixed task definition change type type --- web/apps/doprocess/components/layouts/SplitView.tsx | 2 +- web/apps/doprocess/src/lib/repo/processesRepository.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/web/apps/doprocess/components/layouts/SplitView.tsx b/web/apps/doprocess/components/layouts/SplitView.tsx index 656ffeca56..ffd6966129 100644 --- a/web/apps/doprocess/components/layouts/SplitView.tsx +++ b/web/apps/doprocess/components/layouts/SplitView.tsx @@ -41,7 +41,7 @@ export function SplitView({ children, size, minSize, maxSize, collapsable, colla onTouchStart={handlers.handleTouchStart} orientation="vertical" /> -
+
{children && children[1]}
diff --git a/web/apps/doprocess/src/lib/repo/processesRepository.ts b/web/apps/doprocess/src/lib/repo/processesRepository.ts index cdbda8538d..54c6c4a66b 100644 --- a/web/apps/doprocess/src/lib/repo/processesRepository.ts +++ b/web/apps/doprocess/src/lib/repo/processesRepository.ts @@ -76,7 +76,9 @@ export async function createProcess(userId: string, name: string, basedOn?: stri if (taskDefinitions.length) { await Promise.all(taskDefinitions.map(async td => { const taskDefinitionId = await createTaskDefinition(userId, id, td.text ?? ''); - await changeTaskDefinitionType(userId, id, taskDefinitionId, td.type, td.typeData ?? ''); + if (td.type) { + await changeTaskDefinitionType(userId, id, taskDefinitionId, td.type, td.typeData ?? ''); + } })); } } From 0afb6ee13ad85a1e24cda6ad85c87d17a5454a8c Mon Sep 17 00:00:00 2001 From: Aleksandar Toplek Date: Sat, 25 Nov 2023 22:25:09 +0100 Subject: [PATCH 5/5] Update processesRepository.ts --- web/apps/doprocess/src/lib/repo/processesRepository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/apps/doprocess/src/lib/repo/processesRepository.ts b/web/apps/doprocess/src/lib/repo/processesRepository.ts index cdbda8538d..aa57f805a8 100644 --- a/web/apps/doprocess/src/lib/repo/processesRepository.ts +++ b/web/apps/doprocess/src/lib/repo/processesRepository.ts @@ -66,7 +66,7 @@ export async function createProcess(userId: string, name: string, basedOn?: stri createdBy: userId })).insertId); - // TODO: Copy task definitions to new process (if basedOn is provided) + // Copy task definitions to new process (if basedOn is provided) if (basedOn) { const basedOnId = await getProcessIdByPublicId(basedOn); if (basedOnId) {