diff --git a/src/app/index.tsx b/src/app/index.tsx
index a50dd1b..151fd5c 100644
--- a/src/app/index.tsx
+++ b/src/app/index.tsx
@@ -5,10 +5,12 @@ import { App } from './app';
import { initAxios } from '@/shared/configs/axios';
import { initSentry } from '@/shared/configs/sentry';
import { AppProvider } from './providers/app-provider';
+// import appBridge from '@/shared/lib/appBridge';
const initApp = async () => {
initAxios();
initSentry();
+ // appBridge.sendMessage('login')
};
(async () => {
diff --git a/src/entities/transaction/ui/filter/billing-filter.tsx b/src/entities/transaction/ui/filter/billing-filter.tsx
index 5e67460..49567f0 100644
--- a/src/entities/transaction/ui/filter/billing-filter.tsx
+++ b/src/entities/transaction/ui/filter/billing-filter.tsx
@@ -1,3 +1,4 @@
+import moment from 'moment';
import { ChangeEvent, useState } from 'react';
import { motion } from 'framer-motion';
import { IMAGE_ROOT } from '@/shared/constants/common';
@@ -9,8 +10,7 @@ import {
BillingSearchType
} from '../../model/types';
import { FilterDateOptions } from '@/entities/common/model/types';
-import moment from 'moment';
-import NiceCalendar from '@/shared/ui/calendar';
+import { FilterCalendar } from '@/shared/ui/calendar/filter-calendar';
export const BillingFilter = ({
filterOn,
@@ -163,73 +163,13 @@ export const BillingFilter = ({
/>
-
+
-
조회기간
-
-
- setFilterDate(FilterDateOptions.Today) }
- >당일
- setFilterDate(FilterDateOptions.Week) }
- >일주일
- setFilterDate(FilterDateOptions.Month) }
- >1개월
- setFilterDate(FilterDateOptions.Input) }
- >직접입력
-
-
-
-
-
-
-
~
-
-
-
-
-
-
-
-
-
요청상태
@@ -327,10 +267,6 @@ export const BillingFilter = ({
>적용
-
>
);
diff --git a/src/shared/lib/appBridge.ts b/src/shared/lib/appBridge.ts
new file mode 100644
index 0000000..d335caf
--- /dev/null
+++ b/src/shared/lib/appBridge.ts
@@ -0,0 +1,255 @@
+import {
+ AppBridgeMessage,
+ AppBridgeResponse,
+ BridgeMessageType,
+ DeviceInfo,
+ ShareContent
+} from '@/types';
+import { LoginCredentials, UserInfo } from '@/types/auth';
+
+class AppBridge {
+ private static instance: AppBridge;
+ private messageId = 0;
+ private pendingCallbacks = new Map
void>();
+ private responseListeners: Set<(response: AppBridgeResponse) => void> = new Set();
+
+ private constructor() {
+ this.setupMessageListener();
+ }
+
+ // 외부에서 네이티브 응답을 구독할 수 있도록 리스너 등록/해제 메서드 추가
+ public addResponseListener(listener: (response: AppBridgeResponse) => void) {
+ this.responseListeners.add(listener);
+ }
+
+ public removeResponseListener(listener: (response: AppBridgeResponse) => void) {
+ this.responseListeners.delete(listener);
+ }
+
+ static getInstance(): AppBridge {
+ if (!AppBridge.instance) {
+ AppBridge.instance = new AppBridge();
+ }
+ return AppBridge.instance;
+ }
+
+ private setupMessageListener(): void {
+ window.addEventListener('message', (event) => {
+ try {
+ const response: AppBridgeResponse & { callbackId?: string } = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
+ console.log('response', response);
+ if (response.callbackId && this.pendingCallbacks.has(response.callbackId)) {
+ const callback = this.pendingCallbacks.get(response.callbackId);
+ if (callback) {
+ callback(response);
+ this.pendingCallbacks.delete(response.callbackId);
+ }
+ }
+ // 등록된 리스너들에게 모든 응답 전달
+ this.responseListeners.forEach(listener => listener(response));
+ } catch (error) {
+ console.error('Failed to parse bridge message:', error);
+ }
+ });
+ }
+
+ private generateMessageId(): string {
+ return `bridge_${++this.messageId}_${Date.now()}`;
+ }
+
+ private sendMessage(type: BridgeMessageType, data?: unknown): Promise {
+ return new Promise((resolve, reject) => {
+ const callbackId = this.generateMessageId();
+ const message: AppBridgeMessage & { callbackId: string } = {
+ type,
+ data,
+ callbackId
+ };
+
+ this.pendingCallbacks.set(callbackId, (response: AppBridgeResponse) => {
+ if (response.success) {
+ resolve(response.data);
+ } else {
+ reject(new Error(response.error || 'Bridge call failed'));
+ }
+ });
+
+ // Android WebView 인터페이스
+ if (window.AndroidBridge && window.AndroidBridge.postMessage) {
+ console.log('Android postMessage', message);
+ window.AndroidBridge.postMessage(JSON.stringify(message));
+ return;
+ }
+
+ // iOS WKWebView 인터페이스
+ if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge) {
+ console.log('iOS postMessage', message);
+ window.webkit.messageHandlers.bridge.postMessage(message);
+ return;
+ }
+
+ // 네이티브 환경이 아닌 경우 에러 발생
+ setTimeout(() => {
+ this.pendingCallbacks.delete(callbackId);
+ reject(new Error('Native bridge not available'));
+ }, 100);
+ });
+ }
+
+ // 앱 정보 관련
+ async getAppInfo(): Promise<{ version: string; buildNumber: string }> {
+ return this.sendMessage(BridgeMessageType.GET_APP_INFO);
+ }
+
+ async getDeviceInfo(): Promise {
+ return this.sendMessage(BridgeMessageType.GET_DEVICE_INFO);
+ }
+
+ // 네비게이션 관련
+ async navigateBack(): Promise {
+ return this.sendMessage(BridgeMessageType.NAVIGATE_BACK);
+ }
+
+ async navigateTo(path: string): Promise {
+ return this.sendMessage(BridgeMessageType.NAVIGATE_TO, { path });
+ }
+
+ async navigateToLogin(): Promise {
+ return this.sendMessage(BridgeMessageType.NAVIGATE_TO_LOGIN);
+ }
+
+ async closeWebView(): Promise {
+ return this.sendMessage(BridgeMessageType.CLOSE_WEBVIEW);
+ }
+
+ // 알림 관련
+ async showToast(message: string, duration: number = 3000): Promise {
+ return this.sendMessage(BridgeMessageType.SHOW_TOAST, { message, duration });
+ }
+
+ async showAlert(title: string, message: string): Promise {
+ return this.sendMessage(BridgeMessageType.SHOW_ALERT, { title, message });
+ }
+
+ async showConfirm(title: string, message: string): Promise {
+ return this.sendMessage(BridgeMessageType.SHOW_CONFIRM, { title, message });
+ }
+
+ // 저장소 관련
+ async setStorage(key: string, value: unknown): Promise {
+ return this.sendMessage(BridgeMessageType.SET_STORAGE, { key, value: JSON.stringify(value) });
+ }
+
+ async getStorage(key: string): Promise {
+ try {
+ const result = await this.sendMessage(BridgeMessageType.GET_STORAGE, { key });
+ return result ? JSON.parse(result) : null;
+ } catch {
+ return null;
+ }
+ }
+
+ async removeStorage(key: string): Promise {
+ return this.sendMessage(BridgeMessageType.REMOVE_STORAGE, { key });
+ }
+
+ // 공유 관련
+ async shareContent(content: ShareContent): Promise {
+ return this.sendMessage(BridgeMessageType.SHARE_CONTENT, content);
+ }
+
+ // 로그인 요청
+ async login(credentials?: LoginCredentials): Promise {
+ return this.sendMessage(BridgeMessageType.LOGIN, credentials);
+ }
+
+ async logout(): Promise {
+ return this.sendMessage(BridgeMessageType.LOGOUT);
+ }
+
+ // 언어 설정
+ async setLanguage(language: string): Promise {
+ return this.sendMessage(BridgeMessageType.SET_LANGUAGE, { language });
+ }
+
+ async getLanguage(): Promise {
+ return this.sendMessage(BridgeMessageType.GET_LANGUAGE);
+ }
+
+ // 메시지 카운트 업데이트
+ async updateMessageCount(count: number): Promise {
+ return this.sendMessage(BridgeMessageType.UPDATE_MESSAGE_COUNT, { count });
+ }
+
+ // 네이티브 환경 체크
+ isNativeEnvironment(): boolean {
+ return !!(
+ (window.AndroidBridge && window.AndroidBridge.postMessage) ||
+ (window.Android && window.Android.processMessage) ||
+ (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge)
+ );
+ }
+
+ isAndroid(): boolean {
+ return !!(
+ (window.AndroidBridge && window.AndroidBridge.postMessage) ||
+ (window.Android && window.Android.processMessage)
+ );
+ }
+
+ isIOS(): boolean {
+ return !!(window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.bridge);
+ }
+
+ // 에러 핸들링을 위한 래퍼 메소드들
+ async safeCall(
+ bridgeMethod: () => Promise,
+ fallback?: T,
+ onError?: (error: Error) => void
+ ): Promise {
+ try {
+ return await bridgeMethod();
+ } catch (error) {
+ console.error('Bridge call failed:', error);
+ if (onError) {
+ onError(error as Error);
+ }
+ return fallback;
+ }
+ }
+
+ // 타임아웃을 가진 브리지 호출
+ async callWithTimeout(
+ bridgeMethod: () => Promise,
+ timeout: number = 5000
+ ): Promise {
+ return Promise.race([
+ bridgeMethod(),
+ new Promise((_, reject) =>
+ setTimeout(() => reject(new Error('Bridge call timeout')), timeout)
+ )
+ ]);
+ }
+}
+
+// 글로벌 타입 선언 확장
+declare global {
+ interface Window {
+ AndroidBridge?: {
+ postMessage: (message: string) => void;
+ };
+ Android?: {
+ processMessage: (message: string) => void;
+ };
+ webkit?: {
+ messageHandlers?: {
+ bridge?: {
+ postMessage: (message: unknown) => void;
+ };
+ };
+ };
+ }
+}
+
+export const appBridge = AppBridge.getInstance();
+export default appBridge;
\ No newline at end of file
diff --git a/src/shared/ui/calendar/filter-calendar.tsx b/src/shared/ui/calendar/filter-calendar.tsx
new file mode 100644
index 0000000..2a097c7
--- /dev/null
+++ b/src/shared/ui/calendar/filter-calendar.tsx
@@ -0,0 +1,128 @@
+import moment from 'moment';
+import { ChangeEvent, useState } from 'react';
+import { FilterDateOptions } from '@/entities/common/model/types';
+import { IMAGE_ROOT } from '@/shared/constants/common';
+import NiceCalendar from './nice-calendar';
+
+interface FilterCalendarProps {
+ startDate: string;
+ endDate: string;
+ setStartDate: (startDate: string) => void;
+ setEndDate: (endDate: string) => void;
+};
+
+export const FilterCalendar = ({
+ startDate,
+ endDate,
+ setStartDate,
+ setEndDate
+}: FilterCalendarProps) => {
+ const [dateReadOnly, setDateReadyOnly] = useState(true);
+ const [filterDateOptionsBtn, setFilterDateOptionsBtn] = useState(FilterDateOptions.Input);
+ const [filterStartDate, setFilterStartDate] = useState(startDate);
+ const [filterEndDate, setFilterEndDate] = useState(endDate);
+ const [calendarOpen, setCalendarOpen] = useState(false);
+
+ const setFilterDate = (dateOptions: FilterDateOptions) => {
+ if(dateOptions === FilterDateOptions.Today){
+ setFilterStartDate(moment().format('YYYY-MM-DD'));
+ setFilterEndDate(moment().format('YYYY-MM-DD'));
+ setDateReadyOnly(true);
+ setFilterDateOptionsBtn(FilterDateOptions.Today);
+ }
+ else if(dateOptions === FilterDateOptions.Week){
+ setFilterStartDate(moment().subtract(1, 'week').format('YYYY-MM-DD'));
+ setFilterEndDate(moment().format('YYYY-MM-DD'));
+ setDateReadyOnly(true);
+ setFilterDateOptionsBtn(FilterDateOptions.Week);
+ }
+ else if(dateOptions === FilterDateOptions.Month){
+ setFilterStartDate(moment().subtract(1, 'month').format('YYYY-MM-DD'));
+ setFilterEndDate(moment().format('YYYY-MM-DD'));
+ setDateReadyOnly(true);
+ setFilterDateOptionsBtn(FilterDateOptions.Month);
+ }
+ else if(dateOptions === FilterDateOptions.Input){
+ setDateReadyOnly(false);
+ setFilterDateOptionsBtn(FilterDateOptions.Input);
+ }
+ };
+
+ const onClickToOpenCalendar = () => {
+ if(!dateReadOnly){
+ setCalendarOpen(true);
+ }
+ else{
+ setCalendarOpen(false);
+ }
+ };
+
+ return (
+ <>
+
+
조회기간
+
+
+ setFilterDate(FilterDateOptions.Today) }
+ >당일
+ setFilterDate(FilterDateOptions.Week) }
+ >일주일
+ setFilterDate(FilterDateOptions.Month) }
+ >1개월
+ setFilterDate(FilterDateOptions.Input) }
+ >직접입력
+
+
+
+
+
+
+
~
+
+
+
+
+
+
+
+ >
+ );
+};
\ No newline at end of file
diff --git a/src/shared/ui/calendar/index.tsx b/src/shared/ui/calendar/index.tsx
deleted file mode 100644
index 43c040c..0000000
--- a/src/shared/ui/calendar/index.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import moment from 'moment';
-import styled from "styled-components";
-import { useState } from 'react';
-import Calendar from 'react-calendar';
-import 'react-calendar/dist/Calendar.css';
-
-const NiceCalendar = ({
- setNewDate
-}: any) => {
- const [calendarDate, setCalendarDate] = useState(moment().format('YYYY-MM-DD'));
- const [isOpen, setIsOpen] = useState(false);
-
- const onchangeToDate = (selectedDate: any) => {
- setNewDate(selectedDate)
- setIsOpen(false);
- };
-
- return (
- <>
-
-
-
-
-
- >
- );
-};
-
-const CalendarContainer = styled.div`
- position: relative;
-`;
-const CalendarWrapper: any = styled.div`
- z-index: 11;
- position: absolute;
- top: 100%;
- left: 0;
- display: ${(props: any) => ((props.isOpen)? 'block': 'none')};
-`;
-
-export default NiceCalendar;
\ No newline at end of file
diff --git a/src/shared/ui/calendar/nice-calendar.tsx b/src/shared/ui/calendar/nice-calendar.tsx
new file mode 100644
index 0000000..da6f8ed
--- /dev/null
+++ b/src/shared/ui/calendar/nice-calendar.tsx
@@ -0,0 +1,31 @@
+import moment from 'moment';
+import styled from "styled-components";
+import { useState } from 'react';
+import Calendar from 'react-calendar';
+import 'react-calendar/dist/Calendar.css';
+
+const NiceCalendar = ({
+ calendarOpen,
+ setNewDate
+}: any) => {
+ const [calendarDate, setCalendarDate] = useState(moment().format('YYYY-MM-DD'));
+ const [isOpen, setIsOpen] = useState(calendarOpen);
+
+ const onchangeToDate = (selectedDate: any) => {
+ setNewDate(selectedDate)
+ setIsOpen(false);
+ };
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
+
+export default NiceCalendar;
\ No newline at end of file