namirasoft-site-react 1.4.535 → 1.4.537

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.
@@ -15,50 +15,46 @@ export class NSBoxDateTimeRange extends NSBoxDateRangeBase<NSBoxDateTimeRangePro
15
15
 
16
16
  getError(): string | null
17
17
  {
18
- const fmt = this.getFormat();
19
- const { title, required } = this.props;
18
+ const { title, required, minDate, maxDate } = this.props;
20
19
  const { value } = this.state;
21
20
  if (required && (!value || !value.from || !value.to))
22
21
  return `${title} is required.`;
23
- const minDate = this.props.minDate ? TimeOperation.format(this.props.minDate, fmt) : undefined;
24
- const maxDate = this.props.maxDate ? TimeOperation.format(this.props.maxDate, fmt) : undefined;
25
22
  if (minDate && value?.from && value.from < minDate)
26
- return `${title} (from) must not be before ${minDate}.`;
23
+ return `${title} (from) must not be before ${TimeOperation.format(minDate, this.getFormat())}.`;
27
24
  if (maxDate && value?.to && value.to > maxDate)
28
- return `${title} (to) must not be after ${maxDate}.`;
25
+ return `${title} (to) must not be after ${TimeOperation.format(maxDate, this.getFormat())}.`;
29
26
  return null;
30
27
  }
31
28
 
32
29
  protected getBuiltinPresets()
33
30
  {
34
- const fmt = this.getFormat();
35
31
  const now = new Date();
36
32
  now.setSeconds(0, 0);
37
- const to = TimeOperation.format(now, fmt);
38
33
  return [
39
- { label: '1H', title: 'Last 1 Hour', from: TimeOperation.format(TimeOperation.hoursAgo(1, now), fmt), to },
40
- { label: '6H', title: 'Last 6 Hours', from: TimeOperation.format(TimeOperation.hoursAgo(6, now), fmt), to },
41
- { label: '12H', title: 'Last 12 Hours', from: TimeOperation.format(TimeOperation.hoursAgo(12, now), fmt), to },
42
- { label: '1D', title: 'Last 1 Day', from: TimeOperation.format(TimeOperation.daysAgo(1, now), fmt), to },
43
- { label: '1W', title: 'Last 1 Week', from: TimeOperation.format(TimeOperation.weeksAgo(1, now), fmt), to },
34
+ { label: '1H', title: 'Last 1 Hour', from: TimeOperation.hoursAgo(1, now), to: now },
35
+ { label: '6H', title: 'Last 6 Hours', from: TimeOperation.hoursAgo(6, now), to: now },
36
+ { label: '12H', title: 'Last 12 Hours', from: TimeOperation.hoursAgo(12, now), to: now },
37
+ { label: '1D', title: 'Last 1 Day', from: TimeOperation.daysAgo(1, now), to: now },
38
+ { label: '1W', title: 'Last 1 Week', from: TimeOperation.weeksAgo(1, now), to: now },
44
39
  ];
45
40
  }
46
41
 
47
42
  protected renderPicker()
