em-crm-claim-module 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.
package/README.md ADDED
@@ -0,0 +1,458 @@
1
+ # em-crm-claim-module
2
+
3
+ A responsive React component module for managing claims - Add Claim and List Claims functionality extracted from em-crm project. The module handles all API calls internally, requiring only `baseURL`, `token`, and `userData` as inputs.
4
+
5
+ ## Features
6
+
7
+ - ✅ **Add Claim Component** - Responsive form to create new claims
8
+ - ✅ **Claims List Component** - Responsive table to view and manage claims
9
+ - ✅ **Internal API Handling** - All API calls are handled within the package
10
+ - ✅ **Fully Responsive** - Works seamlessly on mobile, tablet, and desktop
11
+ - ✅ **Material-UI Integration** - Built with Material-UI v5
12
+ - ✅ **Auto-refresh** - Claims list auto-refreshes at configurable intervals
13
+ - ✅ **TypeScript Ready** - Can be used with TypeScript projects
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install em-crm-claim-module
19
+ ```
20
+
21
+ ## Peer Dependencies
22
+
23
+ Make sure you have these peer dependencies installed:
24
+
25
+ ```bash
26
+ npm install react react-dom @mui/material @mui/icons-material @mui/x-date-pickers react-select@^5.0.0 moment react-hot-toast axios@^1.0.0 dayjs
27
+ ```
28
+
29
+ **Notes:**
30
+ - `react-select` version 5.x is required for React 18 compatibility. Version 4.x only supports React 16-17.
31
+ - `axios` version 1.x is required for security. Versions < 1.0.0 have known vulnerabilities (CSRF, DoS, SSRF).
32
+
33
+ ## Quick Start
34
+
35
+ The module requires three essential inputs:
36
+ - `baseURL` - Your API base URL
37
+ - `token` - Authentication token
38
+ - `userData` - User information object
39
+
40
+ **Optional (recommended):**
41
+ - `refreshToken` - Refresh token for automatic token renewal
42
+ - `onTokenRefresh` - Callback function to handle token refresh
43
+
44
+ ```jsx
45
+ import React from 'react';
46
+ import { ClaimProvider, AddClaim, ClaimsList } from 'em-crm-claim-module';
47
+ import { LocalizationProvider } from '@mui/x-date-pickers';
48
+ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
49
+ import { ThemeProvider, createTheme } from '@mui/material/styles';
50
+ import 'em-crm-claim-module/dist/index.css';
51
+
52
+ const theme = createTheme({
53
+ palette: {
54
+ primary: {
55
+ main: '#f45e29',
56
+ },
57
+ },
58
+ });
59
+
60
+ function App() {
61
+ const baseURL = 'https://api.example.com'; // Your API base URL
62
+ const token = 'your-auth-token'; // Your authentication token
63
+ const refreshToken = 'your-refresh-token'; // Optional: for automatic token refresh
64
+
65
+ // Token refresh handler (optional but recommended)
66
+ const handleTokenRefresh = async (refreshTokenValue) => {
67
+ const response = await fetch(`${baseURL}/api/auth/refresh`, {
68
+ method: 'POST',
69
+ body: JSON.stringify({ refreshToken: refreshTokenValue }),
70
+ });
71
+ const data = await response.json();
72
+ return data.token || data.accessToken;
73
+ };
74
+
75
+ const userData = {
76
+ crm_role: 'EMPLOYEE',
77
+ name: 'John Doe',
78
+ crm_profile: 'SALES',
79
+ employee_code: 'EMP001',
80
+ username: 'johndoe',
81
+ uuid: 'user-uuid-123',
82
+ };
83
+
84
+ return (
85
+ <ThemeProvider theme={theme}>
86
+ <LocalizationProvider dateAdapter={AdapterDayjs}>
87
+ <ClaimProvider
88
+ baseURL={baseURL}
89
+ token={token}
90
+ refreshToken={refreshToken}
91
+ onTokenRefresh={handleTokenRefresh}
92
+ userData={userData}
93
+ >
94
+ {/* Your components */}
95
+ <ClaimsList />
96
+ </ClaimProvider>
97
+ </LocalizationProvider>
98
+ </ThemeProvider>
99
+ );
100
+ }
101
+ ```
102
+
103
+ ## Usage
104
+
105
+ ### Basic Setup
106
+
107
+ Wrap your application with `ClaimProvider` and provide the required props:
108
+
109
+ ```jsx
110
+ import { ClaimProvider, AddClaim, ClaimsList } from 'em-crm-claim-module';
111
+ import axios from 'axios';
112
+
113
+ function App() {
114
+ const baseURL = 'https://api.example.com';
115
+ const token = localStorage.getItem('authToken');
116
+ const refreshToken = localStorage.getItem('refreshToken');
117
+
118
+ // Token refresh handler
119
+ const handleTokenRefresh = async (refreshTokenValue) => {
120
+ try {
121
+ const response = await axios.post(`${baseURL}/api/auth/refresh`, {
122
+ refreshToken: refreshTokenValue,
123
+ });
124
+ const newToken = response.data?.token || response.data?.accessToken;
125
+ if (newToken) {
126
+ localStorage.setItem('authToken', newToken);
127
+ return newToken;
128
+ }
129
+ } catch (error) {
130
+ // Handle refresh failure (e.g., redirect to login)
131
+ console.error('Token refresh failed:', error);
132
+ throw error;
133
+ }
134
+ };
135
+
136
+ return (
137
+ <ClaimProvider
138
+ baseURL={baseURL}
139
+ token={token}
140
+ refreshToken={refreshToken}
141
+ onTokenRefresh={handleTokenRefresh}
142
+ userData={{
143
+ crm_role: 'EMPLOYEE',
144
+ name: 'John Doe',
145
+ crm_profile: 'SALES',
146
+ employee_code: 'EMP001',
147
+ username: 'johndoe',
148
+ uuid: 'user-uuid-123',
149
+ }}
150
+ >
151
+ <ClaimsList />
152
+ </ClaimProvider>
153
+ );
154
+ }
155
+ ```
156
+
157
+ ### AddClaim Component
158
+
159
+ ```jsx
160
+ import { ClaimProvider, AddClaim } from 'em-crm-claim-module';
161
+
162
+ function AddClaimPage() {
163
+ const [view, setView] = useState('list');
164
+
165
+ return (
166
+ <ClaimProvider baseURL={baseURL} token={token} userData={userData}>
167
+ <AddClaim
168
+ onSuccess={(data) => {
169
+ console.log('Claim created:', data);
170
+ setView('list'); // Navigate back to list
171
+ }}
172
+ onCancel={() => setView('list')}
173
+ />
174
+ </ClaimProvider>
175
+ );
176
+ }
177
+ ```
178
+
179
+ ### ClaimsList Component
180
+
181
+ ```jsx
182
+ import { ClaimProvider, ClaimsList } from 'em-crm-claim-module';
183
+
184
+ function ClaimsListPage() {
185
+ const [view, setView] = useState('list');
186
+
187
+ return (
188
+ <ClaimProvider baseURL={baseURL} token={token} userData={userData}>
189
+ <ClaimsList
190
+ onAddClaim={() => setView('add')}
191
+ onRefresh={() => {
192
+ console.log('Claims refreshed');
193
+ }}
194
+ autoRefresh={true}
195
+ refreshInterval={30000} // Refresh every 30 seconds
196
+ />
197
+ </ClaimProvider>
198
+ );
199
+ }
200
+ ```
201
+
202
+ ### Complete Example with Navigation and Refresh Token
203
+
204
+ ```jsx
205
+ import React, { useState } from 'react';
206
+ import { ClaimProvider, AddClaim, ClaimsList } from 'em-crm-claim-module';
207
+ import { LocalizationProvider } from '@mui/x-date-pickers';
208
+ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
209
+ import axios from 'axios';
210
+
211
+ function ClaimModule() {
212
+ const [view, setView] = useState('list'); // 'list' or 'add'
213
+
214
+ const baseURL = process.env.REACT_APP_API_URL || 'https://api.example.com';
215
+ const token = localStorage.getItem('authToken');
216
+ const refreshToken = localStorage.getItem('refreshToken');
217
+
218
+ // Token refresh handler
219
+ const handleTokenRefresh = async (refreshTokenValue) => {
220
+ try {
221
+ const response = await axios.post(`${baseURL}/api/auth/refresh`, {
222
+ refreshToken: refreshTokenValue,
223
+ });
224
+ const newToken = response.data?.token || response.data?.accessToken;
225
+ if (newToken) {
226
+ localStorage.setItem('authToken', newToken);
227
+ // Update refresh token if provided
228
+ if (response.data?.refreshToken) {
229
+ localStorage.setItem('refreshToken', response.data.refreshToken);
230
+ }
231
+ return newToken;
232
+ }
233
+ throw new Error('No token received');
234
+ } catch (error) {
235
+ console.error('Token refresh failed:', error);
236
+ // Redirect to login on refresh failure
237
+ // window.location.href = '/login';
238
+ throw error;
239
+ }
240
+ };
241
+
242
+ const userData = {
243
+ crm_role: 'EMPLOYEE',
244
+ name: 'John Doe',
245
+ crm_profile: 'SALES',
246
+ employee_code: 'EMP001',
247
+ username: 'johndoe',
248
+ uuid: 'user-uuid-123',
249
+ };
250
+
251
+ return (
252
+ <LocalizationProvider dateAdapter={AdapterDayjs}>
253
+ <ClaimProvider
254
+ baseURL={baseURL}
255
+ token={token}
256
+ refreshToken={refreshToken}
257
+ onTokenRefresh={handleTokenRefresh}
258
+ userData={userData}
259
+ >
260
+ {view === 'add' ? (
261
+ <AddClaim
262
+ onSuccess={() => setView('list')}
263
+ onCancel={() => setView('list')}
264
+ />
265
+ ) : (
266
+ <ClaimsList
267
+ onAddClaim={() => setView('add')}
268
+ />
269
+ )}
270
+ </ClaimProvider>
271
+ </LocalizationProvider>
272
+ );
273
+ }
274
+ ```
275
+
276
+ ### Refresh Token Handling
277
+
278
+ The module supports automatic token refresh when API calls receive a 401 (Unauthorized) response. There are two ways to handle token refresh:
279
+
280
+ #### Option 1: Custom Refresh Handler (Recommended)
281
+
282
+ Provide a callback function that handles the token refresh:
283
+
284
+ ```jsx
285
+ const handleTokenRefresh = async (refreshTokenValue) => {
286
+ const response = await fetch(`${baseURL}/api/auth/refresh`, {
287
+ method: 'POST',
288
+ headers: { 'Content-Type': 'application/json' },
289
+ body: JSON.stringify({ refreshToken: refreshTokenValue }),
290
+ });
291
+ const data = await response.json();
292
+ return data.token || data.accessToken;
293
+ };
294
+
295
+ <ClaimProvider
296
+ baseURL={baseURL}
297
+ token={token}
298
+ refreshToken={refreshToken}
299
+ onTokenRefresh={handleTokenRefresh}
300
+ userData={userData}
301
+ >
302
+ {/* ... */}
303
+ </ClaimProvider>
304
+ ```
305
+
306
+ #### Option 2: Automatic Refresh
307
+
308
+ Set `onTokenRefresh` to `'auto'` to use the standard endpoint `/api/auth/refresh`:
309
+
310
+ ```jsx
311
+ <ClaimProvider
312
+ baseURL={baseURL}
313
+ token={token}
314
+ refreshToken={refreshToken}
315
+ onTokenRefresh="auto"
316
+ userData={userData}
317
+ >
318
+ {/* ... */}
319
+ </ClaimProvider>
320
+ ```
321
+
322
+ **How it works:**
323
+ - When any API call receives a 401 response, the module automatically attempts to refresh the token
324
+ - All pending requests are queued and retried after a successful token refresh
325
+ - If token refresh fails, all queued requests are rejected and an error is shown
326
+ - The new token is automatically updated in the API service
327
+
328
+ ## Props
329
+
330
+ ### ClaimProvider Props
331
+
332
+ | Prop | Type | Required | Description |
333
+ |------|------|----------|-------------|
334
+ | `baseURL` | `string` | Yes | Base URL for API calls (e.g., 'https://api.example.com') |
335
+ | `token` | `string` | Yes | Authentication token for API requests |
336
+ | `userData` | `object` | Yes | User data object (see UserData structure below) |
337
+ | `refreshToken` | `string` | No | Refresh token for automatic token renewal |
338
+ | `onTokenRefresh` | `function \| string` | No | Callback function to refresh token, or 'auto' for automatic refresh using `/api/auth/refresh` endpoint |
339
+ | `children` | `ReactNode` | Yes | React children components |
340
+
341
+ ### AddClaim Props
342
+
343
+ | Prop | Type | Required | Description |
344
+ |------|------|----------|-------------|
345
+ | `onSuccess` | `function` | No | Callback function called when claim is successfully created |
346
+ | `onCancel` | `function` | No | Callback function called when cancel button is clicked |
347
+ | `visitOptions` | `array` | No | Custom visit purpose options |
348
+
349
+ ### ClaimsList Props
350
+
351
+ | Prop | Type | Required | Description |
352
+ |------|------|----------|-------------|
353
+ | `onAddClaim` | `function` | No | Callback function called when Add Claim button is clicked |
354
+ | `onRefresh` | `function` | No | Callback function called after claims are refreshed |
355
+ | `columns` | `array` | No | Custom column configuration |
356
+ | `autoRefresh` | `boolean` | No | Enable auto-refresh (default: true) |
357
+ | `refreshInterval` | `number` | No | Auto-refresh interval in milliseconds (default: 30000) |
358
+
359
+ ## Data Structures
360
+
361
+ ### UserData Object
362
+
363
+ ```javascript
364
+ {
365
+ crm_role: 'EMPLOYEE', // User's role
366
+ name: 'John Doe', // User's name
367
+ crm_profile: 'SALES', // User's profile
368
+ employee_code: 'EMP001', // Employee code
369
+ username: 'johndoe', // Username
370
+ uuid: 'user-uuid-123', // User UUID
371
+ // Optional fields:
372
+ roleId: 'EMPLOYEE', // Alternative role ID
373
+ profileName: 'SALES', // Alternative profile name
374
+ empCode: 'EMP001', // Alternative employee code
375
+ }
376
+ ```
377
+
378
+ ### Claim Object Structure
379
+
380
+ The API should return claim objects with the following structure:
381
+
382
+ ```javascript
383
+ {
384
+ _id: 'claim-id',
385
+ claimId: 'CLM001',
386
+ requestBy_name: 'John Doe',
387
+ schoolName: 'ABC School',
388
+ expenseType: 'Food',
389
+ claimStatus: 'PENDING AT BUH',
390
+ createdAt: '2024-01-01T00:00:00Z',
391
+ approvedDate: '2024-01-02T00:00:00Z',
392
+ claimAmount: 1000,
393
+ approvedAmount: 950
394
+ }
395
+ ```
396
+
397
+ ## API Endpoints
398
+
399
+ The module expects the following API endpoints:
400
+
401
+ - `POST /api/userClaim/createMyClaim` - Create a new claim
402
+ - `POST /api/userClaim/getMyClaimList` - Get list of claims
403
+ - `PUT /api/userClaim/bulkDelete` - Delete multiple claims
404
+ - `GET /api/claimMaster/getClaimMasterList` - Get claim master list
405
+ - `GET /api/school/getSchoolCodeList` - Search schools
406
+
407
+ All endpoints should:
408
+ - Accept Bearer token authentication in the `Authorization` header
409
+ - Return data in the format: `{ result: [...], message: '...' }`
410
+
411
+ ## Responsive Design
412
+
413
+ Both components are fully responsive and adapt to different screen sizes:
414
+
415
+ - **Mobile (< 768px)**: Stacked layout, optimized for touch interactions
416
+ - **Tablet (768px - 1024px)**: Balanced layout with some columns hidden
417
+ - **Desktop (> 1024px)**: Full layout with all columns visible
418
+
419
+ ## Styling
420
+
421
+ The components use Material-UI's theming system. You can customize the appearance by wrapping your app with a Material-UI ThemeProvider:
422
+
423
+ ```jsx
424
+ import { ThemeProvider, createTheme } from '@mui/material/styles';
425
+
426
+ const theme = createTheme({
427
+ palette: {
428
+ primary: {
429
+ main: '#f45e29',
430
+ },
431
+ },
432
+ });
433
+
434
+ function App() {
435
+ return (
436
+ <ThemeProvider theme={theme}>
437
+ {/* Your components */}
438
+ </ThemeProvider>
439
+ );
440
+ }
441
+ ```
442
+
443
+ ## Error Handling
444
+
445
+ The module handles errors internally and displays toast notifications using `react-hot-toast`. Common errors:
446
+
447
+ - **401 Unauthorized**: Token is invalid or expired - automatically triggers token refresh if `refreshToken` and `onTokenRefresh` are provided
448
+ - **Network errors**: Connection issues are displayed to the user
449
+ - **Validation errors**: Form validation errors are shown inline
450
+ - **Token refresh failures**: If token refresh fails, a toast notification is shown and you can handle redirect to login in your `onTokenRefresh` callback
451
+
452
+ ## License
453
+
454
+ ISC
455
+
456
+ ## Contributing
457
+
458
+ Contributions are welcome! Please feel free to submit a Pull Request.
package/dist/index.css ADDED
@@ -0,0 +1 @@
1
+ .rc-time-picker{display:inline-block;position:relative}.rc-time-picker,.rc-time-picker *{box-sizing:border-box}.rc-time-picker-clear{cursor:pointer;height:20px;line-height:20px;margin:0;overflow:hidden;position:absolute;right:6px;text-align:center;top:3px;width:20px}.rc-time-picker-clear-icon:after{color:#aaa;content:"x";display:inline-block;font-size:12px;font-style:normal;height:20px;line-height:1;-webkit-transition:color .3s ease;transition:color .3s ease;width:20px}.rc-time-picker-clear-icon:hover:after{color:#666}.rc-time-picker-input{background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:4px;color:#666;cursor:text;display:inline-block;font-size:12px;height:28px;line-height:1.5;padding:4px 7px;position:relative;-webkit-transition:border .2s cubic-bezier(.645,.045,.355,1),background .2s cubic-bezier(.645,.045,.355,1),box-shadow .2s cubic-bezier(.645,.045,.355,1);transition:border .2s cubic-bezier(.645,.045,.355,1),background .2s cubic-bezier(.645,.045,.355,1),box-shadow .2s cubic-bezier(.645,.045,.355,1);width:100%}.rc-time-picker-input[disabled]{background:#f7f7f7;color:#ccc;cursor:not-allowed}.rc-time-picker-panel{box-sizing:border-box;position:absolute;width:170px;z-index:1070}.rc-time-picker-panel *{box-sizing:border-box}.rc-time-picker-panel-inner{background-clip:padding-box;background-color:#fff;border:1px solid #ccc;border-radius:4px;box-shadow:0 1px 5px #ccc;display:inline-block;font-size:12px;line-height:1.5;list-style:none;outline:none;position:relative;text-align:left}.rc-time-picker-panel-narrow{max-width:113px}.rc-time-picker-panel-input{border:1px solid transparent;cursor:auto;line-height:1.5;margin:0;outline:0;padding:0;width:100%}.rc-time-picker-panel-input-wrap{border-bottom:1px solid #e9e9e9;box-sizing:border-box;padding:6px;position:relative}.rc-time-picker-panel-input-invalid{border-color:red}.rc-time-picker-panel-select{border:1px solid #e9e9e9;border-width:0 1px;box-sizing:border-box;float:left;font-size:12px;margin-left:-1px;max-height:144px;overflow-y:auto;position:relative;width:56px}.rc-time-picker-panel-select-active{overflow-y:auto}.rc-time-picker-panel-select:first-child{border-left:0;margin-left:0}.rc-time-picker-panel-select:last-child{border-right:0}.rc-time-picker-panel-select ul{box-sizing:border-box;list-style:none;margin:0;padding:0;width:100%}.rc-time-picker-panel-select li{cursor:pointer;height:24px;line-height:24px;list-style:none;margin:0;padding:0 0 0 16px;text-align:left;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:100%}.rc-time-picker-panel-select li:hover{background:#edfaff}li.rc-time-picker-panel-select-option-selected{background:#f7f7f7;font-weight:700}li.rc-time-picker-panel-select-option-disabled{color:#ccc}li.rc-time-picker-panel-select-option-disabled:hover{background:transparent;cursor:not-allowed}.customTPicker{width:100%}.customTPicker .rc-time-picker-input{border:1px solid #dedede;border-radius:4px;font-size:1rem;padding:8.8px;width:100%}.customTPicker .rc-time-picker-input:focus{border-color:#f45e29;outline:none}@media (max-width:768px){.customTPicker .rc-time-picker-input{font-size:.9rem;padding:5px 10px}}
@@ -0,0 +1 @@
1
+ .rc-time-picker{display:inline-block;position:relative}.rc-time-picker,.rc-time-picker *{box-sizing:border-box}.rc-time-picker-clear{cursor:pointer;height:20px;line-height:20px;margin:0;overflow:hidden;position:absolute;right:6px;text-align:center;top:3px;width:20px}.rc-time-picker-clear-icon:after{color:#aaa;content:"x";display:inline-block;font-size:12px;font-style:normal;height:20px;line-height:1;-webkit-transition:color .3s ease;transition:color .3s ease;width:20px}.rc-time-picker-clear-icon:hover:after{color:#666}.rc-time-picker-input{background-color:#fff;background-image:none;border:1px solid #d9d9d9;border-radius:4px;color:#666;cursor:text;display:inline-block;font-size:12px;height:28px;line-height:1.5;padding:4px 7px;position:relative;-webkit-transition:border .2s cubic-bezier(.645,.045,.355,1),background .2s cubic-bezier(.645,.045,.355,1),box-shadow .2s cubic-bezier(.645,.045,.355,1);transition:border .2s cubic-bezier(.645,.045,.355,1),background .2s cubic-bezier(.645,.045,.355,1),box-shadow .2s cubic-bezier(.645,.045,.355,1);width:100%}.rc-time-picker-input[disabled]{background:#f7f7f7;color:#ccc;cursor:not-allowed}.rc-time-picker-panel{box-sizing:border-box;position:absolute;width:170px;z-index:1070}.rc-time-picker-panel *{box-sizing:border-box}.rc-time-picker-panel-inner{background-clip:padding-box;background-color:#fff;border:1px solid #ccc;border-radius:4px;box-shadow:0 1px 5px #ccc;display:inline-block;font-size:12px;line-height:1.5;list-style:none;outline:none;position:relative;text-align:left}.rc-time-picker-panel-narrow{max-width:113px}.rc-time-picker-panel-input{border:1px solid transparent;cursor:auto;line-height:1.5;margin:0;outline:0;padding:0;width:100%}.rc-time-picker-panel-input-wrap{border-bottom:1px solid #e9e9e9;box-sizing:border-box;padding:6px;position:relative}.rc-time-picker-panel-input-invalid{border-color:red}.rc-time-picker-panel-select{border:1px solid #e9e9e9;border-width:0 1px;box-sizing:border-box;float:left;font-size:12px;margin-left:-1px;max-height:144px;overflow-y:auto;position:relative;width:56px}.rc-time-picker-panel-select-active{overflow-y:auto}.rc-time-picker-panel-select:first-child{border-left:0;margin-left:0}.rc-time-picker-panel-select:last-child{border-right:0}.rc-time-picker-panel-select ul{box-sizing:border-box;list-style:none;margin:0;padding:0;width:100%}.rc-time-picker-panel-select li{cursor:pointer;height:24px;line-height:24px;list-style:none;margin:0;padding:0 0 0 16px;text-align:left;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:100%}.rc-time-picker-panel-select li:hover{background:#edfaff}li.rc-time-picker-panel-select-option-selected{background:#f7f7f7;font-weight:700}li.rc-time-picker-panel-select-option-disabled{color:#ccc}li.rc-time-picker-panel-select-option-disabled:hover{background:transparent;cursor:not-allowed}.customTPicker{width:100%}.customTPicker .rc-time-picker-input{border:1px solid #dedede;border-radius:4px;font-size:1rem;padding:8.8px;width:100%}.customTPicker .rc-time-picker-input:focus{border-color:#f45e29;outline:none}@media (max-width:768px){.customTPicker .rc-time-picker-input{font-size:.9rem;padding:5px 10px}}