Skip to content

Commit

Permalink
feat: add column layout modification in general list view
Browse files Browse the repository at this point in the history
  • Loading branch information
RitvikSardana committed Feb 4, 2025
1 parent 171dfa3 commit 233255e
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 4 deletions.
24 changes: 20 additions & 4 deletions desk/src/components/ListViewBuilder.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<Reload @click="reload" :loading="list.loading" />
<Filter :default_filters="defaultParams.filters" />
<SortBy :hide-label="isMobileView" />
<ColumnSettings :hide-label="isMobileView" />
</div>
<div v-else class="flex justify-between items-center w-full">
<Filter :default_filters="defaultParams.filters" />
Expand Down Expand Up @@ -111,13 +112,15 @@ import {
SortBy,
QuickFilters,
Reload,
ColumnSettings,
} from "@/components/view-controls";
import { MultipleAvatar, StarRating } from "@/components";
import { dayjs } from "@/dayjs";
import ListRows from "./ListRows.vue";
import { useScreenSize } from "@/composables/screen";
import EmptyState from "./EmptyState.vue";
import { View } from "@/types";
import { watch } from "vue";
interface P {
options: {
Expand Down Expand Up @@ -173,6 +176,8 @@ const defaultParams = reactive({
page_length: props.options.default_page_length,
page_length_count: props.options.default_page_length,
view: props.options.view,
columns: [],
rows: [],
});
const emptyState = computed(() => {
Expand Down Expand Up @@ -377,7 +382,7 @@ provide("listViewData", listViewData);
provide("listViewActions", {
applyFilters,
applySort,
addColumn,
updateColumns,
reload,
});
Expand All @@ -391,8 +396,19 @@ function applySort(order_by: string) {
list.submit({ ...defaultParams, order_by });
}
function addColumn(field) {
console.log("ADD COLUMN", field);
function updateColumns(obj) {
const { columns: _columns, isDefault, reload, reset } = obj;
_columns?.forEach((column) => {
handleFetchFromField(column);
handleColumnConfig(column);
return column;
});
columns.value = _columns;
list.data.columns = _columns;
defaultParams.columns = _columns;
if (reload) {
list.reload({ ...defaultParams });
}
}
function reload() {
Expand All @@ -416,7 +432,7 @@ function handlePageLength(count: number, loadMore: boolean = false) {
list.reload();
}
// to handle cases where the list view is updated
// to handle cases where the list view is updated from the parent component
defineExpose({
reload,
});
Expand Down
3 changes: 3 additions & 0 deletions desk/src/components/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ export { default as PhoneIcon } from "./PhoneIcon.vue";
export { default as EmailIcon } from "./EmailIcon.vue";
export { default as LinkIcon } from "./LinkIcon.vue";
export { default as ActivityIcon } from "./ActivityIcon.vue";
export { default as ColumnsIcon } from "./ColumnsIcon.vue";
export { default as DotIcon } from "./DotIcon.vue";
export { default as EmailAtIcon } from "./EmailAtIcon.vue";
export { default as EditIcon } from "./EditIcon.vue";
export { default as CommentIcon } from "./CommentIcon.vue";
export { default as IndicatorIcon } from "./IndicatorIcon.vue";
export { default as TicketIcon } from "./TicketIcon.vue";
export { default as AttachmentIcon } from "./AttachmentIcon.vue";
export { default as ReplyIcon } from "./ReplyIcon.vue";
export { default as ReplyAllIcon } from "./ReplyAllIcon.vue";
export { default as RefreshIcon } from "./RefreshIcon.vue";
export { default as ReloadIcon } from "./ReloadIcon.vue";
export { default as DetailsIcon } from "./DetailsIcon.vue";
export { default as ThumbsUpIcon } from "./ThumbsUpIcon.vue";
export { default as ThumbsUpFilledIcon } from "./ThumbsUpFilledIcon.vue";
Expand Down
302 changes: 302 additions & 0 deletions desk/src/components/view-controls/ColumnSettings.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
<template>
<NestedPopover>
<template #target>
<Button label="Columns">
<template v-if="hideLabel">
<ColumnsIcon class="h-4" />
</template>
<template v-if="!hideLabel" #prefix>
<ColumnsIcon class="h-4" />
</template>
</Button>
</template>
<template #body="{ close }">
<div
class="my-2 p-1.5 min-w-40 rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
>
<div v-if="!edit">
<Draggable
:list="columns"
@end="apply"
:delay="isTouchScreenDevice() ? 200 : 0"
item-key="key"
class="list-group"
>
<template #item="{ element }">
<div
class="flex cursor-grab items-center justify-between gap-6 rounded px-2 py-1.5 text-base text-ink-gray-8 hover:bg-surface-gray-2"
>
<div class="flex items-center gap-2">
<DragIcon class="h-3.5" />
<div>{{ element.label }}</div>
</div>
<div class="flex cursor-pointer items-center gap-1">
<Button
variant="ghost"
class="!h-5 w-5 !p-1"
@click="editColumn(element)"
>
<EditIcon class="h-3.5" />
</Button>
<Button
variant="ghost"
class="!h-5 w-5 !p-1"
@click="removeColumn(element)"
>
<FeatherIcon name="x" class="h-3.5" />
</Button>
</div>
</div>
</template>
</Draggable>
<div
class="mt-1.5 flex flex-col gap-1 border-t border-outline-gray-modals pt-1.5"
>
<Autocomplete
value=""
:options="fields"
@change="(e) => addColumn(e)"
>
<template #target="{ togglePopover }">
<Button
class="w-full !justify-start !text-ink-gray-5"
variant="ghost"
@click="togglePopover()"
label="Add Column"
>
<template #prefix>
<FeatherIcon name="plus" class="h-4" />
</template>
</Button>
</template>
</Autocomplete>
<Button
v-if="columnsUpdated"
class="w-full !justify-start !text-ink-gray-5"
variant="ghost"
@click="reset(close)"
label="Reset Changes"
>
<template #prefix>
<ReloadIcon class="h-4" />
</template>
</Button>
<Button
v-if="!is_default"
class="w-full !justify-start !text-ink-gray-5"
variant="ghost"
@click="resetToDefault(close)"
label="Reset to Default"
>
<template #prefix>
<ReloadIcon class="h-4" />
</template>
</Button>
</div>
</div>
<div v-else>
<div
class="flex flex-col items-center justify-between gap-2 rounded px-2 py-1.5 text-base text-ink-gray-8"
>
<div class="flex flex-col items-center gap-3">
<FormControl
type="text"
size="md"
label="Label"
v-model="column.label"
class="sm:w-full w-52"
placeholder="First Name"
/>
<FormControl
type="text"
size="md"
label="Width"
class="sm:w-full w-52"
v-model="column.width"
placeholder="10rem"
:description="'Width can be in number, pixel or rem (eg. 3, 30px, 10rem)'"
:debounce="500"
/>
</div>
<div class="flex w-full gap-2 border-t pt-2">
<Button
variant="subtle"
label="Cancel"
class="w-full flex-1"
@click="cancelUpdate"
/>
<Button
variant="solid"
label="Update"
class="w-full flex-1"
@click="updateColumn(column)"
/>
</div>
</div>
</div>
</div>
</template>
</NestedPopover>
</template>

<script setup>
import {
DragIcon,
ReloadIcon,
EditIcon,
ColumnsIcon,
} from "@/components/icons";
import NestedPopover from "@/components/NestedPopover.vue";
import Autocomplete from "@/components/frappe-ui/Autocomplete.vue";
import { isTouchScreenDevice } from "@/utils";
import Draggable from "vuedraggable";
import { computed, ref, inject } from "vue";
import { watchOnce } from "@vueuse/core";
const props = defineProps({
hideLabel: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update"]);
const columnsUpdated = ref(false);
const oldValues = ref({
columns: [],
rows: [],
isDefault: false,
});
const listViewData = inject("listViewData");
const { list } = listViewData;
const listViewActions = inject("listViewActions");
const edit = ref(false);
const column = ref({
old: {},
label: "",
key: "",
width: "10rem",
});
const is_default = computed({
get: () => list.data?.is_default,
set: (val) => {
list.data.is_default = val;
},
});
const columns = computed({
get: () => list.data?.columns,
set: (val) => {
list.data.columns = val;
},
});
const rows = computed({
get: () => list.data?.data,
set: (val) => {
list.data.data = val;
},
});
const fields = computed(() => {
let allFields = list.data?.fields;
if (!allFields) return [];
return allFields.filter((field) => {
return !columns.value.find((column) => column.key === field.value);
});
});
function addColumn(c) {
let align = ["Float", "Int", "Percent", "Currency"].includes(c.type)
? "right"
: "left";
let _column = {
label: c.label,
type: c.type,
key: c.value,
width: "10rem",
align,
};
// debugger;
columns.value.push(_column);
rows.value.push(c.value);
apply(true);
}
function removeColumn(c) {
columns.value = columns.value.filter((column) => column.key !== c.key);
if (c.key !== "name") {
rows.value = rows.value.filter((row) => row !== c.key);
}
apply();
}
function editColumn(c) {
edit.value = true;
column.value = c;
column.value.old = { ...c };
}
function updateColumn(c) {
edit.value = false;
let index = columns.value.findIndex((column) => column.key === c.key);
columns.value[index].label = c.label;
columns.value[index].width = c.width;
if (columns.value[index].old) {
delete columns.value[index].old;
}
apply();
}
function cancelUpdate() {
edit.value = false;
column.value.label = column.value.old.label;
column.value.width = column.value.old.width;
delete column.value.old;
}
function reset(close) {
apply(true, false, true);
close();
}
function resetToDefault(close) {
apply(true, true);
close();
}
function apply(reload = false, isDefault = false, reset = false) {
is_default.value = isDefault;
columnsUpdated.value = true;
let obj = {
columns: reset ? oldValues.value.columns : columns.value,
rows: reset ? oldValues.value.rows : rows.value,
isDefault: reset ? oldValues.value.isDefault : isDefault,
reload,
reset,
};
listViewActions.updateColumns(obj);
if (reload) {
setTimeout(() => {
is_default.value = reset ? oldValues.value.isDefault : isDefault;
columnsUpdated.value = !reset;
}, 100);
}
}
watchOnce(
() => list.data,
(val) => {
if (!val) return;
oldValues.value.columns = JSON.parse(JSON.stringify(val.columns));
oldValues.value.rows = JSON.parse(JSON.stringify(val.data));
oldValues.value.isDefault = val.is_default;
}
);
</script>
1 change: 1 addition & 0 deletions desk/src/components/view-controls/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { default as Filter } from "./Filter.vue";
export { default as SortBy } from "./SortBy.vue";
export { default as QuickFilters } from "./QuickFilters.vue";
export { default as Reload } from "./Reload.vue";
export { default as ColumnSettings } from "./ColumnSettings.vue";
Loading

0 comments on commit 233255e

Please sign in to comment.