ngx-column-filter-popup 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,236 @@
1
+ import { FilterRule, FilterConfig, GlobalMatchMode, FieldType } from '../models/filter.models';
2
+
3
+ /**
4
+ * Apply column filter rules to a dataset
5
+ * @param data - Array of objects to filter
6
+ * @param columnKey - The property name to filter on
7
+ * @param filterConfig - The filter configuration containing rules
8
+ * @returns Filtered array
9
+ */
10
+ export function applyColumnFilter<T extends Record<string, any>>(
11
+ data: T[],
12
+ columnKey: string,
13
+ filterConfig: FilterConfig | null | undefined
14
+ ): T[] {
15
+ if (!filterConfig || !filterConfig.rules || filterConfig.rules.length === 0) {
16
+ return data;
17
+ }
18
+
19
+ return data.filter(item => itemMatchesFilter(item, columnKey, filterConfig));
20
+ }
21
+
22
+ /**
23
+ * Check if a single item matches the filter rules
24
+ * @param item - The item to check
25
+ * @param columnKey - The property name to filter on
26
+ * @param filterConfig - The filter configuration containing rules
27
+ * @returns true if the item matches the filter
28
+ */
29
+ export function itemMatchesFilter<T extends Record<string, any>>(
30
+ item: T,
31
+ columnKey: string,
32
+ filterConfig: FilterConfig
33
+ ): boolean {
34
+ if (!filterConfig || !filterConfig.rules || filterConfig.rules.length === 0) {
35
+ return true;
36
+ }
37
+
38
+ const value = item[columnKey];
39
+ if (value === null || value === undefined) {
40
+ return false;
41
+ }
42
+
43
+ // Filter out empty rules
44
+ const validRules = filterConfig.rules.filter(rule => {
45
+ if (filterConfig.fieldType === 'status') {
46
+ return rule.value !== '';
47
+ }
48
+ return rule.value.trim() !== '';
49
+ });
50
+
51
+ if (validRules.length === 0) {
52
+ return true;
53
+ }
54
+
55
+ const fieldType = filterConfig.fieldType || 'text';
56
+
57
+ // Check if rule matches based on field type
58
+ const checkRuleMatch = (rule: FilterRule): boolean => {
59
+ if (fieldType === 'currency') {
60
+ return checkCurrencyMatch(value, rule);
61
+ } else if (fieldType === 'age') {
62
+ return checkAgeMatch(value, rule);
63
+ } else if (fieldType === 'date') {
64
+ return checkDateMatch(value, rule);
65
+ } else if (fieldType === 'status') {
66
+ return checkStatusMatch(value, rule);
67
+ } else {
68
+ return checkTextMatch(value, rule);
69
+ }
70
+ };
71
+
72
+ // Get global match mode (default to 'match-any-rule' for backward compatibility)
73
+ const globalMatchMode: GlobalMatchMode = filterConfig.globalMatchMode || 'match-any-rule';
74
+
75
+ // Apply global match mode
76
+ if (globalMatchMode === 'match-all-rules') {
77
+ // ALL rules must match (AND logic)
78
+ return validRules.every(rule => checkRuleMatch(rule));
79
+ } else {
80
+ // ANY rule can match (OR logic)
81
+ return validRules.some(rule => checkRuleMatch(rule));
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Check text match based on match type
87
+ */
88
+ function checkTextMatch(value: any, rule: FilterRule): boolean {
89
+ const stringValue = String(value).toLowerCase();
90
+ const ruleValue = rule.value.trim().toLowerCase();
91
+
92
+ switch (rule.matchType) {
93
+ case 'match-all':
94
+ const words = ruleValue.split(/\s+/).filter(w => w.length > 0);
95
+ return words.length > 0 && words.every(word => stringValue.includes(word));
96
+
97
+ case 'match-any':
98
+ const anyWords = ruleValue.split(/\s+/).filter(w => w.length > 0);
99
+ return anyWords.length > 0 && anyWords.some(word => stringValue.includes(word));
100
+
101
+ case 'starts-with':
102
+ return stringValue.startsWith(ruleValue);
103
+
104
+ case 'ends-with':
105
+ return stringValue.endsWith(ruleValue);
106
+
107
+ case 'contains':
108
+ return stringValue.includes(ruleValue);
109
+
110
+ case 'equals':
111
+ return stringValue === ruleValue;
112
+
113
+ default:
114
+ return false;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Check currency/number match
120
+ */
121
+ function checkCurrencyMatch(value: any, rule: FilterRule): boolean {
122
+ // Parse currency values (remove currency symbols and commas)
123
+ const numValue = parseFloat(String(value).replace(/[^0-9.-]/g, '')) || 0;
124
+ const ruleValue = parseFloat(rule.value.replace(/[^0-9.-]/g, '')) || 0;
125
+
126
+ if (isNaN(numValue) || isNaN(ruleValue)) {
127
+ return false;
128
+ }
129
+
130
+ switch (rule.matchType) {
131
+ case 'equals':
132
+ return numValue === ruleValue;
133
+
134
+ case 'greater-than':
135
+ return numValue > ruleValue;
136
+
137
+ case 'less-than':
138
+ return numValue < ruleValue;
139
+
140
+ case 'greater-equal':
141
+ return numValue >= ruleValue;
142
+
143
+ case 'less-equal':
144
+ return numValue <= ruleValue;
145
+
146
+ default:
147
+ return false;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Check age/number match (same logic as currency but simpler parsing)
153
+ */
154
+ function checkAgeMatch(value: any, rule: FilterRule): boolean {
155
+ // Parse number values
156
+ const numValue = parseFloat(String(value)) || 0;
157
+ const ruleValue = parseFloat(rule.value) || 0;
158
+
159
+ if (isNaN(numValue) || isNaN(ruleValue)) {
160
+ return false;
161
+ }
162
+
163
+ switch (rule.matchType) {
164
+ case 'equals':
165
+ return numValue === ruleValue;
166
+
167
+ case 'greater-than':
168
+ return numValue > ruleValue;
169
+
170
+ case 'less-than':
171
+ return numValue < ruleValue;
172
+
173
+ case 'greater-equal':
174
+ return numValue >= ruleValue;
175
+
176
+ case 'less-equal':
177
+ return numValue <= ruleValue;
178
+
179
+ default:
180
+ return false;
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Check date match
186
+ */
187
+ function checkDateMatch(value: any, rule: FilterRule): boolean {
188
+ const dateValue = new Date(value);
189
+ const ruleDate = new Date(rule.value);
190
+
191
+ if (isNaN(dateValue.getTime()) || isNaN(ruleDate.getTime())) {
192
+ return false;
193
+ }
194
+
195
+ // Normalize dates to midnight for comparison
196
+ const normalizedValue = new Date(dateValue.getFullYear(), dateValue.getMonth(), dateValue.getDate());
197
+ const normalizedRule = new Date(ruleDate.getFullYear(), ruleDate.getMonth(), ruleDate.getDate());
198
+
199
+ switch (rule.matchType) {
200
+ case 'is':
201
+ case 'is-on':
202
+ return normalizedValue.getTime() === normalizedRule.getTime();
203
+
204
+ case 'is-not':
205
+ return normalizedValue.getTime() !== normalizedRule.getTime();
206
+
207
+ case 'is-before':
208
+ return normalizedValue.getTime() < normalizedRule.getTime();
209
+
210
+ case 'is-after':
211
+ return normalizedValue.getTime() > normalizedRule.getTime();
212
+
213
+ default:
214
+ return false;
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Check status match
220
+ */
221
+ function checkStatusMatch(value: any, rule: FilterRule): boolean {
222
+ const stringValue = String(value).toLowerCase().trim();
223
+ const ruleValue = rule.value.toLowerCase().trim();
224
+
225
+ switch (rule.matchType) {
226
+ case 'is':
227
+ case 'equals':
228
+ return stringValue === ruleValue;
229
+
230
+ case 'is-not':
231
+ return stringValue !== ruleValue;
232
+
233
+ default:
234
+ return stringValue === ruleValue;
235
+ }
236
+ }