48
43
  {
44
+ const fmt = this.getFormat();
49
45
  return (
50
46
  <DatePicker.RangePicker
51
47
  className={`${StylesNSBox.ns_box_input} ${Styles.ns_picker}`}
52
48
  variant="borderless"
53
49
  showTime
54
50
  value={this.toDayjsValue()}
55
- format={this.getFormat()}
51
+ format={fmt}
56
52
  minDate={this.props.minDate ? dayjs(this.props.minDate) : undefined}
57
53
  maxDate={this.props.maxDate ? dayjs(this.props.maxDate) : undefined}
58
54
  onChange={(_, dateStrings) =>
59
55
  {
60
56
  if (dateStrings?.[0] && dateStrings?.[1])
61
- this.setValue({ from: dateStrings[0], to: dateStrings[1] });
57
+ this.setValue({ from: dayjs(dateStrings[0], fmt).toDate(), to: dayjs(dateStrings[1], fmt).toDate() });
62
58
  else
63
59
  this.setValue(null);
64
60
  }}
@@ -4,21 +4,53 @@ import { TimePicker } from 'antd';
4
4
  import type { Dayjs } from 'dayjs';
5
5
  import dayjs from 'dayjs';
6
6
  import { TimeOperation } from 'namirasoft-core';
7
+ import { Component, createRef } from 'react';
8
+ import { IBaseComponentProps } from '../props/IBaseComponentProps';
9
+ import { IValidationProps } from '../props/IValidationProps';
7
10
  import { Validator } from '../Validator';
11
+ import { INSBox } from './INSBox';
8
12
  import StylesNSBox from './NSBox.module.css';
9
- import { NSBoxDateRangeBase, NSBoxDateRangeBaseProps } from './NSBoxDateRangeBase';
13
+ import { INSBoxBaseLayoutProps, NSBoxBaseLayout } from './NSBoxBaseLayout';
10
14
  import Styles from './NSBoxDateRangeBase.module.css';
15
+ import { NSBoxDateRangePreset } from './NSBoxDateRangeBase';
16
+ import { safeMenuMenuItem } from './NSMenuButton';
11
17
 
12
- export interface NSBoxTimeRangeProps extends NSBoxDateRangeBaseProps<NSBoxTimeRange>
18
+ export interface NSBoxTimeRangeValue
13
19
  {
20
+ from: string;
21
+ to: string;
22
+ }
23
+
24
+ export interface NSBoxTimeRangeProps extends IBaseComponentProps, IValidationProps, INSBoxBaseLayoutProps<NSBoxTimeRange, NSBoxTimeRangeValue>
25
+ {
26
+ preset?: boolean;
27
+ presets?: NSBoxDateRangePreset[];
14
28
  maxRangeSeconds?: number;
15
29
  }
16
30
 
17
- export class NSBoxTimeRange extends NSBoxDateRangeBase<NSBoxTimeRangeProps>
31
+ export interface NSBoxTimeRangeState
18
32
  {
19
- protected getFormat()
33
+ value: NSBoxTimeRangeValue | null;
34
+ }
35
+
36
+ export class NSBoxTimeRange extends Component<NSBoxTimeRangeProps, NSBoxTimeRangeState> implements INSBox
37
+ {
38
+ NSBoxBaseLayout_Main = createRef<NSBoxBaseLayout>();
39
+ private static readonly FORMAT = 'HH:mm:ss';
40
+
41
+ constructor(props: NSBoxTimeRangeProps)
42
+ {
43
+ super(props);
44
+ this.state = { value: props.defaultValue ?? null };
45
+ this.isEmpty = this.isEmpty.bind(this);
46
+ this.getError = this.getError.bind(this);
47
+ this.getValue = this.getValue.bind(this);
48
+ this.setValue = this.setValue.bind(this);
49
+ }
50
+
51
+ isEmpty(value: NSBoxTimeRangeValue): boolean
20
52
  {
21
- return 'HH:mm:ss';
53
+ return !value || !value.from || !value.to;
22
54
  }
23
55
 
24
56
  getError(): string | null
@@ -29,12 +61,10 @@ export class NSBoxTimeRange extends NSBoxDateRangeBase<NSBoxTimeRangeProps>
29
61
  return `${title} is required.`;
30
62
 
31
63
  const fromErr = Validator.getErrorTime(title, value?.from, false);
32
- if (fromErr)
33
- return fromErr;
64
+ if (fromErr) return fromErr;
34
65
 
35
66
  const toErr = Validator.getErrorTime(title, value?.to, false);
36
- if (toErr)
37
- return toErr;
67
+ if (toErr) return toErr;
38
68
 
39
69
  if (maxRangeSeconds && value?.from && value?.to)
40
70
  {
@@ -50,9 +80,40 @@ export class NSBoxTimeRange extends NSBoxDateRangeBase<NSBoxTimeRangeProps>
50
80
  return null;
51
81
  }
52
82
 
53
- protected getBuiltinPresets()
83
+ getValue(checkError: boolean = true): NSBoxTimeRangeValue | null
84
+ {
85
+ if (this.props.nullable)
86
+ if (this.NSBoxBaseLayout_Main.current?.isNull())
87
+ return null;
88
+ return NSBoxBaseLayout.checkGetValue(this, checkError, () => this.state.value);
89
+ }
90
+
91
+ setValue(value: NSBoxTimeRangeValue | null, callback?: () => void): void
92
+ {
93
+ if (this.props.nullable)
94
+ this.NSBoxBaseLayout_Main.current?.setNull(value == null);
95
+ this.setState({ value }, () =>
96
+ {
97
+ this.props.onChanged?.(this);
98
+ callback?.();
99
+ });
100
+ }
101
+
102
+ setDisabled(disabled: boolean): void
103
+ {
104
+ this.NSBoxBaseLayout_Main.current?.setDisabled(disabled);
105
+ }
106
+
107
+ private toDayjsValue(): [Dayjs, Dayjs] | null
54
108
  {
55
- const fmt = this.getFormat();
109
+ const { value } = this.state;
110
+ if (!value?.from || !value?.to) return null;
111
+ return [dayjs(value.from, NSBoxTimeRange.FORMAT), dayjs(value.to, NSBoxTimeRange.FORMAT)];
112
+ }
113
+
114
+ private getBuiltinPresets(): { label: string; title: string; from: string; to: string }[]
115
+ {
116
+ const fmt = NSBoxTimeRange.FORMAT;
56
117
  const now = new Date();
57
118
  now.setSeconds(0, 0);
58
119
  const to = TimeOperation.format(now, fmt);
@@ -65,30 +126,94 @@ export class NSBoxTimeRange extends NSBoxDateRangeBase<NSBoxTimeRangeProps>
65
126
  ];
66
127
  }
67
128
 
68
- protected override toDayjsValue(): [Dayjs, Dayjs] | null
129
+ private renderPresets(): React.ReactNode
69
130
  {
70
- const { value } = this.state;
71
- if (!value?.from || !value?.to)
72
- return null;
73
- return [dayjs(value.from, 'HH:mm:ss'), dayjs(value.to, 'HH:mm:ss')];
131
+ const { preset, presets: customPresets } = this.props;
132
+ const fmt = NSBoxTimeRange.FORMAT;
133
+
134
+ const builtinPresets = preset ? this.getBuiltinPresets() : [];
135
+
136
+ const resolvedCustom = (customPresets ?? []).map(p => ({
137
+ shortName: p.shortName,
138
+ title: p.title,
139
+ from: TimeOperation.format(p.getFrom(), fmt),
140
+ to: TimeOperation.format(p.getTo(), fmt),
141
+ }));
142
+
143
+ const showDivider = builtinPresets.length > 0 && resolvedCustom.length > 0;
144
+
145
+ return (
146
+ <div className={Styles.ns_preset_container}>
147
+ {builtinPresets.map(p =>
148
+ {
149
+ const isActive = this.state.value?.from === p.from && this.state.value?.to === p.to;
150
+ return (
151
+ <button
152
+ key={p.label}
153
+ type="button"
154
+ title={p.title}
155
+ className={`${Styles.ns_preset_btn} ${isActive ? Styles.ns_preset_btn_active : ''}`}
156
+ onClick={() => this.setValue({ from: p.from, to: p.to })}
157
+ >
158
+ {p.label}
159
+ </button>
160
+ );
161
+ })}
162
+ {showDivider && <div className={Styles.ns_preset_divider} />}
163
+ {resolvedCustom.map(p =>
164
+ {
165
+ const isActive = this.state.value?.from === p.from && this.state.value?.to === p.to;
166
+ return (
167
+ <button
168
+ key={p.shortName}
169
+ type="button"
170
+ title={p.title}
171
+ className={`${Styles.ns_preset_btn} ${isActive ? Styles.ns_preset_btn_active : ''}`}
172
+ onClick={() => this.setValue({ from: p.from, to: p.to })}
173
+ >
174
+ {p.shortName}
175
+ </button>
176
+ );
177
+ })}
178
+ </div>
179
+ );
74
180
  }
75
181
 
76
- protected renderPicker()
182
+ override render()
77
183
  {
184
+ let menu = safeMenuMenuItem(this.props, () => { });
185
+ if (!menu.builtin.copy)
186
+ menu.builtin.copy = {
187
+ enabled: true,
188
+ getValue: () => this.state.value ? `${this.state.value.from} to ${this.state.value.to}` : '',
189
+ };
190
+
191
+ const showPresets = this.props.preset || (this.props.presets && this.props.presets.length > 0);
192
+
78
193
  return (
79
- <TimePicker.RangePicker
80
- className={`${StylesNSBox.ns_box_input} ${Styles.ns_picker}`}
81
- variant="borderless"
82
- value={this.toDayjsValue()}
83
- format={this.getFormat()}
84
- onChange={(_, timeStrings) =>
85
- {
86
- if (timeStrings?.[0] && timeStrings?.[1])
87
- this.setValue({ from: timeStrings[0], to: timeStrings[1] });
88
- else
89
- this.setValue(null);
90
- }}
91
- />
194
+ <NSBoxBaseLayout
195
+ ref={this.NSBoxBaseLayout_Main}
196
+ {...this.props}
197
+ menu={menu}
198
+ getValue={() => this.state.value ? `${this.state.value.from} - ${this.state.value.to}` : null}
199
+ >
200
+ <div className={Styles.ns_wrapper}>
201
+ <TimePicker.RangePicker
202
+ className={`${StylesNSBox.ns_box_input} ${Styles.ns_picker}`}
203
+ variant="borderless"
204
+ value={this.toDayjsValue()}
205
+ format={NSBoxTimeRange.FORMAT}
206
+ onChange={(_, timeStrings) =>
207
+ {
208
+ if (timeStrings?.[0] && timeStrings?.[1])
209
+ this.setValue({ from: timeStrings[0], to: timeStrings[1] });
210
+ else
211
+ this.setValue(null);
212
+ }}
213
+ />
214
+ </div>
215
+ {showPresets && this.renderPresets()}
216
+ </NSBoxBaseLayout>
92
217
  );
93
218
  }
94
219
  }
@@ -219,6 +219,17 @@ export class NSTable<RowType> extends Component<NSTableProps<RowType>, NSTableSt
219
219
  {
220
220
  let columns: TableColumnInfo[] = [];
221
221
  this.foreachColumn(visible, c => columns.push(c));
222
+ if (visible === true)
223
+ {
224
+ let vs = this.getVisibleColumns();
225
+ if (vs.length > 0)
226
+ columns.sort((a, b) =>
227
+ {
228
+ let ai = vs.findIndex(v => v.table === a.table.name && v.column === a.name);
229
+ let bi = vs.findIndex(v => v.table === b.table.name && v.column === b.name);
230
+ return ai - bi;
231
+ });
232
+ }
222
233
  return columns;
223
234
  }
224
235
  private showModal(description: string, title?: string)