Skip to content

Commit f34f3eb

Browse files
authored
Merge pull request #1353 from Gadha2311/journal-entry
feat: add Recent Used Options to Quick Search Command Bar
2 parents 2232259 + 545ee3b commit f34f3eb

File tree

5 files changed

+176
-25
lines changed

5 files changed

+176
-25
lines changed

models/baseModels/Invoice/Invoice.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,13 +1271,16 @@ export abstract class Invoice extends Transactional {
12711271

12721272
let accountField: AccountFieldEnum = AccountFieldEnum.Account;
12731273
let paymentType: PaymentTypeEnum = PaymentTypeEnum.Receive;
1274+
let referenceType: 'SalesInvoice' | 'PurchaseInvoice';
12741275

1275-
if (this.isSales && this.isReturn) {
1276-
accountField = AccountFieldEnum.PaymentAccount;
1277-
paymentType = PaymentTypeEnum.Pay;
1278-
}
1279-
1280-
if (!this.isSales) {
1276+
if (this.isSales) {
1277+
referenceType = 'SalesInvoice';
1278+
if (this.isReturn) {
1279+
accountField = AccountFieldEnum.PaymentAccount;
1280+
paymentType = PaymentTypeEnum.Pay;
1281+
}
1282+
} else {
1283+
referenceType = 'PurchaseInvoice';
12811284
accountField = AccountFieldEnum.PaymentAccount;
12821285
paymentType = PaymentTypeEnum.Pay;
12831286

@@ -1296,6 +1299,7 @@ export abstract class Invoice extends Transactional {
12961299
paymentType,
12971300
amount: paymentAmount,
12981301
[accountField]: this.account,
1302+
referenceType,
12991303
for: [
13001304
{
13011305
referenceType: this.schemaName,

src/components/SearchBar.vue

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ import { docsPathMap } from 'src/utils/misc';
252252
import {
253253
SearchGroup,
254254
SearchItems,
255+
SearchItem,
255256
getGroupLabelMap,
256257
searchGroups,
257258
} from 'src/utils/search';
@@ -317,6 +318,7 @@ export default defineComponent({
317318
List: 'teal',
318319
Report: 'yellow',
319320
Page: 'orange',
321+
Recent: 'purple',
320322
};
321323
},
322324
groupColorClassMap(): Record<SearchGroup, string> {
@@ -422,7 +424,13 @@ export default defineComponent({
422424
},
423425
select(idx?: number): void {
424426
this.idx = idx ?? this.idx;
425-
this.suggestions[this.idx]?.action?.();
427+
const selectedItem = this.suggestions[this.idx];
428+
429+
if (selectedItem?.action) {
430+
this.searcher?.addToRecent(selectedItem);
431+
selectedItem.action();
432+
}
433+
426434
this.close();
427435
},
428436
scrollToHighlighted(): void {

src/utils/search.ts

Lines changed: 123 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,19 @@ import { safeParseFloat } from 'utils/index';
1010
import { RouteLocationRaw } from 'vue-router';
1111
import { fuzzyMatch } from '.';
1212
import { getFormRoute, routeTo } from './ui';
13+
import { searchGroups } from '../../utils/types';
14+
import type { SearchGroup, SearchItem } from '../../utils/types';
1315

14-
export const searchGroups = [
15-
'Docs',
16-
'List',
17-
'Create',
18-
'Report',
19-
'Page',
20-
] as const;
16+
export { searchGroups };
17+
export type { SearchGroup, SearchItem };
2118

22-
export type SearchGroup = typeof searchGroups[number];
23-
interface SearchItem {
19+
interface StoredRecentItem {
2420
label: string;
25-
group: Exclude<SearchGroup, 'Docs'>;
21+
group: string;
2622
route?: string;
27-
action?: () => void | Promise<void>;
23+
schemaName?: string;
24+
reportName?: string;
25+
timestamp: number;
2826
}
2927

3028
interface DocSearchItem extends Omit<SearchItem, 'group'> {
@@ -33,7 +31,11 @@ interface DocSearchItem extends Omit<SearchItem, 'group'> {
3331
more: string[];
3432
}
3533

36-
export type SearchItems = (DocSearchItem | SearchItem)[];
34+
interface RecentSearchItem extends Omit<SearchItem, 'group'> {
35+
group: 'Recent';
36+
}
37+
38+
export type SearchItems = (DocSearchItem | SearchItem | RecentSearchItem)[];
3739

3840
interface Searchable {
3941
needsUpdate: boolean;
@@ -69,6 +71,7 @@ export function getGroupLabelMap() {
6971
Report: t`Report`,
7072
Docs: t`Docs`,
7173
Page: t`Page`,
74+
Recent: t`Recent`,
7275
};
7376
}
7477

@@ -195,7 +198,7 @@ function getListViewList(fyo: Fyo): SearchItem[] {
195198
ModelNameEnum.PrintTemplate,
196199
];
197200

198-
if (fyo.doc.singles.AccountingSecuttings?.enableInventory) {
201+
if (fyo.doc.singles.AccountingSettings?.enableInventory) {
199202
schemaNames.push(
200203
ModelNameEnum.StockMovement,
201204
ModelNameEnum.Shipment,
@@ -350,6 +353,7 @@ export class Search {
350353

351354
_obsSet = false;
352355
numSearches = 0;
356+
recentKey = 'searchRecents';
353357
searchables: Record<string, Searchable>;
354358
keywords: Record<string, Keyword[]>;
355359
priorityMap: Record<string, number> = {
@@ -371,6 +375,7 @@ export class Search {
371375
Create: true,
372376
Page: true,
373377
Docs: true,
378+
Recent: true,
374379
},
375380
schemaFilters: {},
376381
skipTables: false,
@@ -384,6 +389,9 @@ export class Search {
384389
_nonDocSearchList: SearchItem[];
385390
_groupLabelMap?: Record<SearchGroup, string>;
386391

392+
maxRecentItems = 10;
393+
recentExpiryDays = 30;
394+
387395
constructor(fyo: Fyo) {
388396
this.fyo = fyo;
389397
this.keywords = {};
@@ -396,6 +404,89 @@ export class Search {
396404
* `skipT*` filters and the `schemaFilters`.
397405
*/
398406

407+
private _loadAndCleanRecentItems(): StoredRecentItem[] {
408+
try {
409+
const raw = localStorage.getItem(this.recentKey);
410+
return raw ? (JSON.parse(raw) as StoredRecentItem[]) : [];
411+
} catch (error) {
412+
return [];
413+
}
414+
}
415+
416+
private _saveRecentItems(items: StoredRecentItem[]) {
417+
localStorage.setItem(this.recentKey, JSON.stringify(items));
418+
}
419+
420+
addToRecent(item: SearchItems[number]) {
421+
const recents = this._loadAndCleanRecentItems();
422+
423+
const recentItem: StoredRecentItem = {
424+
label: item.label,
425+
group: item.group,
426+
timestamp: Date.now(),
427+
};
428+
429+
if ('route' in item && item.route) {
430+
recentItem.route = item.route;
431+
} else if (item.group === 'Docs') {
432+
recentItem.schemaName = item.schemaLabel;
433+
}
434+
435+
const updatedRecents = [
436+
recentItem,
437+
...recents.filter((r) => r.label !== recentItem.label),
438+
].slice(0, this.maxRecentItems);
439+
440+
this._saveRecentItems(updatedRecents);
441+
}
442+
443+
getRecentItems(searchTerm?: string): RecentSearchItem[] {
444+
try {
445+
const recents = this._loadAndCleanRecentItems();
446+
447+
let filtered = recents;
448+
if (searchTerm) {
449+
const lower = searchTerm.toLowerCase();
450+
filtered = recents.filter(
451+
(item) =>
452+
item.label.toLowerCase().includes(lower) ||
453+
item.group.toLowerCase().includes(lower)
454+
);
455+
}
456+
457+
const result = filtered.map((item) => ({
458+
label: item.label,
459+
group: 'Recent' as const,
460+
action: () => this._executeRecentAction(item),
461+
route: item.route,
462+
}));
463+
464+
return result;
465+
} catch (error) {
466+
return [];
467+
}
468+
}
469+
470+
private _executeRecentAction(item: StoredRecentItem) {
471+
if (item.route) {
472+
void routeTo(item.route);
473+
} else if (item.schemaName) {
474+
this._openDocList(item.schemaName);
475+
} else if (item.reportName) {
476+
this._openReport(item.reportName);
477+
}
478+
}
479+
480+
private _openDocList(schemaName: string) {
481+
const route = `/list/${schemaName}`;
482+
void routeTo(route);
483+
}
484+
485+
private _openReport(reportName: string) {
486+
const route = `/report/${reportName}`;
487+
void routeTo(route);
488+
}
489+
399490
get skipTables() {
400491
let value = true;
401492
for (const val of Object.values(this.searchables)) {
@@ -500,7 +591,7 @@ export class Search {
500591
}
501592

502593
_searchSuggestions(input: string): SearchItems {
503-
const matches: { si: SearchItem | DocSearchItem; distance: number }[] = [];
594+
const matches: { si: SearchItems[number]; distance: number }[] = [];
504595

505596
for (const si of this._intermediate.suggestions) {
506597
const label = si.label;
@@ -571,9 +662,21 @@ export class Search {
571662

572663
keys.sort((a, b) => safeParseFloat(b) - safeParseFloat(a));
573664
const array: SearchItems = [];
665+
666+
const showRecent =
667+
!input ||
668+
input.startsWith('#') ||
669+
input.toLowerCase().startsWith('recent');
670+
if (showRecent && this.filters.groupFilters.Recent) {
671+
const recentSearchTerm = input?.replace(/^#|recent/gi, '').trim();
672+
const recentItems = this.getRecentItems(recentSearchTerm);
673+
if (recentItems.length > 0) {
674+
array.push(...recentItems);
675+
}
676+
}
677+
574678
for (const key of keys) {
575679
const keywords = groupedKeywords[key] ?? [];
576-
577680
this._pushDocSearchItems(keywords, array, input);
578681
if (key === '0') {
579682
this._pushNonDocSearchItems(array, input);
@@ -609,8 +712,7 @@ export class Search {
609712
items: (SearchItem | Keyword)[],
610713
input?: string
611714
): SearchItems {
612-
const subArray: { item: SearchItem | DocSearchItem; distance: number }[] =
613-
[];
715+
const subArray: { item: SearchItems[number]; distance: number }[] = [];
614716

615717
for (const item of items) {
616718
const subArrayItem = this._getSubArrayItem(item, input);
@@ -625,7 +727,10 @@ export class Search {
625727
return subArray.map(({ item }) => item);
626728
}
627729

628-
_getSubArrayItem(item: SearchItem | Keyword, input?: string) {
730+
_getSubArrayItem(
731+
item: SearchItem | Keyword,
732+
input?: string
733+
): { item: SearchItems[number]; distance: number } | null {
629734
if (isSearchItem(item)) {
630735
return this._getSubArrayItemFromSearchItem(item, input);
631736
}

src/utils/ui.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ export function getActionsForDoc(doc?: Doc): Action[] {
192192
const actions: Action[] = [
193193
...getActions(doc),
194194
getDuplicateAction(doc),
195+
getNewAction(doc),
195196
getDeleteAction(doc),
196197
getCancelAction(doc),
197198
];
@@ -290,6 +291,21 @@ function getDuplicateAction(doc: Doc): Action {
290291
};
291292
}
292293

294+
function getNewAction(doc: Doc): Action {
295+
return {
296+
label: t`New Entry`,
297+
group: t`Create`,
298+
async action() {
299+
try {
300+
const newDoc = fyo.doc.getNewDoc(doc.schemaName);
301+
await openEdit(newDoc);
302+
} catch (err) {
303+
await handleErrorWithDialog(err as Error, doc);
304+
}
305+
},
306+
};
307+
}
308+
293309
export function getFieldsGroupedByTabAndSection(
294310
schema: Schema,
295311
doc: Doc

utils/types.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,21 @@ interface ModMap {
7979
export interface ConfigFilesWithModified extends ConfigFile {
8080
modified: string;
8181
}
82+
83+
export const searchGroups = [
84+
'Docs',
85+
'List',
86+
'Create',
87+
'Report',
88+
'Page',
89+
'Recent',
90+
] as const;
91+
92+
export type SearchGroup = typeof searchGroups[number];
93+
94+
export interface SearchItem {
95+
label: string;
96+
group: Exclude<SearchGroup, 'Docs' | 'Recent'>;
97+
route?: string;
98+
action?: () => void | Promise<void>;
99+
}

0 commit comments

Comments
 (0)