Skip to content

Commit 82182d8

Browse files
authored
Merge pull request #1384 from Gadha2311/multi-item-selection
feat : add multiple selection option in item master
2 parents eddfb84 + 5308919 commit 82182d8

File tree

3 files changed

+164
-7
lines changed

3 files changed

+164
-7
lines changed

src/components/Controls/Check.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
{{ df.label }}
99
</div>
1010
<div
11-
style="width: 14px; height: 14px; overflow: hidden"
11+
style="width: 14px; height: 14px"
1212
:class="isReadOnly ? 'cursor-default' : 'cursor-pointer'"
1313
>
1414
<svg

src/pages/ListView/List.vue

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,24 @@
77
paddingRight: dataSlice.length > 13 ? 'var(--w-scrollbar)' : '',
88
}"
99
>
10-
<p class="w-8 text-end me-4 text-gray-700 dark:text-gray-400">#</p>
10+
<div
11+
v-if="!isSelectionMode"
12+
class="w-8 text-end me-2 text-gray-700 dark:text-gray-400"
13+
>
14+
#
15+
</div>
16+
<div v-else class="w-8 flex justify-end me-2">
17+
<Check
18+
:df="{
19+
fieldtype: 'Check',
20+
fieldname: 'selectAll',
21+
label: '',
22+
}"
23+
:show-label="false"
24+
:value="isAllSelected"
25+
@change="toggleSelectAll"
26+
/>
27+
</div>
1128
<Row
1229
class="flex-1 text-gray-700 dark:text-gray-400 h-row-mid"
1330
:column-count="columns.length"
@@ -47,9 +64,25 @@
4764
<div v-for="(row, i) in dataSlice" :key="(row.name as string)">
4865
<!-- Row Content -->
4966
<div class="flex hover:bg-gray-50 dark:hover:bg-gray-850 items-center">
50-
<p class="w-8 text-end me-4 text-gray-900 dark:text-gray-25">
67+
<div
68+
v-if="!isSelectionMode"
69+
class="w-8 text-end me-2 text-gray-700 dark:text-gray-400"
70+
>
5171
{{ i + pageStart + 1 }}
52-
</p>
72+
</div>
73+
<div v-else class="w-8 flex justify-end me-2">
74+
<Check
75+
:df="{
76+
fieldtype: 'Check',
77+
fieldname: 'selectItem',
78+
label: '',
79+
}"
80+
:show-label="false"
81+
:value="selectedItems.includes(row.name as string)"
82+
@change="toggleItemSelection(row.name as string)"
83+
/>
84+
</div>
85+
5386
<Row
5487
gap="1rem"
5588
class="
@@ -60,7 +93,7 @@
6093
h-row-mid
6194
"
6295
:column-count="columns.length"
63-
@click="$emit('openDoc', row.name)"
96+
@click="isSelectionMode ? null : $emit('openDoc', row.name)"
6497
>
6598
<ListCell
6699
v-for="(column, c) in columns"
@@ -111,6 +144,7 @@
111144
import { ListViewSettings, RenderData } from 'fyo/model/types';
112145
import { cloneDeep } from 'lodash';
113146
import Button from 'src/components/Button.vue';
147+
import Check from 'src/components/Controls/Check.vue';
114148
import Paginator from 'src/components/Paginator.vue';
115149
import Row from 'src/components/Row.vue';
116150
import { fyo } from 'src/initFyo';
@@ -125,6 +159,7 @@ export default defineComponent({
125159
Row,
126160
ListCell,
127161
Button,
162+
Check,
128163
Paginator,
129164
},
130165
props: {
@@ -138,14 +173,16 @@ export default defineComponent({
138173
},
139174
schemaName: { type: String, required: true },
140175
canCreate: Boolean,
176+
isSelectionMode: Boolean,
141177
},
142-
emits: ['openDoc', 'makeNewDoc', 'updatedData'],
178+
emits: ['openDoc', 'makeNewDoc', 'updatedData', 'selected-items-changed'],
143179
data() {
144180
return {
145181
data: [] as RenderData[],
146182
pageStart: 0,
147183
pageEnd: 0,
148184
statusMap: {} as Record<string, string>,
185+
selectedItems: [] as string[],
149186
};
150187
},
151188
computed: {
@@ -155,6 +192,11 @@ export default defineComponent({
155192
count() {
156193
return this.pageEnd - this.pageStart + 1;
157194
},
195+
isAllSelected(): boolean {
196+
return (
197+
this.data.length > 0 && this.selectedItems.length === this.data.length
198+
);
199+
},
158200
columns() {
159201
let columns = this.listConfig?.columns ?? [];
160202
@@ -261,6 +303,21 @@ export default defineComponent({
261303
})) as RenderData[];
262304
this.$emit('updatedData', filters);
263305
},
306+
toggleItemSelection(itemName: string) {
307+
const index = this.selectedItems.indexOf(itemName);
308+
if (index > -1) {
309+
this.selectedItems.splice(index, 1);
310+
} else {
311+
this.selectedItems.push(itemName);
312+
}
313+
this.$emit('selected-items-changed', this.selectedItems);
314+
},
315+
toggleSelectAll(checked: boolean) {
316+
this.selectedItems = checked
317+
? this.data.map((row) => row.name as string)
318+
: [];
319+
this.$emit('selected-items-changed', this.selectedItems);
320+
},
264321
},
265322
});
266323
</script>

src/pages/ListView/ListView.vue

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,46 @@
11
<template>
22
<div class="flex flex-col">
33
<PageHeader :title="title">
4+
<Button
5+
v-if="
6+
schemaName === 'Item' &&
7+
(!isSelectionMode || (isSelectionMode && selectedItems.length === 0))
8+
"
9+
@click="toggleSelectionMode"
10+
>
11+
{{ t`Select` }}
12+
</Button>
13+
<div
14+
v-if="
15+
isSelectionMode && schemaName === 'Item' && selectedItems.length > 0
16+
"
17+
class="relative"
18+
>
19+
<Button class="w-40" @click="toggleDropdown"> Create Invoice </Button>
20+
<div
21+
v-if="showDropdown"
22+
class="
23+
absolute
24+
top-full
25+
mt-1
26+
bg-white
27+
border border-gray-300
28+
rounded
29+
shadow-lg
30+
z-10
31+
w-40
32+
"
33+
>
34+
<div
35+
v-for="option in actionOptions"
36+
:key="option.value"
37+
class="px-4 py-2 hover:bg-gray-100 cursor-pointer text-sm"
38+
@click="createInvoice(option.value)"
39+
>
40+
{{ option.label }}
41+
</div>
42+
</div>
43+
</div>
444
<Button ref="exportButton" :icon="false" @click="openExportModal = true">
545
{{ t`Export` }}
646
</Button>
@@ -16,7 +56,7 @@
1656
type="primary"
1757
:padding="false"
1858
class="px-3"
19-
@click="makeNewDoc"
59+
@click="handleMakeNewDoc"
2060
>
2161
<feather-icon name="plus" class="w-4 h-4" />
2262
</Button>
@@ -27,10 +67,12 @@
2767
:list-config="listConfig"
2868
:filters="filters"
2969
:can-create="canCreate"
70+
:is-selection-mode="isSelectionMode"
3071
class="flex-1 flex h-full"
3172
@open-doc="openDoc"
3273
@updated-data="updatedData"
3374
@make-new-doc="makeNewDoc"
75+
@selected-items-changed="updateSelectedItems"
3476
/>
3577
<Modal :open-modal="openExportModal" @closemodal="openExportModal = false">
3678
<ExportWizard
@@ -49,6 +91,7 @@ import ExportWizard from 'src/components/ExportWizard.vue';
4991
import FilterDropdown from 'src/components/FilterDropdown.vue';
5092
import Modal from 'src/components/Modal.vue';
5193
import PageHeader from 'src/components/PageHeader.vue';
94+
5295
import { fyo } from 'src/initFyo';
5396
import { shortcutsKey } from 'src/utils/injectionKeys';
5497
import {
@@ -60,6 +103,8 @@ import { getFormRoute, routeTo } from 'src/utils/ui';
60103
import { QueryFilter } from 'utils/db/types';
61104
import { defineComponent, inject, ref } from 'vue';
62105
import List from './List.vue';
106+
import { Money } from 'pesa';
107+
import { ModelNameEnum } from 'models/types';
63108
64109
export default defineComponent({
65110
name: 'ListView',
@@ -90,10 +135,16 @@ export default defineComponent({
90135
listConfig: undefined,
91136
openExportModal: false,
92137
listFilters: {},
138+
isSelectionMode: false,
139+
showDropdown: false,
140+
selectedItems: [] as string[],
93141
} as {
94142
listConfig: undefined | ReturnType<typeof getListConfig>;
95143
openExportModal: boolean;
96144
listFilters: QueryFilter;
145+
isSelectionMode: boolean;
146+
showDropdown: boolean;
147+
selectedItems: string[];
97148
};
98149
},
99150
computed: {
@@ -113,6 +164,12 @@ export default defineComponent({
113164
canCreate(): boolean {
114165
return fyo.schemaMap[this.schemaName]?.create !== false;
115166
},
167+
actionOptions(): { value: string; label: string }[] {
168+
return [
169+
{ value: 'SalesInvoice', label: 'Sales Invoice' },
170+
{ value: 'PurchaseInvoice', label: 'Purchase Invoice' },
171+
];
172+
},
116173
},
117174
activated() {
118175
this.listConfig = getListConfig(this.schemaName);
@@ -160,9 +217,52 @@ export default defineComponent({
160217
const route = getFormRoute(this.schemaName, doc.name!);
161218
await routeTo(route);
162219
},
220+
async handleMakeNewDoc() {
221+
await this.makeNewDoc();
222+
},
163223
applyFilter(filters: QueryFilter) {
164224
this.list?.updateData(filters);
165225
},
226+
toggleSelectionMode() {
227+
this.isSelectionMode = !this.isSelectionMode;
228+
if (!this.isSelectionMode) {
229+
this.showDropdown = false;
230+
this.selectedItems = [];
231+
}
232+
},
233+
toggleDropdown() {
234+
this.showDropdown = !this.showDropdown;
235+
},
236+
async createInvoice(value: string) {
237+
if (
238+
value === ModelNameEnum.SalesInvoice ||
239+
value === ModelNameEnum.PurchaseInvoice
240+
) {
241+
const doc = fyo.doc.getNewDoc(value);
242+
243+
for (const itemName of this.selectedItems) {
244+
const itemDoc = await fyo.doc.getDoc('Item', itemName);
245+
246+
const itemRow = {
247+
item: itemName,
248+
rate: (itemDoc.rate as Money) || fyo.pesa(0),
249+
quantity: 1,
250+
};
251+
252+
await doc.append('items', itemRow);
253+
}
254+
255+
const route = getFormRoute(value, doc.name!);
256+
await routeTo(route);
257+
this.selectedItems = [];
258+
this.isSelectionMode = false;
259+
this.showDropdown = false;
260+
}
261+
},
262+
263+
updateSelectedItems(selected: string[]) {
264+
this.selectedItems = selected;
265+
},
166266
},
167267
});
168268

0 commit comments

Comments
 (0)