ui-soxo-bootstrap-core 2.4.25-dev.47 → 2.4.25-dev.49

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.
@@ -1,52 +1,63 @@
1
1
  # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2
2
  # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3
3
 
4
- name: Node.js Package
4
+ name: Publish Node Package (OIDC)
5
5
 
6
6
  on:
7
7
  release:
8
8
  types: [created]
9
9
 
10
+ permissions:
11
+ contents: read
12
+ id-token: write # REQUIRED for OIDC
13
+
10
14
  jobs:
11
- build:
15
+ publish:
12
16
  runs-on: ubuntu-latest
17
+
13
18
  steps:
14
- # 1. Checkout the repository
15
- - name: Checkout Code
16
- uses: actions/checkout@v3
19
+ # 1. Checkout the exact commit the release was created from
20
+ - name: Checkout repository
21
+ uses: actions/checkout@v4
17
22
 
18
- # 2. Setup Node.js and npm registry
23
+ # 2. Setup Node.js
24
+ # Node 22 = current LTS
19
25
  - name: Setup Node.js
20
- uses: actions/setup-node@v3
26
+ uses: actions/setup-node@v4
21
27
  with:
22
- node-version: 16
28
+ node-version: 22
23
29
  registry-url: https://registry.npmjs.org/
30
+ cache: npm
24
31
 
25
- # 3. Install dependencies using ci for reproducibility
26
- - name: Install Dependencies
32
+ # 3. Install dependencies deterministically
33
+ - name: Install dependencies
27
34
  run: npm ci
28
35
 
29
- # 4. Verify tag matches package.json version
30
- - name: Verify Tag Matches Version
36
+ # 4. Verify git tag matches package.json version
37
+ # This prevents publishing the wrong version
38
+ - name: Verify tag matches package.json version
31
39
  run: |
