Skip to content

Commit 6a2cb04

Browse files
committed
feat: empty kanban lane
1 parent 3b9198a commit 6a2cb04

File tree

4 files changed

+88
-50
lines changed

4 files changed

+88
-50
lines changed

apps/frontend/src/lib/components/blocks/kanban-view/select-kanban-lane.svelte

Lines changed: 48 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
export let viewId: string
3131
export let fieldId: string
3232
export let field: SelectField
33-
export let option: IOption
33+
export let option: IOption | null
3434
export let readonly = false
3535
export let shareId: string
3636
@@ -40,7 +40,7 @@
4040
shareId,
4141
filters: {
4242
conjunction: "and",
43-
children: [{ field: fieldId, op: "eq", value: option.id }],
43+
children: [{ field: fieldId, op: "eq", value: option ? option.id : null }],
4444
},
4545
pagination: {
4646
page: pageParam,
@@ -54,7 +54,7 @@
5454
viewId,
5555
filters: {
5656
conjunction: "and",
57-
children: [{ field: fieldId, op: "eq", value: option.id }],
57+
children: [{ field: fieldId, op: "eq", value: option ? option.id : null }],
5858
},
5959
pagination: {
6060
page: pageParam,
@@ -64,7 +64,7 @@
6464
}
6565
6666
const query = createInfiniteQuery({
67-
queryKey: [tableId, fieldId, "getRecords", option.id],
67+
queryKey: [tableId, fieldId, "getRecords", option?.id],
6868
queryFn: getRecords,
6969
initialPageParam: 1,
7070
getNextPageParam: (lastPage, pages) => {
@@ -81,7 +81,7 @@
8181
const updateRecord = createMutation({
8282
mutationFn: trpc.record.update.mutate,
8383
onSuccess: (data, variables, context) => {
84-
recordsStore.setRecordValue(variables.id, fieldId, option.id)
84+
recordsStore.setRecordValue(variables.id, fieldId, option ? option.id : null)
8585
recordsStore.invalidateRecord($table, variables.id)
8686
},
8787
})
@@ -108,7 +108,7 @@
108108
option: {
109109
options: field.option
110110
.unwrapOrElse(() => ({ options: [] }))
111-
.options.map((o) => (o.id === option.id ? { ...option } : o)),
111+
.options.map((o) => (o.id === option?.id ? { ...option } : o)),
112112
},
113113
},
114114
})
@@ -122,14 +122,14 @@
122122
type: "select",
123123
name: field.name.value,
124124
option: {
125-
options: field.option.unwrapOrElse(() => ({ options: [] })).options.filter((o) => o.id !== option.id),
125+
options: field.option.unwrapOrElse(() => ({ options: [] })).options.filter((o) => o.id !== option?.id),
126126
},
127127
},
128128
})
129129
}
130130
131131
onMount(() => {
132-
if (!shareId) {
132+
if (!shareId && !readonly) {
133133
new Sortable(laneElement, {
134134
group: "shared",
135135
animation: 150,
@@ -139,8 +139,7 @@
139139
onEnd: (evt) => {
140140
const recordId = evt.item.dataset.recordId
141141
if (!recordId) return
142-
const optionId = evt.to.dataset.optionId
143-
if (!optionId) return
142+
const optionId = evt.to.dataset.optionId ?? null
144143
145144
$updateRecord.mutate({
146145
tableId,
@@ -160,7 +159,7 @@
160159
recordsStore.upsertRecords(Records.fromJSON($table, records))
161160
}
162161
163-
$: recordDos = recordsStore.getRecords(Some(new SelectEqual(option.id, new FieldIdVo(fieldId))))
162+
$: recordDos = recordsStore.getRecords(Some(new SelectEqual(option?.id ?? null, new FieldIdVo(fieldId))))
164163
165164
$: fields = $table.getOrderedVisibleFields(viewId) ?? []
166165
@@ -169,20 +168,25 @@
169168
</script>
170169

171170
<div
172-
data-option-id={option.id}
171+
data-option-id={option?.id ?? null}
173172
class="kanban-lane flex w-[350px] shrink-0 flex-col space-y-2 rounded-sm px-2 pt-2 transition-all"
174173
>
175174
<div class="flex w-full items-center justify-between gap-1">
176175
<div class="flex items-center gap-1">
177-
{#if !shareId}
176+
{#if !shareId && option && !readonly}
178177
<div class="lane-handle cursor-move">
179178
<GripVerticalIcon class="text-muted-foreground h-4 w-4" />
180179
</div>
181180
{/if}
182-
<Option {option} />
181+
182+
{#if option}
183+
<Option {option} />
184+
{:else}
185+
<Option option={{ id: "", name: "No Option", color: "gray" }} />
186+
{/if}
183187
</div>
184188

185-
{#if !shareId && !readonly}
189+
{#if !shareId && !readonly && option}
186190
<DropdownMenu.Root>
187191
<DropdownMenu.Trigger asChild let:builder>
188192
<Button size="xs" variant="ghost" builders={[builder]}>
@@ -208,17 +212,17 @@
208212
</DropdownMenu.Root>
209213
{/if}
210214
</div>
211-
<div class="max-w-[350px] flex-1 space-y-2 overflow-auto" data-option-id={option.id}>
215+
<div class="max-w-[350px] flex-1 space-y-2 overflow-auto" data-option-id={option?.id ?? null}>
212216
<div
213217
bind:this={laneElement}
214-
data-option-id={option.id}
218+
data-option-id={option?.id ?? null}
215219
class="min-h-[200px] space-y-2 rounded-lg border bg-gray-100 p-2"
216220
>
217221
{#if !readonly && $hasPermission("record:create")}
218222
<Button
219223
on:click={() => {
220224
$defaultRecordValues = {
221-
[fieldId]: option.id,
225+
[fieldId]: option ? option.id : null,
222226
}
223227
toggleModal(CREATE_RECORD_MODAL)
224228
}}
@@ -253,27 +257,31 @@
253257
</div>
254258
</div>
255259

256-
<Dialog.Root bind:open={updateOptionDialogOpen}>
257-
<Dialog.Content>
258-
<Dialog.Header>
259-
<Dialog.Title>Update option</Dialog.Title>
260-
</Dialog.Header>
260+
{#if option}
261+
<Dialog.Root bind:open={updateOptionDialogOpen}>
262+
<Dialog.Content>
263+
<Dialog.Header>
264+
<Dialog.Title>Update option</Dialog.Title>
265+
</Dialog.Header>
261266

262-
<form on:submit|preventDefault={() => updateOption()}>
263-
<OptionEditor bind:name={option.name} bind:color={option.color} />
264-
<Button type="submit" disabled={$updateFieldMutation.isPending} class="mt-2 w-full">Update</Button>
265-
</form>
266-
</Dialog.Content>
267-
</Dialog.Root>
267+
<form on:submit|preventDefault={() => updateOption()}>
268+
<OptionEditor bind:name={option.name} bind:color={option.color} />
269+
<Button type="submit" disabled={$updateFieldMutation.isPending} class="mt-2 w-full">Update</Button>
270+
</form>
271+
</Dialog.Content>
272+
</Dialog.Root>
273+
{/if}
268274

269-
<AlertDialog.Root bind:open={deleteOptionDialogOpen}>
270-
<AlertDialog.Content>
271-
<AlertDialog.Header>
272-
<AlertDialog.Title>Delete Option <Option {option} /></AlertDialog.Title>
273-
</AlertDialog.Header>
274-
<AlertDialog.Footer>
275-
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
276-
<AlertDialog.Action on:click={() => deleteOption()}>Continue</AlertDialog.Action>
277-
</AlertDialog.Footer>
278-
</AlertDialog.Content>
279-
</AlertDialog.Root>
275+
{#if option}
276+
<AlertDialog.Root bind:open={deleteOptionDialogOpen}>
277+
<AlertDialog.Content>
278+
<AlertDialog.Header>
279+
<AlertDialog.Title>Delete Option <Option {option} /></AlertDialog.Title>
280+
</AlertDialog.Header>
281+
<AlertDialog.Footer>
282+
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
283+
<AlertDialog.Action on:click={() => deleteOption()}>Continue</AlertDialog.Action>
284+
</AlertDialog.Footer>
285+
</AlertDialog.Content>
286+
</AlertDialog.Root>
287+
{/if}

apps/frontend/src/lib/components/blocks/kanban-view/select-kanban-view.svelte

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,15 @@
9696

9797
<div class="flex-1 overflow-x-auto overflow-y-hidden p-4">
9898
<div bind:this={lanesContainer} class="flex h-full overflow-y-hidden pr-4">
99+
<SelectKanbanLane
100+
{field}
101+
{readonly}
102+
tableId={$table.id.value}
103+
viewId={view.id.value}
104+
{fieldId}
105+
option={null}
106+
{shareId}
107+
/>
99108
{#each options as option (option.id)}
100109
<SelectKanbanLane
101110
{field}

packages/persistence/src/record/record.filter-visitor.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,14 @@ export class RecordFilterVisitor extends AbstractQBVisitor<RecordDO> implements
106106
this.addCond(cond)
107107
}
108108
dateEqual(spec: DateEqual): void {
109-
this.addCond(this.eb.eb(this.getFieldId(spec), "=", spec.date?.getTime() ?? null))
109+
const time = spec.date?.getTime()
110+
if (time === null) {
111+
const cond = this.eb.eb(this.getFieldId(spec), "is", null)
112+
this.addCond(cond)
113+
} else {
114+
const cond = this.eb.eb(this.getFieldId(spec), "=", time)
115+
this.addCond(cond)
116+
}
110117
}
111118
attachmentEqual(s: AttachmentEqual): void {
112119
throw new Error("Method not implemented.")
@@ -123,7 +130,7 @@ export class RecordFilterVisitor extends AbstractQBVisitor<RecordDO> implements
123130
}
124131
userEqual(spec: UserEqual): void {
125132
if (spec.value === null) {
126-
const cond = this.eb.eb(this.getFieldId(spec), "=", null)
133+
const cond = this.eb.eb(this.getFieldId(spec), "is", null)
127134
this.addCond(cond)
128135
} else {
129136
function convertMacro(value: string) {
@@ -140,9 +147,14 @@ export class RecordFilterVisitor extends AbstractQBVisitor<RecordDO> implements
140147
const cond = this.eb.eb(this.getFieldId(spec), "=", JSON.stringify(converted))
141148
this.addCond(cond)
142149
} else {
143-
const converted = spec.value ? convertMacro(spec.value) : null
144-
const cond = this.eb.eb(this.getFieldId(spec), "=", converted)
145-
this.addCond(cond)
150+
if (!spec.value) {
151+
const cond = this.eb.eb(this.getFieldId(spec), "is", null)
152+
this.addCond(cond)
153+
} else {
154+
const converted = convertMacro(spec.value)
155+
const cond = this.eb.eb(this.getFieldId(spec), "=", converted)
156+
this.addCond(cond)
157+
}
146158
}
147159
}
148160
}
@@ -151,8 +163,13 @@ export class RecordFilterVisitor extends AbstractQBVisitor<RecordDO> implements
151163
this.addCond(cond)
152164
}
153165
currencyEqual(spec: CurrencyEqual): void {
154-
const cond = this.eb.eb(this.getFieldId(spec), "=", spec.value === null ? null : spec.value * 100)
155-
this.addCond(cond)
166+
if (spec.value === null) {
167+
const cond = this.eb.eb(this.getFieldId(spec), "is", null)
168+
this.addCond(cond)
169+
} else {
170+
const cond = this.eb.eb(this.getFieldId(spec), "=", spec.value * 100)
171+
this.addCond(cond)
172+
}
156173
}
157174
percentageEqual(spec: PercentageEqual): void {
158175
const cond = this.eb.eb(this.getFieldId(spec), "=", spec.value)
@@ -204,7 +221,7 @@ export class RecordFilterVisitor extends AbstractQBVisitor<RecordDO> implements
204221
}
205222
selectEqual(spec: SelectEqual): void {
206223
if (spec.value === null) {
207-
const cond = this.eb.eb(this.getFieldId(spec), "=", null)
224+
const cond = this.eb.eb(this.getFieldId(spec), "is", null)
208225
this.addCond(cond)
209226
} else {
210227
const cond = this.eb.eb(
@@ -216,7 +233,7 @@ export class RecordFilterVisitor extends AbstractQBVisitor<RecordDO> implements
216233
}
217234
}
218235
selectEmpty(spec: SelectEmpty): void {
219-
const cond = this.eb.eb(this.getFieldId(spec), "=", null)
236+
const cond = this.eb.eb(this.getFieldId(spec), "is", null)
220237
this.addCond(cond)
221238
}
222239
selectContainsAnyOf(spec: SelectContainsAnyOf): void {

packages/persistence/src/record/record.mutate-visitor.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,11 @@ export class RecordMutateVisitor extends AbstractQBMutationVisitor implements IR
284284
const fieldValue = new SelectFieldValue(spec.value)
285285
const value = fieldValue.getValue(field)
286286

287-
this.setData(spec.fieldId.value, Array.isArray(value) ? JSON.stringify(value) : value)
287+
if (Array.isArray(value)) {
288+
this.setData(spec.fieldId.value, value.length ? JSON.stringify(value) : null)
289+
} else {
290+
this.setData(spec.fieldId.value, value ?? null)
291+
}
288292
}
289293
selectContainsAnyOf(spec: SelectContainsAnyOf): void {
290294
throw new Error("Method not implemented.")

0 commit comments

Comments
 (0)