create-nexwire 4.2.4 → 4.3.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.
- package/README.md +20 -8
- package/express/.env +1 -1
- package/express/.vscode/extensions.json +6 -0
- package/express/.vscode/settings.json +37 -26
- package/express/biome.json +99 -66
- package/express/package.json +31 -29
- package/express/src/config/database.ts +48 -29
- package/express/src/config/index.ts +83 -83
- package/express/src/config/openapi.ts +6 -24
- package/express/src/config/queue.ts +30 -30
- package/express/src/config/validate.ts +50 -40
- package/express/src/database/migrations/sqlite/0000_init.sql +47 -47
- package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
- package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
- package/express/src/database/schema.ts +2 -2
- package/express/src/framework/cache/cache.ts +40 -8
- package/express/src/framework/circuit-breaker/cb.ts +88 -60
- package/express/src/framework/database/connection.ts +52 -27
- package/express/src/framework/events/dispatcher.ts +61 -49
- package/express/src/framework/facade.ts +46 -3
- package/express/src/framework/http/app.ts +28 -22
- package/express/src/framework/http/logger.ts +27 -16
- package/express/src/framework/http/openapi.ts +9 -11
- package/express/src/framework/modules/routes.ts +26 -21
- package/express/src/framework/notification/channels/broadcast.ts +15 -0
- package/express/src/framework/notification/channels/mail.ts +40 -0
- package/express/src/framework/notification/contracts.ts +29 -0
- package/express/src/framework/notification/index.ts +19 -94
- package/express/src/framework/notification/registry.ts +15 -0
- package/express/src/framework/providers/adapter.ts +27 -17
- package/express/src/framework/queue/queue.ts +43 -44
- package/express/src/framework/realtime/broadcast.ts +29 -5
- package/express/src/framework/realtime/socket.ts +149 -119
- package/express/src/framework/redis/client.ts +148 -151
- package/express/src/framework/runtime/server-adapter.ts +39 -34
- package/express/src/framework/scheduler/lock.ts +128 -116
- package/express/src/framework/server.ts +33 -49
- package/express/src/framework/session/session.ts +69 -14
- package/express/src/framework/storage/storage.ts +744 -785
- package/express/src/framework/support/mail.ts +15 -7
- package/express/src/providers/notification.ts +4 -0
- package/express/src/resources/src/components/Checkbox.vue +32 -29
- package/express/src/resources/src/components/Input.vue +243 -223
- package/express/src/resources/src/components/InputGroup.vue +147 -145
- package/express/src/resources/src/components/InputPasswordToggle.vue +229 -213
- package/express/src/resources/src/components/Modal.vue +71 -55
- package/express/src/resources/src/components/Refresh.vue +19 -0
- package/express/src/resources/src/helpers/assets.ts +51 -0
- package/express/src/resources/src/layouts/Layout/Header.vue +124 -105
- package/express/src/resources/src/main.ts +3 -1
- package/express/src/resources/src/plugins/gum.ts +1 -1
- package/express/src/resources/src/plugins/pulse.ts +157 -147
- package/express/src/resources/src/stores/auth.ts +184 -164
- package/express/src/resources/vite.config.ts +54 -36
- package/express/src/storage/database/nexwire.sqlite +0 -0
- package/express/src/storage/logs/app.log +0 -0
- package/express/src/storage/logs/fatal.log +0 -0
- package/express/src/types/global.d.ts +32 -26
- package/hono/.env +1 -1
- package/hono/.vscode/extensions.json +6 -0
- package/hono/.vscode/settings.json +37 -26
- package/hono/biome.json +25 -2
- package/hono/package.json +25 -23
- package/hono/src/config/database.ts +48 -29
- package/hono/src/config/index.ts +83 -83
- package/hono/src/config/openapi.ts +6 -24
- package/hono/src/config/queue.ts +30 -30
- package/hono/src/config/validate.ts +50 -40
- package/hono/src/database/migrations/sqlite/0000_init.sql +47 -47
- package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
- package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
- package/hono/src/database/schema.ts +2 -2
- package/hono/src/framework/cache/cache.ts +40 -8
- package/hono/src/framework/circuit-breaker/cb.ts +88 -60
- package/hono/src/framework/database/connection.ts +52 -27
- package/hono/src/framework/events/dispatcher.ts +61 -49
- package/hono/src/framework/facade.ts +38 -1
- package/hono/src/framework/http/openapi.ts +7 -9
- package/hono/src/framework/notification/channels/broadcast.ts +15 -0
- package/hono/src/framework/notification/channels/mail.ts +40 -0
- package/hono/src/framework/notification/contracts.ts +29 -0
- package/hono/src/framework/notification/index.ts +19 -94
- package/hono/src/framework/notification/registry.ts +15 -0
- package/hono/src/framework/providers/adapter.ts +27 -17
- package/hono/src/framework/queue/queue.ts +43 -44
- package/hono/src/framework/realtime/broadcast.ts +29 -5
- package/hono/src/framework/realtime/socket.ts +217 -196
- package/hono/src/framework/redis/client.ts +148 -151
- package/hono/src/framework/scheduler/lock.ts +128 -116
- package/hono/src/framework/server.ts +27 -29
- package/hono/src/framework/session/session.ts +62 -14
- package/hono/src/framework/storage/storage.ts +744 -785
- package/hono/src/framework/support/mail.ts +56 -48
- package/hono/src/providers/notification.ts +4 -0
- package/hono/src/resources/src/components/Checkbox.vue +32 -29
- package/hono/src/resources/src/components/Input.vue +243 -234
- package/hono/src/resources/src/components/InputGroup.vue +147 -149
- package/hono/src/resources/src/components/InputPasswordToggle.vue +229 -222
- package/hono/src/resources/src/components/Modal.vue +71 -55
- package/hono/src/resources/src/components/Refresh.vue +19 -0
- package/hono/src/resources/src/helpers/assets.ts +51 -0
- package/hono/src/resources/src/layouts/Layout/Header.vue +124 -105
- package/hono/src/resources/src/main.ts +3 -1
- package/hono/src/resources/src/plugins/gum.ts +1 -1
- package/hono/src/resources/src/plugins/pulse.ts +157 -147
- package/hono/src/resources/src/stores/auth.ts +184 -164
- package/hono/src/resources/vite.config.ts +54 -36
- package/hono/src/storage/database/nexwire.sqlite +0 -0
- package/hono/src/storage/logs/app.log +0 -0
- package/hono/src/storage/logs/fatal.log +0 -0
- package/hono/src/types/global.d.ts +32 -26
- package/package.json +2 -2
- package/src/index.js +139 -14
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import nodemailer from "nodemailer";
|
|
2
2
|
import { mailConfig } from "@/config/index.js";
|
|
3
|
-
import {
|
|
3
|
+
import { createCircuitBreaker, isCircuitBreakerError } from "@/framework/circuit-breaker/cb.js";
|
|
4
4
|
import { logger } from "@/framework/support/logger.js";
|
|
5
5
|
|
|
6
6
|
type MailPayload = {
|
|
@@ -17,7 +17,7 @@ const transport = nodemailer.createTransport({
|
|
|
17
17
|
auth: mailConfig.username ? { user: mailConfig.username, pass: mailConfig.password } : undefined
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
const mailBreaker =
|
|
20
|
+
const mailBreaker = createCircuitBreaker({ name: "SMTP Mail" });
|
|
21
21
|
|
|
22
22
|
export const mail = {
|
|
23
23
|
/**
|
|
@@ -35,11 +35,19 @@ export const mail = {
|
|
|
35
35
|
})
|
|
36
36
|
);
|
|
37
37
|
} catch (error) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
// Distinguish between a Circuit Breaker rejection and an actual SMTP failure
|
|
39
|
+
if (isCircuitBreakerError(error)) {
|
|
40
|
+
logger.warn("Mail send skipped: Circuit breaker is open", {
|
|
41
|
+
to: payload.to,
|
|
42
|
+
subject: payload.subject
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
logger.error("Mail send failed", {
|
|
46
|
+
to: payload.to,
|
|
47
|
+
subject: payload.subject,
|
|
48
|
+
error: error instanceof Error ? error.message : error
|
|
49
|
+
});
|
|
50
|
+
}
|
|
43
51
|
|
|
44
52
|
if (!mailConfig.failSilent) throw error;
|
|
45
53
|
return null;
|
|
@@ -1,29 +1,32 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div class="form-check mb-0" :class="props.topclass">
|
|
3
|
-
<input
|
|
4
|
-
:id="String(props.id)"
|
|
5
|
-
v-model="model"
|
|
6
|
-
class="form-check-input shadow-none
|
|
7
|
-
type="checkbox"
|
|
8
|
-
v-bind="$attrs"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<div class="form-check mb-0" :class="props.topclass">
|
|
3
|
+
<input
|
|
4
|
+
:id="String(props.id)"
|
|
5
|
+
v-model="model"
|
|
6
|
+
class="form-check-input shadow-none"
|
|
7
|
+
type="checkbox"
|
|
8
|
+
v-bind="$attrs"
|
|
9
|
+
/>
|
|
10
|
+
<label v-if="props.label" class="form-check-label" :for="String(props.id)">{{
|
|
11
|
+
props.label
|
|
12
|
+
}}</label>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
defineOptions({ name: "Checkbox", inheritAttrs: false });
|
|
18
|
+
|
|
19
|
+
interface CheckboxProps {
|
|
20
|
+
id?: string | number;
|
|
21
|
+
label?: string;
|
|
22
|
+
topclass?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const props = defineProps<CheckboxProps>();
|
|
26
|
+
|
|
27
|
+
type CheckboxModel = boolean | Array<string | number>;
|
|
28
|
+
|
|
29
|
+
const model = defineModel<CheckboxModel>({ default: false });
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<style lang="scss" scoped></style>
|
|
@@ -1,223 +1,243 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div :class="topclass">
|
|
3
|
-
<div class="position-relative">
|
|
4
|
-
<label
|
|
5
|
-
:for="inputId"
|
|
6
|
-
class="d-flex align-items-center mb-1"
|
|
7
|
-
:class="{ 'd-none': !inputLabel }"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
<span
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="topclass">
|
|
3
|
+
<div class="position-relative">
|
|
4
|
+
<label
|
|
5
|
+
:for="inputId"
|
|
6
|
+
class="d-flex align-items-center mb-1"
|
|
7
|
+
:class="{ 'd-none': !inputLabel }"
|
|
8
|
+
>
|
|
9
|
+
<div class="position-relative">
|
|
10
|
+
<span class="text-capitalize" v-html="inputLabel"></span>
|
|
11
|
+
<span
|
|
12
|
+
class="position-absolute text-danger rounded-circle bg-danger must"
|
|
13
|
+
:class="{ 'd-none': !props.must }"
|
|
14
|
+
></span>
|
|
15
|
+
</div>
|
|
16
|
+
<div
|
|
17
|
+
class="text-end ms-auto text-primary fw-semibold"
|
|
18
|
+
:class="{ 'd-none': !props.hood }"
|
|
19
|
+
style="font-size: 12px; margin-top: 0.15rem"
|
|
20
|
+
v-html="hoodHtml"
|
|
21
|
+
></div>
|
|
22
|
+
</label>
|
|
23
|
+
<input
|
|
24
|
+
ref="inputRef"
|
|
25
|
+
class="form-control shadow-none"
|
|
26
|
+
:value="props.modelValue"
|
|
27
|
+
:maxlength="maxLengthAttr"
|
|
28
|
+
:readonly="props.readonly"
|
|
29
|
+
v-bind="$attrs"
|
|
30
|
+
:autocomplete="inputAutocomplete"
|
|
31
|
+
@input="updateModel"
|
|
32
|
+
/>
|
|
33
|
+
<div
|
|
34
|
+
class="position-absolute bottom-0 end-0 pe-1 text-secondary max"
|
|
35
|
+
:class="{ 'd-none': !max }"
|
|
36
|
+
>
|
|
37
|
+
{{ maxCounter }}
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
<div
|
|
41
|
+
:id="`${inputId}Help`"
|
|
42
|
+
class="form-text mt-1 text-danger"
|
|
43
|
+
:class="{ 'd-none': !props.err }"
|
|
44
|
+
>
|
|
45
|
+
{{ props.err }}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<script setup lang="ts">
|
|
51
|
+
import { computed, nextTick, onMounted, ref, useAttrs } from "vue";
|
|
52
|
+
import { browserDetect } from "@/plugins/browserDetect";
|
|
53
|
+
|
|
54
|
+
defineOptions({ name: "Input", inheritAttrs: false });
|
|
55
|
+
|
|
56
|
+
const { isFirefox } = browserDetect;
|
|
57
|
+
|
|
58
|
+
const $attrs = useAttrs();
|
|
59
|
+
const inputRef = ref<HTMLInputElement | null>(null);
|
|
60
|
+
type InputCategory =
|
|
61
|
+
| "number"
|
|
62
|
+
| "decimal"
|
|
63
|
+
| "+decimal-"
|
|
64
|
+
| "mobile"
|
|
65
|
+
| "tax-period"
|
|
66
|
+
| "bin"
|
|
67
|
+
| "challan-number"
|
|
68
|
+
| string;
|
|
69
|
+
|
|
70
|
+
interface InputProps {
|
|
71
|
+
focus?: boolean;
|
|
72
|
+
must?: boolean;
|
|
73
|
+
err?: string | boolean;
|
|
74
|
+
modelValue?: string | number;
|
|
75
|
+
category?: InputCategory;
|
|
76
|
+
hood?: string | boolean | number;
|
|
77
|
+
readonly?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const props = withDefaults(defineProps<InputProps>(), {
|
|
81
|
+
err: false,
|
|
82
|
+
modelValue: "",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const topclass = computed(() => ($attrs.topclass as string | undefined) || "mb-2");
|
|
86
|
+
const inputId = computed(() => ($attrs.id as string | undefined) || "");
|
|
87
|
+
const inputLabel = computed(() => ($attrs.label as string | undefined) || "");
|
|
88
|
+
const inputType = computed(() => ($attrs.type as string | undefined) || "text");
|
|
89
|
+
|
|
90
|
+
const max = computed<number | null>(() => {
|
|
91
|
+
const raw = $attrs.maxlength as string | number | undefined;
|
|
92
|
+
return raw != null ? Number(raw) : null;
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const inputAutocomplete = computed(() => {
|
|
96
|
+
const attrAutocomplete = $attrs.autocomplete as string | undefined;
|
|
97
|
+
if (attrAutocomplete) {
|
|
98
|
+
return attrAutocomplete;
|
|
99
|
+
}
|
|
100
|
+
return ["number", "decimal"].includes(props.category ?? "") ? "off" : "on";
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const maxLengthAttr = computed<number | undefined>(() => max.value ?? undefined);
|
|
104
|
+
|
|
105
|
+
const hoodHtml = computed(() =>
|
|
106
|
+
props.hood === false || props.hood == null ? "" : String(props.hood),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const maxCounter = computed(() => {
|
|
110
|
+
if (max.value == null) {
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!isFirefox && inputType.value === "month") {
|
|
115
|
+
return "";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return max.value - String(props.modelValue).length;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const emit = defineEmits<(event: "update:modelValue", value: string | number) => void>();
|
|
122
|
+
const updateModel = (e: Event) => {
|
|
123
|
+
const target = e.target as HTMLInputElement;
|
|
124
|
+
|
|
125
|
+
if (props.category === "number") {
|
|
126
|
+
emit("update:modelValue", (target.value = target.value.replace(/[^\d]/gi, "")));
|
|
127
|
+
} else if (props.category === "decimal") {
|
|
128
|
+
let val = target.value;
|
|
129
|
+
|
|
130
|
+
// Remove everything except digits, decimal point, and minus sign
|
|
131
|
+
val = val.replace(/[^0-9.]/g, "");
|
|
132
|
+
|
|
133
|
+
// Allow only one decimal point
|
|
134
|
+
const parts = val.split(".");
|
|
135
|
+
if (parts.length > 2) {
|
|
136
|
+
val = `${parts[0]}.${parts.slice(1).join("")}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
emit("update:modelValue", (target.value = val));
|
|
140
|
+
} else if (props.category === "+decimal-") {
|
|
141
|
+
let val = target.value;
|
|
142
|
+
|
|
143
|
+
// Remove everything except digits, decimal point, and minus sign
|
|
144
|
+
val = val.replace(/[^0-9.-]/g, ""); // if need minus sign
|
|
145
|
+
|
|
146
|
+
// Move minus sign to the front if it exists anywhere
|
|
147
|
+
const isNegative = val.includes("-");
|
|
148
|
+
val = val.replace(/-/g, ""); // remove all minus signs
|
|
149
|
+
if (isNegative) {
|
|
150
|
+
val = `-${val}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Allow only one decimal point
|
|
154
|
+
const parts = val.split(".");
|
|
155
|
+
if (parts.length > 2) {
|
|
156
|
+
val = `${parts[0]}.${parts.slice(1).join("")}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
emit("update:modelValue", (target.value = val));
|
|
160
|
+
} else if (props.category === "mobile") {
|
|
161
|
+
let digits = target.value.replace(/[^\d]/g, ""); // only digits
|
|
162
|
+
let val = digits;
|
|
163
|
+
if (target.value.includes("+")) {
|
|
164
|
+
val = `+${digits}`;
|
|
165
|
+
}
|
|
166
|
+
emit("update:modelValue", (target.value = val));
|
|
167
|
+
} else if (props.category === "tax-period") {
|
|
168
|
+
let val = target.value.replace(/[^\d-]/g, ""); // Only numbers and hyphen
|
|
169
|
+
val = val.replace(/-/g, ""); // Remove all hyphens
|
|
170
|
+
val = val.slice(0, 6); // Only 6 digits (YYYYMM)
|
|
171
|
+
|
|
172
|
+
if (val.length > 4) {
|
|
173
|
+
let year = val.slice(0, 4);
|
|
174
|
+
let month = val.slice(4, 6);
|
|
175
|
+
|
|
176
|
+
// Only correct if both month digits entered
|
|
177
|
+
if (month.length === 2) {
|
|
178
|
+
let m = parseInt(month, 10);
|
|
179
|
+
// Clamp month between 1 and 12
|
|
180
|
+
if (m < 1) m = 1;
|
|
181
|
+
if (m > 12) m = 12;
|
|
182
|
+
// Always pad to 2 digits
|
|
183
|
+
month = m.toString().padStart(2, "0");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
val = `${year}-${month}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
emit("update:modelValue", (target.value = val));
|
|
190
|
+
} else if (props.category === "bin") {
|
|
191
|
+
let digits = target.value.replace(/[^\d]/g, "");
|
|
192
|
+
digits = digits.slice(0, 14);
|
|
193
|
+
let formatted = digits;
|
|
194
|
+
if (digits.length > 9) {
|
|
195
|
+
formatted = `${digits.slice(0, 9)}-${digits.slice(9, 14)}`;
|
|
196
|
+
}
|
|
197
|
+
emit("update:modelValue", (target.value = formatted));
|
|
198
|
+
} else if (props.category === "echallan-number") {
|
|
199
|
+
// Format: V + 9 digits (V000517226)
|
|
200
|
+
let raw = (target.value || "").toUpperCase();
|
|
201
|
+
|
|
202
|
+
// keep only digits, but allow V (we’ll force V at start)
|
|
203
|
+
let digits = raw.replace(/[^\d]/g, "").slice(0, 9);
|
|
204
|
+
|
|
205
|
+
// always prefix V
|
|
206
|
+
let formatted = `V${digits}`;
|
|
207
|
+
|
|
208
|
+
emit("update:modelValue", (target.value = formatted));
|
|
209
|
+
} else if (props.category === "challan-number") {
|
|
210
|
+
let digits = target.value.replace(/[^\d]/g, "");
|
|
211
|
+
digits = digits.slice(0, 16);
|
|
212
|
+
let formatted = digits;
|
|
213
|
+
if (digits.length > 4) {
|
|
214
|
+
formatted = `${digits.slice(0, 4)}-${digits.slice(4, 16)}`;
|
|
215
|
+
}
|
|
216
|
+
emit("update:modelValue", (target.value = formatted));
|
|
217
|
+
} else {
|
|
218
|
+
emit("update:modelValue", target.value);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
onMounted(async () => {
|
|
223
|
+
const shouldFocus = props.focus !== false && props.focus !== undefined;
|
|
224
|
+
if (!shouldFocus) return;
|
|
225
|
+
|
|
226
|
+
await nextTick();
|
|
227
|
+
// preventScroll: focusing inside a modal must not scroll the modal/page
|
|
228
|
+
inputRef.value?.focus({ preventScroll: true });
|
|
229
|
+
});
|
|
230
|
+
</script>
|
|
231
|
+
|
|
232
|
+
<style lang="scss" scoped>
|
|
233
|
+
.must {
|
|
234
|
+
width: 4px;
|
|
235
|
+
height: 4px;
|
|
236
|
+
margin-left: 2px;
|
|
237
|
+
margin-top: 5px;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.max {
|
|
241
|
+
font-size: 0.6rem;
|
|
242
|
+
}
|
|
243
|
+
</style>
|