-
-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1275 from undb-xyz/feature/gantt
- Loading branch information
Showing
56 changed files
with
1,792 additions
and
12,798 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
apps/backend/src/core/table/commands/set-gantt-field.command.handler.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import type { ICommandHandler } from '@nestjs/cqrs' | ||
import { CommandHandler } from '@nestjs/cqrs' | ||
import { type ITableRepository } from '@undb/core' | ||
import { SetGanttFieldCommandHandler as DomainHandler, SetGanttFieldCommand } from '@undb/cqrs' | ||
import { InjectTableRepository } from '../adapters/index.js' | ||
|
||
@CommandHandler(SetGanttFieldCommand) | ||
export class SetGanttFieldCommandHandler extends DomainHandler implements ICommandHandler<SetGanttFieldCommand> { | ||
constructor( | ||
@InjectTableRepository() | ||
protected readonly repo: ITableRepository, | ||
) { | ||
super(repo) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
<script lang="ts"> | ||
import { getTable, getView } from '$lib/store/table' | ||
import { Button, Hr, Radio } from 'flowbite-svelte' | ||
import FieldIcon from '$lib/field/FieldIcon.svelte' | ||
import { trpc } from '$lib/trpc/client' | ||
import { writable } from 'svelte/store' | ||
import { configViewModal, createFieldInitial, createFieldModal } from '$lib/store/modal' | ||
import { t } from '$lib/i18n' | ||
import { invalidate } from '$app/navigation' | ||
import { FieldId } from '@undb/core' | ||
const table = getTable() | ||
const view = getView() | ||
$: ganttFields = $table.schema.ganttFields | ||
const ganttField = writable($view.ganttFieldIdString) | ||
const setField = trpc().table.view.gantt.setField.mutation({ | ||
async onSuccess(data, variables, context) { | ||
await invalidate(`table:${$table.id.value}`) | ||
$view.ganttFieldIdString = $ganttField | ||
configViewModal.close() | ||
}, | ||
}) | ||
const onChange = async () => { | ||
if (ganttField) { | ||
$setField.mutate({ | ||
tableId: $table.id.value, | ||
viewId: $view.id.value, | ||
field: $ganttField, | ||
}) | ||
} | ||
} | ||
</script> | ||
|
||
<div class="flex flex-col space-y-2"> | ||
{#each ganttFields as field} | ||
<Radio bind:group={$ganttField} name="ganttFieldId" value={field.id.value} on:change={onChange} class="space-x-1"> | ||
<FieldIcon type={field.type} /> | ||
<span>{field.name.value}</span> | ||
</Radio> | ||
{/each} | ||
</div> | ||
|
||
{#if ganttFields.length} | ||
<div class="my-4"> | ||
<Hr> | ||
<span class="text-gray-400 text-sm font-normal">{$t('or', { ns: 'common' })}</span> | ||
</Hr> | ||
</div> | ||
{/if} | ||
|
||
<div class="flex flex-col justify-center gap-2"> | ||
<Button | ||
size="xs" | ||
color="light" | ||
class="flex gap-2" | ||
on:click={() => { | ||
const id = FieldId.createId() | ||
$createFieldInitial = { | ||
id, | ||
type: 'date-range', | ||
} | ||
|
||
createFieldModal.open(async () => { | ||
$setField.mutate({ | ||
tableId: $table.id.value, | ||
viewId: $view.id.value, | ||
field: id, | ||
}) | ||
}) | ||
}} | ||
> | ||
<i class="ti ti-plus" /> | ||
<span>{$t('Create New Date Range Field')}</span> | ||
<FieldIcon type="select" /> | ||
</Button> | ||
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<script lang="ts"> | ||
import { getTable, getView } from '$lib/store/table' | ||
import { Card } from 'flowbite-svelte' | ||
import type { DateRangeField } from '@undb/core' | ||
import GanttConfig from './GanttConfig.svelte' | ||
import GanttView from './GanttView.svelte' | ||
const table = getTable() | ||
const view = getView() | ||
$: fieldId = $view.ganttFieldIdString | ||
$: field = fieldId ? ($table.schema.getFieldById(fieldId).into() as DateRangeField | undefined) : undefined | ||
</script> | ||
|
||
{#if field} | ||
<GanttView {field} /> | ||
{:else} | ||
<div class="flex items-center justify-center h-screen w-full bg-gray-100 dark:bg-slate-800/80"> | ||
<Card class="flex-1"> | ||
<GanttConfig /> | ||
</Card> | ||
</div> | ||
{/if} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<script lang="ts"> | ||
import ShareViewButton from '$lib/share/ShareViewButton.svelte' | ||
import CreateRecordButton from '$lib/table/CreateRecordButton.svelte' | ||
import FilterMenu from '$lib/table/FilterMenu.svelte' | ||
</script> | ||
|
||
<CreateRecordButton /> | ||
<FilterMenu /> | ||
<ShareViewButton /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
<script lang="ts"> | ||
import { SvelteGantt, SvelteGanttDependencies, SvelteGanttTable } from 'svelte-gantt' | ||
import type { SvelteGanttComponent, SvelteGanttOptions } from 'svelte-gantt/types/gantt' | ||
import { addDays, endOfWeek, startOfWeek, subDays } from 'date-fns' | ||
import { currentRecordId, getTable, listRecordFn, readonly, recordHash } from '$lib/store/table' | ||
import { RecordFactory, type DateRangeField } from '@undb/core' | ||
import type { RowModel } from 'svelte-gantt/types/core/row' | ||
import type { TaskModel } from 'svelte-gantt/types/core/task' | ||
import { onMount } from 'svelte' | ||
import { t } from '$lib/i18n' | ||
import { trpc } from '$lib/trpc/client' | ||
const table = getTable() | ||
export let field: DateRangeField | ||
let currentStart = startOfWeek(new Date()) | ||
let currentEnd = endOfWeek(new Date()) | ||
const previous = () => { | ||
currentStart = subDays(currentStart, 7) | ||
currentEnd = subDays(currentEnd, 7) | ||
} | ||
const next = () => { | ||
currentStart = addDays(currentStart, 7) | ||
currentEnd = addDays(currentEnd, 7) | ||
} | ||
$: listRecords = $listRecordFn( | ||
[ | ||
{ | ||
path: field.id.value, | ||
type: field.type, | ||
operator: '$between', | ||
value: [currentStart.toISOString(), currentEnd.toISOString()], | ||
}, | ||
], | ||
{ | ||
queryHash: $recordHash + '_gantt', | ||
}, | ||
) | ||
const updateRecord = trpc().record.update.mutation({ | ||
async onSuccess(data, variables, context) { | ||
await $listRecords.refetch() | ||
}, | ||
}) | ||
$: records = RecordFactory.fromQueryRecords($listRecords?.data?.records ?? [], $table.schema.toIdMap()) ?? [] | ||
$: rows = records.map<RowModel>((r) => ({ | ||
id: r.id.value, | ||
label: r.getDisplayFieldsValue($table), | ||
height: 52, | ||
classes: 'bg-gray-100 dark:!bg-gray-300 dark:text-white', | ||
})) | ||
$: tasks = records.map<TaskModel>((r) => { | ||
const value = r.valuesJSON?.[field.id.value] | ||
const [from, to] = value | ||
const fromTimeStamp = new Date(from).getTime() | ||
const toTimeStampe = new Date(to).getTime() | ||
return { | ||
id: r.id.value as any as number, | ||
resourceId: r.id.value as any as number, | ||
label: r.getDisplayFieldsValue($table), | ||
from: fromTimeStamp, | ||
to: toTimeStampe, | ||
classes: '!bg-blue-400 hover:!bg-blue-500', | ||
enableDragging: !$readonly, | ||
} | ||
}) | ||
$: options = { | ||
rows, | ||
tasks, | ||
dependencies: [], | ||
timeRanges: [], | ||
columnOffset: 15, | ||
magnetOffset: 15, | ||
rowHeight: 52, | ||
rowPadding: 6, | ||
headers: [{ unit: 'day', format: 'MMMM Do' }], | ||
fitWidth: true, | ||
minWidth: 800, | ||
from: currentStart.getTime(), | ||
to: currentEnd.getTime(), | ||
tableHeaders: [{ title: $t('Label', { ns: 'common' }), property: 'label', width: 140 }], | ||
tableWidth: 240, | ||
ganttTableModules: [SvelteGanttTable], | ||
ganttBodyModules: [SvelteGanttDependencies], | ||
} satisfies SvelteGanttOptions | ||
let ele: HTMLElement | undefined | ||
let gantt: SvelteGanttComponent | ||
onMount(() => { | ||
if (ele) { | ||
gantt = new SvelteGantt({ target: ele, props: options }) | ||
// @ts-expect-error | ||
gantt.api.tasks.on.dblclicked((event, b) => { | ||
const [model] = event | ||
if (!model) return | ||
const recordId = model.id | ||
$currentRecordId = recordId | ||
}) | ||
// @ts-expect-error | ||
gantt.api.tasks.on.changed((event) => { | ||
const [model] = event | ||
if (!model) return | ||
if (model.sourceRow.model.id !== model.targetRow.model.id) return | ||
const task = model.task | ||
const recordId = task.model.resourceId | ||
const newFrom = new Date(task.model.from) | ||
const newTo = new Date(task.model.to) | ||
$updateRecord.mutate({ | ||
tableId: $table.id.value, | ||
id: recordId, | ||
values: { | ||
[field.id.value]: [newFrom.toISOString(), newTo.toISOString()], | ||
}, | ||
}) | ||
}) | ||
} | ||
}) | ||
$: if (gantt) gantt.$set(options) | ||
</script> | ||
|
||
<div class="w-full"> | ||
<div class="p-2 text-gray-500"> | ||
<div class="flex justify-end gap-2"> | ||
<button | ||
on:click={previous} | ||
class="p-1 hover:bg-gray-100 w-6 h-6 inline-flex items-center justify-center transition" | ||
> | ||
<i class="ti ti-chevron-left" /> | ||
</button> | ||
<button on:click={next} class="p-1 hover:bg-gray-100 w-6 h-6 inline-flex items-center justify-center transition"> | ||
<i class="ti ti-chevron-right" /> | ||
</button> | ||
</div> | ||
</div> | ||
<div class="border-t" bind:this={ele} id="undb-gantt" /> | ||
</div> | ||
|
||
<style> | ||
#undb-gantt { | ||
flex-grow: 1; | ||
overflow: auto; | ||
} | ||
#undb-gantt :global(.sg-hover) { | ||
background-color: #00000008; | ||
} | ||
#undb-gantt :global(.sg-hover .sg-table-body-cell) { | ||
background-color: #00000008; | ||
} | ||
:global(.dark .sg-gantt .column-header-cell) { | ||
color: white; | ||
} | ||
:global(.dark .sg-gantt .column-header-cell:hover) { | ||
color: #374151; | ||
background-color: #f7f7f7; | ||
} | ||
:global(.dark .sg-gantt .sg-table-body-cell) { | ||
color: white; | ||
background-color: #374151; | ||
border: none; | ||
} | ||
:global(.dark .sg-gantt .sg-table-header-cell) { | ||
color: white; | ||
background-color: #374151; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.