@suflon/rnmd-reporting 0.0.1

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.
Files changed (39) hide show
  1. package/App.tsx +69 -0
  2. package/README.md +97 -0
  3. package/app.json +4 -0
  4. package/babel.config.js +18 -0
  5. package/global.css +143 -0
  6. package/index.js +9 -0
  7. package/metro.config.js +209 -0
  8. package/nativewind-env.d.ts +1 -0
  9. package/package.json +106 -0
  10. package/patches/@react-navigation+stack+7.6.16.patch +11 -0
  11. package/patches/@suflon+native-ui+0.0.18.patch +26020 -0
  12. package/patches/react-native+0.83.1.patch +52 -0
  13. package/react-native.config.js +27 -0
  14. package/scripts/fix-suflon-native-ui.js +25 -0
  15. package/scripts/link-react-native-pnpm.js +42 -0
  16. package/src/config/index.ts +10 -0
  17. package/src/context/ConnectionI18nContext.tsx +61 -0
  18. package/src/modules/Reporting/component/README.md +3 -0
  19. package/src/modules/Reporting/component/ReportChart.tsx +882 -0
  20. package/src/modules/Reporting/component/ReportFilterModal.tsx +403 -0
  21. package/src/modules/Reporting/component/ReportingDetail.tsx +481 -0
  22. package/src/modules/Reporting/index.tsx +239 -0
  23. package/src/modules/Reporting/utils.tsx +14 -0
  24. package/src/navigation/index.tsx +31 -0
  25. package/src/screens/ConnectionListScreen.tsx +471 -0
  26. package/src/screens/DevToolsCorner.tsx +810 -0
  27. package/src/screens/NewConnectionModal.tsx +282 -0
  28. package/src/services/ApiService.ts +22 -0
  29. package/src/services/ConnectionService.ts +81 -0
  30. package/src/services/api.ts +83 -0
  31. package/src/stores/connection.store.ts +3 -0
  32. package/src/stores/language.store.ts +54 -0
  33. package/src/theme/colors.ts +56 -0
  34. package/src/types/connection.ts +69 -0
  35. package/src/utils/AsyncStorageUtils.ts +56 -0
  36. package/src/utils/connectionStrings.ts +158 -0
  37. package/src/utils/errorMessage.ts +11 -0
  38. package/tailwind.config.js +196 -0
  39. package/tsconfig.json +25 -0