32
- TAG=${GITHUB_REF#refs/tags/}
33
- PACKAGE_VERSION=$(node -p "require('./package.json').version")
34
- echo "Release Tag: $TAG"
35
- echo "Package Version: $PACKAGE_VERSION"
36
- if [[ "$TAG" != "v$PACKAGE_VERSION"* ]]; then
37
- echo "Error: Tag does not match package.json version"
40
+ TAG="${{ github.event.release.tag_name }}"
41
+ VERSION=$(node -p "require('./package.json').version")
42
+
43
+ echo "Release tag: $TAG"
44
+ echo "Package version: $VERSION"
45
+
46
+ if [[ "$TAG" != "v$VERSION"* ]]; then
47
+ echo "::error::Release tag does not match package.json version"
38
48
  exit 1
39
49
  fi
40
- # 5. Publish the package
41
- - name: Publish Package
50
+
51
+ # 5. Publish using OIDC (NO TOKEN)
52
+ # --provenance triggers GitHub → npm identity verification
53
+ - name: Publish to npm (OIDC)
42
54
  run: |
43
- TAG_NAME=${GITHUB_REF#refs/tags/}
44
- if [[ "$TAG_NAME" == *"dev"* ]]; then
45
- echo "Publishing development package with 'dev' tag"
46
- npm publish --tag dev
55
+ TAG="${{ github.event.release.tag_name }}"
56
+
57
+ if [[ "$TAG" == *"dev"* ]]; then
58
+ echo "Publishing dev release"
59
+ npm publish --tag dev --provenance
47
60
  else
48
- echo "Publishing stable package"
49
- npm publish
61
+ echo "Publishing stable release"
62
+ npm publish --provenance
50
63
  fi
51
- env:
52
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
@@ -6,79 +6,48 @@
6
6
  * Ensure to follow a minimal standard
7
7
  */
8
8
  import React, { useState, useEffect, useRef } from 'react';
9
-
10
9
  import PhoneInput from 'react-phone-input-2';
11
-
12
10
  import 'react-phone-input-2/lib/style.css';
13
-
14
- import PropTypes from "prop-types";
15
-
11
+ import PropTypes from 'prop-types';
16
12
  import './phone-input.scss';
17
13
 
14
+ export default function CountryPhoneInput({ value, onChange, disabled, disableCountryCode, required, defaultCountryCode, onBlur,className }) {
15
+ const inputRef = useRef();
16
+ const hasInitializedRef = useRef(false); // IMPORTANT
18
17
 
19
- export default function CountryPhoneInput({ value, onChange, disabled, disableCountryCode, required, defaultCountryCode, onBlur }) {
20
-
21
- let inputRef = useRef();
22
-
23
- const [phone, setPhone] = useState();
18
+ const [phone, setPhone] = useState('');
24
19
 
20
+ /**
21
+ * run ONLY ONCE when initial value has dial code
22
+ */
25
23
  useEffect(() => {
26
- if (value && value.code && value.code.dialCode !== null) {
27
- // To prepopulate phone number ,concat dialCode and phone number
24
+ if (
25
+ !hasInitializedRef.current &&
26
+ value?.code?.dialCode &&
27
+ value?.value
28
+ ) {
28
29
  setPhone(value.code.dialCode + value.value);
29
-
30
- // In case of update , to identify the country code that is getting selected
31
- // We are setting a timeout
32
- setTimeout(() => {
33
- // Using the reference , we get the internal state of the library component
34
- let selectedCountry = null;
35
-
36
- let selectedCode = null;
37
-
38
- if (
39
- inputRef.current &&
40
- inputRef.current.state &&
41
- inputRef.current.state.selectedCountry
42
- ) {
43
- selectedCountry = inputRef.current.state.selectedCountry;
44
-
45
- // Once we get the selected country ,we format it to the form that is received for normal onChange
46
- selectedCode = {
47
- countryCode: selectedCountry.iso2,
48
- dialCode: selectedCountry.dialCode,
49
- name: selectedCountry.name,
50
- };
51
- }
52
-
53
- // Below being the format expected , we trigger the onChange to manually update the form
54
- let updatedValue = {
55
- value: value.code.dialCode + value.value,
56
- code: selectedCode,
57
- };
58
-
59
- onChange(updatedValue);
60
- }, 0);
30
+ hasInitializedRef.current = true; // 👈 mark as done
61
31
  }
62
- }, []);
32
+ }, [value]);
63
33
 
64
34
  /**
65
- * To get value in parent component
66
- * @param {*} value
35
+ * handle user input
67
36
  */
68
- function handleInput(value, code) {
69
- if (code.dialCode) {
70
- value = value.substring(code.dialCode.length);
71
- }
72
- // To get country code with phone number in parent component
73
- value = {
74
- value,
37
+ function handleInput(inputValue, code) {
38
+ if (!code?.dialCode) return;
39
+
40
+ const phoneNumber = inputValue.slice(code.dialCode.length);
41
+
42
+ // remove autofill class when user edits field (if you use it)
43
+ onChange({
44
+ value: phoneNumber,
75
45
  code,
76
- };
77
- onChange(value);
46
+ });
78
47
  }
79
48
 
80
49
  return (
81
- <div className="phone-input">
50
+ <div className={`phone-input ${className || ''}`}>
82
51
  <PhoneInput
83
52
  ref={inputRef}
84
53
  country={defaultCountryCode || 'in'}
@@ -12,6 +12,20 @@
12
12
  // }
13
13
  }
14
14
 
15
+ .autofilled-field {
16
+ .react-tel-input {
17
+ .form-control {
18
+ border-color: #fa8c16 !important; // AntD warning orange
19
+ background-color: #fff7e6;
20
+ box-shadow: none;
21
+ }
22
+
23
+ .flag-dropdown {
24
+ border-color: #fa8c16 !important;
25
+ background-color: #fff7e6;
26
+ }
27
+ }
28
+ }
15
29
  // /* For tablets and smaller screens (max-width: 1024px) */
16
30
  // @media (max-width: 1024px) {
17
31
  // .phone-input {
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useEffect, useRef } from 'react';
2
- import './staff-add.scss';
2
+
3
3
  import { Modal, Tabs, Input, Form, Row, Col, message, Checkbox, Select } from 'antd';
4
4
  import { useTranslation, Button } from './../../../../lib/';
5
5
  import { UsersAPI } from '../../..';
@@ -163,20 +163,20 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
163
163
  return Promise.resolve();
164
164
  };
165
165
 
166
- const validateSerial = (_, value) => {
167
- if (!value) {
168
- return Promise.resolve();
169
- }
166
+ const validateSerial = (_, value) => {
167
+ if (!value) {
168
+ return Promise.resolve();
169
+ }
170
170
 
171
- // Allow same serial number for the same staff in edit mode
172
- if (editMode && String(value) === String(staffData?.slNo)) {
173
- return Promise.resolve();
174
- }
171
+ // Allow same serial number for the same staff in edit mode
172
+ if (editMode && String(value) === String(staffData?.slNo)) {
173
+ return Promise.resolve();
174
+ }
175
175
 
176
- const exists = staffList.some((staff) => String(staff.slNo) === String(value));
176
+ const exists = staffList.some((staff) => String(staff.slNo) === String(value));
177
177
 
178
- return exists ? Promise.reject('Serial Number already exists') : Promise.resolve();
179
- };
178
+ return exists ? Promise.reject('Serial Number already exists') : Promise.resolve();
179
+ };
180
180
 
181
181
  /** -------------------------------
182
182
  * UTIL – Enter Key Navigation
@@ -215,6 +215,8 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
215
215
  });
216
216
  };
217
217
 
218
+ const existing = staffData || {};
219
+
218
220
  /** -------------------------------
219
221
  * SUBMIT HANDLER
220
222
  * ------------------------------- */
@@ -225,19 +227,19 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
225
227
  id: values.id,
226
228
  shortName: values.shortName,
227
229
  description: values.description,
228
- designationPtr: values.designation || null,
229
- phone: values.phone1 || null,
230
- mobile: values.phone1 || null,
231
- alternateMobile: values.phone2 || null,
232
- email: values.email1 || null,
233
- alternateEmail: values.email2 || null,
234
- remarks: values.remarks || null,
235
- slNo: values.slno ? Number(values.slno) : null,
230
+ designationPtr: values.designation ?? existing.designationPtr ?? null,
231
+ phone: values.phone1 ?? existing.phone ?? null,
232
+ mobile: values.phone1 ?? existing.mobile ?? null,
233
+ alternateMobile: values.phone2 ?? existing.alternateMobile ?? null,
234
+ email: values.email1 ?? existing.email ?? null,
235
+ alternateEmail: values.email2 ?? existing.alternateEmail ?? null,
236
+ remarks: values.remarks ?? existing.remarks ?? null,
237
+ slNo: values.slno ?? existing.slNo ?? null,
236
238
  active: values.active === 'Y' ? 'Y' : 'N',
237
- address1: values.address1 || null,
238
- address2: values.address2 || null,
239
- place: values.place || null,
240
- zip: values.zip || null,
239
+ address1: values.address1 ?? existing.address1 ?? null,
240
+ address2: values.address2 ?? existing.address2 ?? null,
241
+ place: values.place ?? existing.place ?? null,
242
+ zip: values.zip ?? existing.zip ?? null,
241
243
  };
242
244
 
243
245
  try {
@@ -279,10 +281,7 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
279
281
  name="id"
280
282
  validateTrigger="onChange"
281
283
  hasFeedback={!editMode}
282
- rules={[
283
- { required: true, message: 'Code is required' },
284
- { validator: validateInput },
285
- ]}
284
+ rules={[{ required: true, message: 'Code is required' }, { validator: validateInput }]}
286
285
  >
287
286
  <Input placeholder="Enter Code" autoComplete="off" maxLength={10} ref={nameInputRef} onKeyDown={handleEnterKey} disabled={editMode} />
288
287
  </Form.Item>
@@ -383,7 +382,7 @@ const StaffAdd = ({ visible, onCancel, staffId, staffData, onSuccess }) => {
383
382
  name="slno"
384
383
  rules={[{ message: 'only numbers are allowed', pattern: /^\d*$/ }, { validator: validateSerial }]}
385
384
  >
386
- <Input autoComplete="off" maxLength={5} placeholder="Enter Serial Number" onKeyDown={handleEnterKey} />
385
+ <Input autoComplete="off" maxLength={5} placeholder="Enter Serial Number" onKeyDown={handleEnterKey} />
387
386
  </Form.Item>
388
387
  </Col>
389
388
 
@@ -73,6 +73,7 @@ class UserRole extends Base {
73
73
  return false;
74
74
  }
75
75
 
76
+
76
77
  /**
77
78
  *
78
79
  * @param {*} formBody
@@ -86,6 +87,19 @@ class UserRole extends Base {
86
87
  })
87
88
  }
88
89
 
90
+ /**
91
+ *
92
+ * @param {*} formBody
93
+ * @returns
94
+ */
95
+ addUserRoleSave = (values) => {
96
+
97
+ return this.post({
98
+ url: 'core-user-roles/save-core-user-roles',
99
+ formBody: values
100
+ })
101
+ }
102
+
89
103
  /**
90
104
  *
91
105
  *
@@ -51,6 +51,9 @@ export default function AssignRole() {
51
51
  const [selectedMenus, setSelectedMenus] = useState([]);
52
52
  const [search, setSearch] = useState('');
53
53
 
54
+ // for initial roles
55
+ const [initialRoles, setInitialRoles] = useState([]);
56
+
54
57
  /**
55
58
  * Load user details and assigned roles when user ID changes
56
59
  */
@@ -89,13 +92,14 @@ export default function AssignRole() {
89
92
  /* ---------- USER ROLES ---------- */
90
93
  const roleList = Array.isArray(roleRes?.result) ? roleRes.result : [];
91
94
 
92
- // Extract valid & unique role IDs
93
- const roleIds = [...new Set(roleList.filter((item) => item.role_id).map((item) => Number(item.role_id)))];
95
+ // Extract VALID role IDs (ignore nulls & duplicates)
96
+ const roleIds = [...new Set(list.filter((item) => item.role_id && item.active === 'Y').map((item) => Number(item.role_id)))];
94
97
 
95
98
  setSelectedRoles(roleIds);
96
- } catch (error) {
97
- setUser(null);
98
- setSelectedRoles([]);
99
+ setInitialRoles(roleIds);
100
+ } catch (e) {
101
+ console.error(e);
102
+ message.error('Unable to load user details');
99
103
  } finally {
100
104
  setLoadingUser(false);
101
105
  }
@@ -202,25 +206,39 @@ export default function AssignRole() {
202
206
  * @returns {Promise<void>}
203
207
  */
204
208
  const handleSaveUserRole = async () => {
205
- if (!id || !selectedRoles.length) {
206
- message.warning('No roles selected to save');
207
- return;
208
- }
209
+ if (!id) {
210
+ message.error('Invalid user');
211
+ return;
212
+ }
209
213
  // start button spinner
210
214
  setSaving(true);
211
215
 
212
216
  try {
213
- // Save all roles in parallel
214
- await Promise.all(
215
- selectedRoles.map((roleId) =>
216
- UserRolesAPI.addUserRole({
217
- values: { user_id: id, role_id: roleId },
218
- })
219
- )
220
- );
217
+ // roles to ADD
218
+ const role_ids = selectedRoles.filter((rid) => !initialRoles.includes(rid));
219
+
220
+ // roles to REMOVE
221
+ const update_ids = initialRoles.filter((rid) => !selectedRoles.includes(rid));
222
+
223
+ if (!role_ids.length && !update_ids.length) {
224
+ message.info('No changes to save');
225
+ return;
226
+ }
227
+
228
+ const payload = {
229
+ user_id: id,
230
+ role_ids,
231
+ update_ids,
232
+ };
233
+
234
+ await UserRolesAPI.addUserRoleSave(payload);
221
235
 
222
236
  // Only show success AFTER all saves
223
- message.success('User roles saved successfully');
237
+
238
+ message.success('User roles updated successfully');
239
+
240
+ await loadUser();
241
+
224
242
  } catch (err) {
225
243
  console.error(err);
226
244
  message.error('Failed to save user roles');
@@ -290,7 +308,7 @@ export default function AssignRole() {
290
308
  </div>
291
309
 
292
310
  <div className="footer-actions">
293
- <Button type="primary" onClick={handleSaveUserRole} loading={saving}>
311
+ <Button type="primary" onClick={handleSaveUserRole} loading={saving} disabled={!selectedRoles.length}>
294
312
  Save
295
313
  </Button>
296
314
  </div>
@@ -288,7 +288,7 @@ class Users extends Base {
288
288
  roles associated with a specific user from the API. */
289
289
 
290
290
  getUserRole = ({ id }) => {
291
- return ApiUtils.get({ url: `core-user-roles?user_id=${id}` });
291
+ return ApiUtils.get({ url: `core-user-roles?user_id=${id}&active=Y` });
292
292
 
293
293
  };
294
294
 
@@ -241,39 +241,7 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
241
241
 
242
242
  return <Component {...step.config} {...props} step={step} params={urlParams} onStepComplete={() => setIsStepCompleted(true)} />;
243
243
  };
244
- /**
245
- * Keyboard Navigation
246
- * - Enables left and right arrow keys for step navigation.
247
- * - Prevents navigation beyond step boundaries.
248
- * - Cleans up event listeners on unmount.
249
- */
250
- // useEffect(() => {
251
- // const handleKeyDown = (event) => {
252
- // // Handle Left Arrow key press to go to the previous step
253
- // if (event.key === 'ArrowLeft') {
254
- // if (activeStep > 0) {
255
- // handlePrevious();
256
- // }
257
- // }
258
- // // Handle Right Arrow key press to go to the next step
259
- // else if (event.key === 'ArrowRight') {
260
- // if (activeStep < steps.length - 1) {
261
- // handleNext();
262
- // }
263
- // }
264
- // };
265
-
266
- // // externalWin?.addEventListener('keydown', handleKeyDown);
267
- // if (externalWin instanceof Window) {
268
- // console.log("********************************",externalWin.addEventListener)
269
- // externalWin.addEventListener('keydown', handleKeyDown);
270
- // }
271
244
 
272
- // // showExternalWindow.addEventListener('keydown', handleKeyDown);
273
- // console.log("LLLLLLLLLLLLLL",window.addEventListener)
274
- // window.addEventListener('keydown', handleKeyDown);
275
- // return () => window.removeEventListener('keydown', handleKeyDown);
276
- // }, [activeStep, steps, handlePrevious, handleNext, externalWin]);
277
245
  useEffect(() => {
278
246
  const handleKeyDown = (event) => {
279
247
  if (event.key === 'ArrowLeft' && activeStep > 0) {
@@ -355,7 +323,6 @@ export default function ProcessStepsPage({ match, CustomComponents = {}, ...prop
355
323
  <>
356
324
  <ExternalWindow
357
325
  onWindowReady={(win) => {
358
- console.log('>>>>>>>>>>>>>>>>>>>>>', win.addEventListener);
359
326
  setExternalWin(win);
360
327
  win.focus();
361
328
  }}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ui-soxo-bootstrap-core",
3
- "version": "2.4.25-dev.47",
3
+ "version": "2.4.25-dev.49",
4
4
  "description": "All the Core Components for you to start",
5
5
  "keywords": [
6
6
  "all in one"