@@ -0,0 +1,282 @@
1
+ import React, { useState, useMemo, useEffect } from 'react';
2
+ import { View, Text, StyleSheet } from 'react-native';
3
+ import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
4
+ import {
5
+ BottomModal,
6
+ Loader,
7
+ TextArea,
8
+ Button,
9
+ ModalTitle,
10
+ SearchFilter,
11
+ Toast,
12
+ useConnectionStore,
13
+ } from '@suflon/native-ui';
14
+ import { useConnectionI18n } from '@/context/ConnectionI18nContext';
15
+ import { getBackendMessage } from '@/utils/errorMessage';
16
+
17
+ interface NewConnectionModalProps {
18
+ visible: boolean;
19
+ onClose: () => void;
20
+ onSuccess?: (message?: string) => void;
21
+ }
22
+
23
+ const NewConnectionModal = ({ visible, onClose, onSuccess }: NewConnectionModalProps) => {
24
+ const [searchNum, setSearchNum] = useState('');
25
+ const [message, setMessage] = useState('');
26
+ const [toast, setToast] = useState({ visible: false, message: '' });
27
+ const [successToast, setSuccessToast] = useState({ visible: false, message: '' });
28
+
29
+ const { t } = useConnectionI18n();
30
+
31
+ const {
32
+ searchResult,
33
+ isSearching,
34
+ isSubmitting,
35
+ error,
36
+ searchCompany,
37
+ sendRequest,
38
+ clearSearch,
39
+ clearError,
40
+ } = useConnectionStore();
41
+
42
+ useEffect(() => {
43
+ if (error && !searchResult && !isSearching) {
44
+ setToast({ visible: true, message: error });
45
+ }
46
+ }, [error, searchResult, isSearching]);
47
+
48
+ const handleSearch = () => {
49
+ if (searchNum.trim()) {
50
+ searchCompany(searchNum);
51
+ }
52
+ };
53
+
54
+ const handleSendRequest = async () => {
55
+ if (searchResult) {
56
+ try {
57
+ await sendRequest(searchResult.id, message);
58
+ setSuccessToast({ visible: true, message: 'Connection request sent successfully' });
59
+ clearSearch();
60
+ setSearchNum('');
61
+ setMessage('');
62
+ } catch (err: any) {
63
+ const msg = getBackendMessage(err);
64
+ setTimeout(() => {
65
+ setToast({ visible: true, message: msg });
66
+ }, 100);
67
+ }
68
+ }
69
+ };
70
+
71
+ const handleClose = () => {
72
+ onClose();
73
+ clearSearch();
74
+ clearError();
75
+ setSearchNum('');
76
+ setMessage('');
77
+ setToast({ visible: false, message: '' });
78
+ setSuccessToast({ visible: false, message: '' });
79
+ };
80
+
81
+ return (
82
+ <BottomModal
83
+ isVisible={visible}
84
+ onClose={handleClose}
85
+ heightMode="fixed"
86
+ fixedHeightPercent={88}
87
+ maxHeightPercent={90}
88
+ >
89
+ <View style={styles.contentWrap} className="bg-background-light dark:bg-background-darkSecondaryBg">
90
+ <View style={styles.toastWrapper} pointerEvents="box-none">
91
+ <Toast
92
+ key={toast.visible ? `toast-${toast.message}` : 'toast-hidden'}
93
+ message={toast.visible ? toast.message : ''}
94
+ visible={toast.visible}
95
+ type="error"
96
+ position="top"
97
+ onClose={() => {
98
+ setToast({ visible: false, message: '' });
99
+ clearError();
100
+ }}
101
+ />
102
+ <Toast
103
+ key={successToast.visible ? `success-${successToast.message}` : 'success-hidden'}
104
+ message={successToast.visible ? successToast.message : ''}
105
+ visible={successToast.visible}
106
+ type="success"
107
+ position="top"
108
+ onClose={() => setSuccessToast({ visible: false, message: '' })}
109
+ />
110
+ </View>
111
+
112
+ <ModalTitle
113
+ heading={t('newConnection')}
114
+ showCloseIcon
115
+ onClose={handleClose}
116
+ />
117
+
118
+ <View style={styles.body}>
119
+ <KeyboardAwareScrollView
120
+ style={styles.scrollView}
121
+ contentContainerStyle={styles.scrollContent}
122
+ keyboardShouldPersistTaps="handled"
123
+ enableOnAndroid={true}
124
+ extraScrollHeight={60}
125
+ extraHeight={120}
126
+ >
127
+ <View style={styles.searchSection}>
128
+ <Text className="text-sm font-semibold text-text-light-primary dark:text-text-dark-primary mb-2">{t('companyRegNumber')}</Text>
129
+ <View style={styles.searchRow}>
130
+ <View style={styles.searchInputWrap}>
131
+ <SearchFilter
132
+ placeholder="Search Company By ID"
133
+ value={searchNum}
134
+ onChangeText={setSearchNum}
135
+ />
136
+ </View>
137
+ <Button
138
+ title={t('search')}
139
+ type="primary"
140
+ size="md"
141
+ onPress={handleSearch}
142
+ icon={{ name: 'search', type: 'Feather' }}
143
+ iconPosition="left"
144
+ useSafeArea={false}
145
+ />
146
+ </View>
147
+ <Text className="text-[11px] text-text-light-subHeading dark:text-text-dark-subHeading italic mt-2">
148
+ {t('resultsValidated')}
149
+ </Text>
150
+ </View>
151
+
152
+ <View key={`search-${isSearching}-${searchResult?.id ?? 'none'}`} style={styles.resultBlock}>
153
+ {isSearching && (
154
+ <Loader
155
+ message={t('searching')}
156
+ size="large"
157
+ className="mb-6"
158
+ show={true}
159
+ />
160
+ )}
161
+
162
+ {!isSearching && searchResult != null && (
163
+ <View className="rounded-2xl border border-background-lightGray dark:border-background-darkGray bg-background-light dark:bg-background-darkSecondaryBg overflow-hidden mb-6">
164
+ <View className="px-4 py-3 flex-row items-center justify-between border-b border-background-lightGray dark:border-background-darkGray bg-background-secondaryBg dark:bg-background-darkSecondaryBg">
165
+ <Text className="text-xs font-medium text-text-light-secondary dark:text-text-dark-secondary uppercase">{t('profileFound')}</Text>
166
+ <View className="flex-row items-center gap-1.5 px-2.5 py-1 rounded-full bg-green-100 dark:bg-background-darkGreen">
167
+ <Text className="text-[11px] font-bold text-green-700 dark:text-green-100 uppercase">{t('verified')}</Text>
168
+ </View>
169
+ </View>
170
+ <View className="p-4">
171
+ <Text className="text-lg font-bold text-text-light-primary dark:text-text-dark-primary mb-4">
172
+ {searchResult.display_name}
173
+ </Text>
174
+ <View className="space-y-4">
175
+ <View className="flex-row justify-between mb-4">
176
+ <View className="flex-1">
177
+ <Text className="text-[10px] font-bold text-text-light-subHeading dark:text-text-dark-subHeading uppercase">{t('regId')}</Text>
178
+ <Text className="text-sm font-medium text-text-light-primary dark:text-text-dark-primary">{searchResult.company_num}</Text>
179
+ </View>
180
+ <View className="flex-1">
181
+ <Text className="text-[10px] font-bold text-text-light-subHeading dark:text-text-dark-subHeading uppercase">{t('email')}</Text>
182
+ <Text className="text-sm font-medium text-text-light-primary dark:text-text-dark-primary" numberOfLines={1}>
183
+ {searchResult.email}
184
+ </Text>
185
+ </View>
186
+ </View>
187
+ <View className="flex-row justify-between">
188
+ <View className="flex-1">
189
+ <Text className="text-[10px] font-bold text-text-light-subHeading dark:text-text-dark-subHeading uppercase">{t('phone')}</Text>
190
+ <Text className="text-sm font-medium text-text-light-primary dark:text-text-dark-primary">{searchResult.phone}</Text>
191
+ </View>
192
+ <View className="flex-1">
193
+ <Text className="text-[10px] font-bold text-text-light-subHeading dark:text-text-dark-subHeading uppercase">{t('location')}</Text>
194
+ <Text className="text-sm font-medium text-text-light-primary dark:text-text-dark-primary">{searchResult.city}</Text>
195
+ </View>
196
+ </View>
197
+ </View>
198
+ <Button
199
+ title={t('clearSelection')}
200
+ type="outline"
201
+ size="md"
202
+ onPress={clearSearch}
203
+ className="mt-4"
204
+ useSafeArea={false}
205
+ />
206
+ </View>
207
+ </View>
208
+ )}
209
+ </View>
210
+
211
+ <View style={styles.messageSection}>
212
+ <TextArea
213
+ label={t('professionalMessage')}
214
+ placeholder={t('messagePlaceholder')}
215
+ value={message}
216
+ onChangeText={setMessage}
217
+ maxLength={512}
218
+ showCharCount={true}
219
+ />
220
+ </View>
221
+ </KeyboardAwareScrollView>
222
+ <View className="px-4 py-4 border-t border-background-lightGray dark:border-background-darkGray bg-gray-100 dark:bg-background-darkSecondaryBg">
223
+ <Button
224
+ title={isSubmitting ? t('sending') : t('sendRequest')}
225
+ type="primary"
226
+ size="lg"
227
+ onPress={handleSendRequest}
228
+ disabled={!searchResult || isSubmitting}
229
+ loading={isSubmitting}
230
+ icon={!isSubmitting ? { name: 'send', type: 'Feather' } : undefined}
231
+ iconPosition="right"
232
+ useSafeArea={false}
233
+ />
234
+ </View>
235
+ </View>
236
+ </View>
237
+ </BottomModal>
238
+ );
239
+ };
240
+
241
+ const styles = StyleSheet.create({
242
+ contentWrap: {
243
+ flex: 1,
244
+ },
245
+ body: {
246
+ flex: 1,
247
+ },
248
+ toastWrapper: {
249
+ position: 'absolute',
250
+ top: 0,
251
+ left: 24,
252
+ right: 24,
253
+ zIndex: 10000,
254
+ },
255
+ scrollView: {
256
+ flex: 1,
257
+ },
258
+ scrollContent: {
259
+ paddingHorizontal: 16,
260
+ paddingBottom: 24,
261
+ },
262
+ searchSection: {
263
+ marginBottom: 20,
264
+ paddingVertical: 8,
265
+ },
266
+ searchRow: {
267
+ flexDirection: 'row',
268
+ alignItems: 'center',
269
+ },
270
+ searchInputWrap: {
271
+ flex: 1,
272
+ marginRight: 8,
273
+ },
274
+ resultBlock: {
275
+ marginBottom: 16,
276
+ },
277
+ messageSection: {
278
+ marginBottom: 16,
279
+ },
280
+ });
281
+
282
+ export default NewConnectionModal;
@@ -0,0 +1,22 @@
1
+ import { axiosInstance } from './api';
2
+
3
+ export const ApiService = {
4
+ get: async (url: string, params?: Record<string, any>) => {
5
+ const response = await axiosInstance.get(url, { params });
6
+ return response.data;
7
+ },
8
+
9
+ post: async (url: string, body: unknown) => {
10
+ const response = await axiosInstance.post(url, body);
11
+ return response.data;
12
+ },
13
+
14
+ patch: async (url: string, body: unknown) => {
15
+ const response = await axiosInstance.patch(url, body);
16
+ return response.data;
17
+ },
18
+
19
+ fetchList: async (url: string, params?: Record<string, any>) => {
20
+ return ApiService.get(url, params);
21
+ },
22
+ };
@@ -0,0 +1,81 @@
1
+ import { ApiService } from './ApiService';
2
+ import { API_ENDPOINTS } from '@/config';
3
+ import type { ConnectionFilterParams } from '@/types/connection';
4
+
5
+ export const ConnectionService = {
6
+ getConnections: async (page = 1, limit = 10, filters?: ConnectionFilterParams | null) => {
7
+ const params: Record<string, any> = {
8
+ sort_by: 'updated_at',
9
+ sort_mode: 'desc',
10
+ page_number: page,
11
+ page_limit: limit,
12
+ };
13
+ if (filters?.connection_status) params.connection_status = filters.connection_status;
14
+ if (filters?.direction) params.direction = filters.direction;
15
+ try {
16
+ return await ApiService.fetchList(API_ENDPOINTS.getConnections, params);
17
+ } catch (error) {
18
+ throw error;
19
+ }
20
+ },
21
+
22
+ createRequest: async (body: {
23
+ requested_company_id: number;
24
+ request_message?: string;
25
+ expires_at?: string;
26
+ }) => {
27
+ try {
28
+ return await ApiService.post(API_ENDPOINTS.createConnectionRequest, {
29
+ id: 0,
30
+ ...body,
31
+ });
32
+ } catch (error) {
33
+ throw error;
34
+ }
35
+ },
36
+
37
+ searchByNum: async (num: string) => {
38
+ const url = `${API_ENDPOINTS.searchCompany}${num}`;
39
+ try {
40
+ return await ApiService.get(url);
41
+ } catch (error) {
42
+ throw error;
43
+ }
44
+ },
45
+
46
+ acceptConnection: async (id: number) => {
47
+ const url = API_ENDPOINTS.connectionAction(id, 'accept');
48
+ try {
49
+ return await ApiService.patch(url, {});
50
+ } catch (error) {
51
+ throw error;
52
+ }
53
+ },
54
+
55
+ rejectConnection: async (id: number) => {
56
+ const url = API_ENDPOINTS.connectionAction(id, 'reject');
57
+ try {
58
+ return await ApiService.patch(url, {});
59
+ } catch (error) {
60
+ throw error;
61
+ }
62
+ },
63
+
64
+ cancelConnection: async (id: number) => {
65
+ const url = API_ENDPOINTS.connectionAction(id, 'cancel');
66
+ try {
67
+ return await ApiService.patch(url, {});
68
+ } catch (error) {
69
+ throw error;
70
+ }
71
+ },
72
+
73
+ disconnectConnection: async (id: number) => {
74
+ const url = API_ENDPOINTS.connectionAction(id, 'disconnect');
75
+ try {
76
+ return await ApiService.patch(url, {});
77
+ } catch (error) {
78
+ throw error;
79
+ }
80
+ },
81
+ };
@@ -0,0 +1,83 @@
1
+ import axios, { type InternalAxiosRequestConfig } from 'axios';
2
+ import { getData, removeData } from '@/utils/AsyncStorageUtils';
3
+ import { BASE_URL } from '@/config';
4
+
5
+ const DEFAULT_BASE = BASE_URL;
6
+
7
+ export const axiosInstance = axios.create({
8
+ baseURL: DEFAULT_BASE,
9
+ headers: {
10
+ accept: 'application/json',
11
+ 'Content-Type': 'application/json',
12
+ },
13
+ });
14
+
15
+ function toNum(v: string | number | null | undefined): number | null {
16
+ if (v == null) return null;
17
+ const n = typeof v === 'number' ? v : Number(v);
18
+ return Number.isNaN(n) ? null : n;
19
+ }
20
+
21
+ axiosInstance.interceptors.request.use(
22
+ async (config: InternalAxiosRequestConfig) => {
23
+ const tokenRaw = await getData<string>('token');
24
+ let token: string | null =
25
+ tokenRaw != null && typeof tokenRaw === 'string' ? tokenRaw : null;
26
+ const apiBaseRaw = await getData<string>('api_base_url');
27
+ const base =
28
+ apiBaseRaw != null && typeof apiBaseRaw === 'string' && apiBaseRaw.trim()
29
+ ? apiBaseRaw.trim().replace(/\/+$/, '')
30
+ : null;
31
+ if (base) {
32
+ if (/\/platform\/api\/online$/i.test(base)) {
33
+ config.baseURL = base;
34
+ } else {
35
+ config.baseURL = `${base}/platform/api/online`;
36
+ }
37
+ }
38
+ if (__DEV__) {
39
+ const method = (config.method ?? 'get').toUpperCase();
40
+ const fullUrl = config.baseURL && config.url ? `${config.baseURL}${config.url}` : config.url ?? '';
41
+ console.log('[Connection API]', method, fullUrl);
42
+ }
43
+ const region_id = toNum((await getData('region_id')) as number | string | null);
44
+ const company_id = toNum((await getData('company_id')) as number | string | null);
45
+ const staff_id = toNum((await getData('staff_id')) as number | string | null);
46
+ const user_id = toNum((await getData('user_id')) as number | string | null);
47
+ const bu_id = toNum((await getData('bu_id')) as number | string | null);
48
+
49
+ if (token && typeof token === 'string') {
50
+ let cleanToken = token.trim();
51
+ if (cleanToken.startsWith('"') && cleanToken.endsWith('"')) {
52
+ cleanToken = cleanToken.slice(1, -1);
53
+ }
54
+ if (cleanToken && cleanToken !== 'null' && cleanToken !== 'undefined') {
55
+ const authValue = cleanToken.startsWith('Bearer ')
56
+ ? cleanToken
57
+ : `Bearer ${cleanToken}`;
58
+ config.headers.set('Authorization', authValue);
59
+ config.headers.set('authorization', authValue);
60
+ }
61
+ }
62
+ if (region_id != null) config.headers.set('region_id', String(region_id));
63
+ if (company_id != null) config.headers.set('company_id', String(company_id));
64
+ if (staff_id != null) config.headers.set('staff_id', String(staff_id));
65
+ if (user_id != null) config.headers.set('user_id', String(user_id));
66
+ if (bu_id != null) config.headers.set('bu_id', String(bu_id));
67
+
68
+ return config;
69
+ },
70
+ (error: unknown) => Promise.reject(error)
71
+ );
72
+
73
+ axiosInstance.interceptors.response.use(
74
+ (response) => response,
75
+ async (error: { response?: { status?: number } }) => {
76
+ if (error?.response?.status === 401) {
77
+ await removeData('token').catch(() => { });
78
+ }
79
+ return Promise.reject(error);
80
+ }
81
+ );
82
+
83
+ export default axiosInstance;
@@ -0,0 +1,3 @@
1
+ import { useConnectionStore } from '@suflon/native-ui';
2
+ export { useConnectionStore };
3
+ export * from '@suflon/native-ui';
@@ -0,0 +1,54 @@
1
+ import { I18nManager, NativeModules } from 'react-native';
2
+ import { create } from 'zustand';
3
+ import { getData, saveData } from '@/utils/AsyncStorageUtils';
4
+
5
+ export type LanguageCode = 'en' | 'hi' | 'ar';
6
+
7
+ interface LanguageState {
8
+ language: LanguageCode;
9
+ setLanguage: (lang: LanguageCode) => Promise<void>;
10
+ /** Call once on app init to load saved language from AsyncStorage */
11
+ loadSavedLanguage: () => Promise<void>;
12
+ }
13
+
14
+ /** Reload the app so RTL/LTR layout change takes effect (required by React Native) */
15
+ function reloadApp() {
16
+ if (__DEV__ && NativeModules.DevSettings?.reload) {
17
+ NativeModules.DevSettings.reload();
18
+ return;
19
+ }
20
+ // Production: use react-native-restart if installed: require('react-native-restart').Restart();
21
+ if (NativeModules.DevSettings?.reload) {
22
+ NativeModules.DevSettings.reload();
23
+ }
24
+ }
25
+
26
+ export const useLanguageStore = create<LanguageState>((set) => ({
27
+ language: 'en',
28
+
29
+ setLanguage: async (lang: LanguageCode) => {
30
+ const wantRTL = lang === 'ar';
31
+ const wasRTL = I18nManager.isRTL;
32
+
33
+ set({ language: lang });
34
+ await saveData('language', lang);
35
+
36
+ // Switch layout direction when moving to/from Arabic (RTL requires app reload in RN)
37
+ if (wantRTL !== wasRTL) {
38
+ I18nManager.allowRTL(true);
39
+ I18nManager.forceRTL(wantRTL);
40
+ reloadApp();
41
+ }
42
+ },
43
+
44
+ loadSavedLanguage: async () => {
45
+ try {
46
+ const saved = await getData<string>('language');
47
+ if (saved === 'en' || saved === 'hi' || saved === 'ar') {
48
+ set({ language: saved });
49
+ }
50
+ } catch {
51
+ // keep default 'en'
52
+ }
53
+ },
54
+ }));
@@ -0,0 +1,56 @@
1
+ export const themeColors = {
2
+ brand: {
3
+ 50: '#f5f3ff',
4
+ 100: '#ede9fe',
5
+ 200: '#ddd6fe',
6
+ 400: '#a78bfa',
7
+ 500: '#8b5cf6',
8
+ 600: '#7c3aed',
9
+ 700: '#6d28d9',
10
+ 900: '#4c1d95',
11
+ DEFAULT: '#7c3aed',
12
+ },
13
+ slate: {
14
+ 50: '#f8fafc',
15
+ 100: '#f1f5f9',
16
+ 200: '#e2e8f0',
17
+ 300: '#cbd5e1',
18
+ 400: '#94a3b8',
19
+ 500: '#64748b',
20
+ 600: '#475569',
21
+ 700: '#334155',
22
+ 800: '#1e293b',
23
+ 900: '#0f172a',
24
+ 950: '#020617',
25
+ },
26
+ emerald: {
27
+ 50: '#ecfdf5',
28
+ 100: '#d1fae5',
29
+ 500: '#10b981',
30
+ 600: '#059669',
31
+ 700: '#047857',
32
+ 800: '#065f46',
33
+ },
34
+ amber: {
35
+ 50: '#fffbeb',
36
+ 100: '#fef3c7',
37
+ 500: '#f59e0b',
38
+ 800: '#92400e',
39
+ },
40
+ rose: {
41
+ 100: '#ffe4e6',
42
+ 500: '#f43f5e',
43
+ 800: '#9f1239',
44
+ },
45
+ blue: {
46
+ 100: '#dbeafe',
47
+ 400: '#60a5fa',
48
+ 600: '#2563eb',
49
+ 700: '#1d4ed8',
50
+ 900: '#1e3a8a',
51
+ },
52
+ white: '#ffffff',
53
+ transparent: 'transparent',
54
+ };
55
+
56
+ export type ThemeColors = typeof themeColors;
@@ -0,0 +1,69 @@
1
+ /** Connection status filter – matches backend IConnectionStatusEnum */
2
+ export type ConnectionStatusFilter =
3
+ | 'PENDING'
4
+ | 'ACCEPTED'
5
+ | 'REJECTED'
6
+ | 'CANCELLED'
7
+ | 'EXPIRED'
8
+ | 'DISCONNECTED';
9
+
10
+ /** Direction filter – matches backend IDirectionEnum */
11
+ export type DirectionFilter = 'INCOMING' | 'OUTGOING';
12
+
13
+ export interface ConnectionFilterParams {
14
+ connection_status?: ConnectionStatusFilter | null;
15
+ direction?: DirectionFilter | null;
16
+ }
17
+
18
+ /** Connection status filter – matches backend IConnectionStatusEnum */
19
+ export type ConnectionStatus =
20
+ | 'PENDING'
21
+ | 'ACCEPTED'
22
+ | 'REJECTED'
23
+ | 'CANCELLED'
24
+ | 'EXPIRED'
25
+ | 'DISCONNECTED';
26
+
27
+ /** Direction filter – matches backend IDirectionEnum */
28
+ export type Direction = 'INCOMING' | 'OUTGOING';
29
+
30
+ export interface ConnectionFilterParams {
31
+ connection_status?: ConnectionStatus;
32
+ direction?: Direction;
33
+ }
34
+
35
+ export interface CompanyBasic {
36
+ id: number;
37
+ sysid: string;
38
+ slug: string;
39
+ display_name: string;
40
+ company_num: string;
41
+ logo: string;
42
+ phone: string;
43
+ email: string;
44
+ business_reg_address: string;
45
+ city: string;
46
+ country: string;
47
+ }
48
+
49
+ export interface Connection {
50
+ id: number;
51
+ sysid: string;
52
+ requesting_company_id: number;
53
+ requested_company_id: number;
54
+ connection_status: ConnectionStatus;
55
+ requested_at: string;
56
+ connected_at: string | null;
57
+ direction: Direction;
58
+ request_message: string;
59
+ response_message: string | null;
60
+ is_active: boolean;
61
+ requesting_company: CompanyBasic;
62
+ requested_company: CompanyBasic;
63
+ }
64
+
65
+ export interface CompanyDetails extends CompanyBasic {
66
+ is_active: boolean;
67
+ is_verified: boolean;
68
+ // Add other fields from the schema as needed
69
+ }