@t4h.ds/service-conversation-card 0.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/dist/components/ServiceConversationCard/ServiceConversationCard.d.ts +5 -0
- package/dist/components/ServiceConversationCard/ServiceConversationCard.js +6 -0
- package/dist/components/ServiceConversationCard/ServiceConversationCardContainer.d.ts +10 -0
- package/dist/components/ServiceConversationCard/ServiceConversationCardContainer.js +15 -0
- package/dist/components/ServiceConversationCard/ServiceConversationCardLayout.d.ts +13 -0
- package/dist/components/ServiceConversationCard/ServiceConversationCardLayout.js +19 -0
- package/dist/components/ServiceConversationCard/ServiceConversationCardStatus.d.ts +7 -0
- package/dist/components/ServiceConversationCard/ServiceConversationCardStatus.js +8 -0
- package/dist/components/ServiceConversationCard/tokens.d.ts +1 -0
- package/dist/components/ServiceConversationCard/tokens.js +72 -0
- package/dist/components/ServiceConversationCard/useServiceConversationCardAnimation.d.ts +8 -0
- package/dist/components/ServiceConversationCard/useServiceConversationCardAnimation.js +16 -0
- package/dist/components/ServiceConversationCard/useServiceConversationCardState.d.ts +13 -0
- package/dist/components/ServiceConversationCard/useServiceConversationCardState.js +31 -0
- package/dist/service-conversation-card.d.ts +1 -0
- package/dist/service-conversation-card.js +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { PressableProps } from '@t4h.ds/pressable';
|
|
2
|
+
import { ServiceConversationCardContainerProps } from './ServiceConversationCardContainer.js';
|
|
3
|
+
export interface ServiceConversationCardProps extends Omit<PressableProps, 'children'>, Omit<ServiceConversationCardContainerProps, 'isHovered' | 'isPressed'> {
|
|
4
|
+
}
|
|
5
|
+
export declare function ServiceConversationCard(props: ServiceConversationCardProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Pressable } from '@t4h.ds/pressable';
|
|
3
|
+
import { ServiceConversationCardContainer, } from './ServiceConversationCardContainer.js';
|
|
4
|
+
export function ServiceConversationCard(props) {
|
|
5
|
+
return (_jsx(Pressable, { ...props, children: ({ isHovered, isPressed }) => (_jsx(ServiceConversationCardContainer, { ...props, isHovered: isHovered, isPressed: isPressed })) }));
|
|
6
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ServiceConversationCardLayoutProps } from './ServiceConversationCardLayout.js';
|
|
2
|
+
export interface ServiceConversationCardContainerProps extends Omit<ServiceConversationCardLayoutProps, 'status' | 'animations' | 'remainingTimeText' | 'showRemainingTimeTextAndUnreadMessageCount'> {
|
|
3
|
+
isHovered?: boolean;
|
|
4
|
+
isPressed?: boolean;
|
|
5
|
+
lastMessageAt: Date;
|
|
6
|
+
timeLimitInSeconds: number;
|
|
7
|
+
isStandby?: boolean;
|
|
8
|
+
isSelected?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function ServiceConversationCardContainer({ isHovered, isPressed, isSelected, lastMessageAt, timeLimitInSeconds, isStandby, ...props }: ServiceConversationCardContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ServiceConversationCardLayout, } from './ServiceConversationCardLayout.js';
|
|
3
|
+
import { useServiceConversationCardState } from './useServiceConversationCardState.js';
|
|
4
|
+
import { useServiceConversationCardAnimation } from './useServiceConversationCardAnimation.js';
|
|
5
|
+
import { ServiceConversationCardStatus } from './ServiceConversationCardStatus.js';
|
|
6
|
+
export function ServiceConversationCardContainer({ isHovered = false, isPressed = false, isSelected = false, lastMessageAt, timeLimitInSeconds, isStandby, ...props }) {
|
|
7
|
+
const { status, remainingTimeText } = useServiceConversationCardState({
|
|
8
|
+
lastMessageAt,
|
|
9
|
+
timeLimitInSeconds,
|
|
10
|
+
isStandby,
|
|
11
|
+
isSelected,
|
|
12
|
+
});
|
|
13
|
+
const animations = useServiceConversationCardAnimation(status, isHovered, isPressed);
|
|
14
|
+
return (_jsx(ServiceConversationCardLayout, { ...props, remainingTimeText: remainingTimeText, status: status, animations: animations, showRemainingTimeTextAndUnreadMessageCount: status !== ServiceConversationCardStatus.Standby && !isSelected, unreadMessageCount: props.unreadMessageCount }));
|
|
15
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { UseServiceConversationCardAnimation } from './useServiceConversationCardAnimation.js';
|
|
2
|
+
import { ServiceConversationCardStatus } from './ServiceConversationCardStatus.js';
|
|
3
|
+
import './tokens.js';
|
|
4
|
+
export interface ServiceConversationCardLayoutProps {
|
|
5
|
+
title: string;
|
|
6
|
+
message: string;
|
|
7
|
+
remainingTimeText: string;
|
|
8
|
+
status: ServiceConversationCardStatus;
|
|
9
|
+
animations: UseServiceConversationCardAnimation;
|
|
10
|
+
unreadMessageCount: number;
|
|
11
|
+
showRemainingTimeTextAndUnreadMessageCount: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function ServiceConversationCardLayout({ title, message, remainingTimeText, unreadMessageCount, showRemainingTimeTextAndUnreadMessageCount, status, animations, }: ServiceConversationCardLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from '@t4h.ds/core';
|
|
3
|
+
import { Animated, useAnimatedStyle } from '@t4h.ds/reanimated';
|
|
4
|
+
import { ServiceConversationCardStatus } from './ServiceConversationCardStatus.js';
|
|
5
|
+
import './tokens.js';
|
|
6
|
+
import { Avatar } from '@t4h.ds/avatar';
|
|
7
|
+
const colors = {
|
|
8
|
+
[ServiceConversationCardStatus.Normal]: 'green',
|
|
9
|
+
[ServiceConversationCardStatus.NearBreach]: 'yellow',
|
|
10
|
+
[ServiceConversationCardStatus.Breached]: 'red',
|
|
11
|
+
[ServiceConversationCardStatus.Standby]: 'grey',
|
|
12
|
+
[ServiceConversationCardStatus.Selected]: 'blue',
|
|
13
|
+
};
|
|
14
|
+
export function ServiceConversationCardLayout({ title, message, remainingTimeText, unreadMessageCount, showRemainingTimeTextAndUnreadMessageCount, status, animations, }) {
|
|
15
|
+
return (_jsxs(Box, { as: Animated.View, row: true, rounded: true, overflow: true, p: 'space.150', gap: 'space.150', borderWidth: 1, style: useAnimatedStyle(() => ({
|
|
16
|
+
backgroundColor: animations.backgroundColor.value,
|
|
17
|
+
borderColor: animations.borderColor.value,
|
|
18
|
+
})), children: [_jsx(Avatar, { name: title ?? '?', color: colors[status] }), _jsxs(Box, { flex: 1, gap: 'space.050', children: [_jsx(Text, { bold: true, numberOfLines: 1, lineBreakMode: 'tail', color: `service-conversation-card.${status}.color.title`, children: title }), _jsx(Text, { numberOfLines: 1, lineBreakMode: 'tail', color: `service-conversation-card.${status}.color.message`, children: message })] }), showRemainingTimeTextAndUnreadMessageCount && (_jsxs(Box, { children: [_jsx(Box, { children: _jsx(Text, { color: `service-conversation-card.${status}.color.icon`, children: remainingTimeText }) }), !!unreadMessageCount && (_jsx(Box, { ml: 'auto', rounded: 'full', bg: `service-conversation-card.${status}.color.icon`, p: 'space.050', center: true, height: 20, width: 20, children: _jsx(Text, { bold: true, color: '#FFFFFF', fontSize: 'xs', children: unreadMessageCount }) }))] }))] }));
|
|
19
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export var ServiceConversationCardStatus;
|
|
2
|
+
(function (ServiceConversationCardStatus) {
|
|
3
|
+
ServiceConversationCardStatus["Selected"] = "selected";
|
|
4
|
+
ServiceConversationCardStatus["Normal"] = "normal";
|
|
5
|
+
ServiceConversationCardStatus["NearBreach"] = "near-breach";
|
|
6
|
+
ServiceConversationCardStatus["Breached"] = "breached";
|
|
7
|
+
ServiceConversationCardStatus["Standby"] = "standby";
|
|
8
|
+
})(ServiceConversationCardStatus || (ServiceConversationCardStatus = {}));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Theme } from '@t4h.ds/core';
|
|
2
|
+
Theme.get()
|
|
3
|
+
.tokens.set({
|
|
4
|
+
'service-conversation-card.selected.color.icon': '@color.background.brand.bold',
|
|
5
|
+
'service-conversation-card.selected.color.title': '@color.background.brand.bold',
|
|
6
|
+
'service-conversation-card.selected.color.message': '#60646C',
|
|
7
|
+
'service-conversation-card.selected.color.background': '#F7F9FF',
|
|
8
|
+
'service-conversation-card.selected.hovered.color.background': '#EDF1FF',
|
|
9
|
+
'service-conversation-card.selected.pressed.color.background': '@service-conversation-card.selected.hovered.color.background',
|
|
10
|
+
'service-conversation-card.selected.color.icon.background': '#D2DDFF',
|
|
11
|
+
'service-conversation-card.selected.hovered.color.icon.background': '@service-conversation-card.selected.color.icon.background',
|
|
12
|
+
'service-conversation-card.selected.pressed.color.icon.background': '@service-conversation-card.selected.hovered.color.icon.background',
|
|
13
|
+
'service-conversation-card.selected.color.border': '#D2DDFF',
|
|
14
|
+
'service-conversation-card.selected.hovered.color.border': '@service-conversation-card.selected.color.border',
|
|
15
|
+
'service-conversation-card.selected.pressed.color.border': '@service-conversation-card.selected.hovered.color.border',
|
|
16
|
+
})
|
|
17
|
+
.set({
|
|
18
|
+
'service-conversation-card.normal.color.icon': '#2A7E3B',
|
|
19
|
+
'service-conversation-card.normal.color.title': '#1C2024',
|
|
20
|
+
'service-conversation-card.normal.color.message': '#60646C',
|
|
21
|
+
'service-conversation-card.normal.color.background': '#F5FBF5',
|
|
22
|
+
'service-conversation-card.normal.color.background.hovered': '#E9F6E9',
|
|
23
|
+
'service-conversation-card.normal.color.background.pressed': '@service-conversation-card.normal.color.background.hovered',
|
|
24
|
+
'service-conversation-card.normal.color.icon.background': '#B2DDB5',
|
|
25
|
+
'service-conversation-card.normal.hovered.color.icon.background': '@service-conversation-card.normal.color.icon.background',
|
|
26
|
+
'service-conversation-card.normal.pressed.color.icon.background': '@service-conversation-card.normal.hovered.color.icon.background',
|
|
27
|
+
'service-conversation-card.normal.color.border': '#E8E8EC',
|
|
28
|
+
'service-conversation-card.normal.hovered.color.border': '@service-conversation-card.normal.color.border',
|
|
29
|
+
'service-conversation-card.normal.pressed.color.border': '@service-conversation-card.normal.hovered.color.border',
|
|
30
|
+
})
|
|
31
|
+
.set({
|
|
32
|
+
'service-conversation-card.near-breach.color.icon': '#AB6400',
|
|
33
|
+
'service-conversation-card.near-breach.color.title': '#1C2024',
|
|
34
|
+
'service-conversation-card.near-breach.color.message': '#60646C',
|
|
35
|
+
'service-conversation-card.near-breach.color.background': '#FEFBE9',
|
|
36
|
+
'service-conversation-card.near-breach.hovered.color.background': '#FFF7C2',
|
|
37
|
+
'service-conversation-card.near-breach.pressed.color.background': '@service-conversation-card.near-breach.hovered.color.background',
|
|
38
|
+
'service-conversation-card.near-breach.color.icon.background': '#F3D673',
|
|
39
|
+
'service-conversation-card.near-breach.color.icon.background.hovered': '@service-conversation-card.near-breach.color.icon.background',
|
|
40
|
+
'service-conversation-card.near-breach.color.icon.background.pressed': '@service-conversation-card.near-breach.color.icon.background.hovered',
|
|
41
|
+
'service-conversation-card.near-breach.color.border': '#E8E8EC',
|
|
42
|
+
'service-conversation-card.near-breach.hovered.color.border': '@service-conversation-card.near-breach.color.border',
|
|
43
|
+
'service-conversation-card.near-breach.pressed.color.border': '@service-conversation-card.near-breach.hovered.color.border',
|
|
44
|
+
})
|
|
45
|
+
.set({
|
|
46
|
+
'service-conversation-card.breached.color.icon': '#E5484D',
|
|
47
|
+
'service-conversation-card.breached.color.title': '#1C2024',
|
|
48
|
+
'service-conversation-card.breached.color.message': '#60646C',
|
|
49
|
+
'service-conversation-card.breached.color.background': '#FFF7F7',
|
|
50
|
+
'service-conversation-card.breached.hovered.color.background': '#FEEBEC',
|
|
51
|
+
'service-conversation-card.breached.pressed.color.background': '@service-conversation-card.breached.hovered.color.background',
|
|
52
|
+
'service-conversation-card.breached.color.icon.background': '#FDBDBE',
|
|
53
|
+
'service-conversation-card.breached.hovered.color.icon.background': '@service-conversation-card.breached.color.icon.background',
|
|
54
|
+
'service-conversation-card.breached.pressed.color.icon.background': '@service-conversation-card.breached.hovered.color.icon.background',
|
|
55
|
+
'service-conversation-card.breached.color.border': '#E8E8EC',
|
|
56
|
+
'service-conversation-card.breached.hovered.color.border': '@service-conversation-card.breached.color.border',
|
|
57
|
+
'service-conversation-card.breached.pressed.color.border': '@service-conversation-card.breached.hovered.color.border',
|
|
58
|
+
})
|
|
59
|
+
.set({
|
|
60
|
+
'service-conversation-card.standby.color.icon': '#60646C',
|
|
61
|
+
'service-conversation-card.standby.color.title': '#1C2024',
|
|
62
|
+
'service-conversation-card.standby.color.message': '#60646C',
|
|
63
|
+
'service-conversation-card.standby.color.background': '#F9F9FB',
|
|
64
|
+
'service-conversation-card.standby.hovered.color.background': '@service-conversation-card.standby.color.background',
|
|
65
|
+
'service-conversation-card.standby.pressed.color.background': '@service-conversation-card.standby.hovered.color.background',
|
|
66
|
+
'service-conversation-card.standby.color.icon.background': '#D9D9E0',
|
|
67
|
+
'service-conversation-card.standby.hovered.color.icon.background': '@service-conversation-card.standby.color.icon.background',
|
|
68
|
+
'service-conversation-card.standby.pressed.color.icon.background': '@service-conversation-card.standby.hovered.color.icon.background',
|
|
69
|
+
'service-conversation-card.standby.color.border': '#E8E8EC',
|
|
70
|
+
'service-conversation-card.standby.hovered.color.border': '@service-conversation-card.standby.color.border',
|
|
71
|
+
'service-conversation-card.standby.pressed.color.border': '@service-conversation-card.standby.hovered.color.border',
|
|
72
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SharedValue } from '@t4h.ds/reanimated';
|
|
2
|
+
import { ServiceConversationCardStatus } from './ServiceConversationCardStatus.js';
|
|
3
|
+
export interface UseServiceConversationCardAnimation {
|
|
4
|
+
backgroundColor: SharedValue<string>;
|
|
5
|
+
borderColor: SharedValue<string>;
|
|
6
|
+
iconBackgroundColor: SharedValue<string>;
|
|
7
|
+
}
|
|
8
|
+
export declare function useServiceConversationCardAnimation(status: ServiceConversationCardStatus, isHovered: boolean, isPressed: boolean): UseServiceConversationCardAnimation;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useReanimatedTransitionThemeToken, } from '@t4h.ds/reanimated';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
export function useServiceConversationCardAnimation(status, isHovered, isPressed) {
|
|
4
|
+
const tag = useMemo(() => {
|
|
5
|
+
const tag = `service-conversation-card.${status}`;
|
|
6
|
+
if (isPressed)
|
|
7
|
+
return `${tag}.pressed`;
|
|
8
|
+
if (isHovered)
|
|
9
|
+
return `${tag}.hovered`;
|
|
10
|
+
return tag;
|
|
11
|
+
}, [status, isPressed, isHovered]);
|
|
12
|
+
const backgroundColor = useReanimatedTransitionThemeToken(`${tag}.color.background`, { duration: 300 });
|
|
13
|
+
const borderColor = useReanimatedTransitionThemeToken(`${tag}.color.border`, { duration: 300 });
|
|
14
|
+
const iconBackgroundColor = useReanimatedTransitionThemeToken(`${tag}.color.icon.background`, { duration: 300 });
|
|
15
|
+
return { backgroundColor, borderColor, iconBackgroundColor };
|
|
16
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ServiceConversationCardStatus } from './ServiceConversationCardStatus.js';
|
|
2
|
+
export interface UseServiceConversationCardState {
|
|
3
|
+
remainingTimeText: string;
|
|
4
|
+
diffInSeconds: number;
|
|
5
|
+
status: ServiceConversationCardStatus;
|
|
6
|
+
}
|
|
7
|
+
export interface UseServiceConversationCardStateProps {
|
|
8
|
+
lastMessageAt: Date;
|
|
9
|
+
timeLimitInSeconds: number;
|
|
10
|
+
isStandby?: boolean;
|
|
11
|
+
isSelected?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function useServiceConversationCardState({ lastMessageAt, timeLimitInSeconds, isStandby, isSelected, }: UseServiceConversationCardStateProps): UseServiceConversationCardState;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useAnimationFrameState } from '@t4h.ds/animation-frame';
|
|
3
|
+
import { ServiceConversationCardStatus } from './ServiceConversationCardStatus.js';
|
|
4
|
+
export function useServiceConversationCardState({ lastMessageAt, timeLimitInSeconds, isStandby, isSelected, }) {
|
|
5
|
+
const [remainingTimeText, diffInSeconds] = useAnimationFrameState(() => {
|
|
6
|
+
const now = new Date();
|
|
7
|
+
const expireAtTime = lastMessageAt.getTime() + timeLimitInSeconds * 1000;
|
|
8
|
+
const nowTime = now.getTime();
|
|
9
|
+
const diffInSeconds = Math.floor((expireAtTime - nowTime) / 1000);
|
|
10
|
+
const minutes = Math.abs(Math.floor(diffInSeconds / 60))
|
|
11
|
+
.toString()
|
|
12
|
+
.padStart(2, '0');
|
|
13
|
+
const seconds = Math.abs(diffInSeconds % 60)
|
|
14
|
+
.toString()
|
|
15
|
+
.padStart(2, '0');
|
|
16
|
+
const display = diffInSeconds < 0 ? `-${minutes}:${seconds}` : `${minutes}:${seconds}`;
|
|
17
|
+
return [display, diffInSeconds];
|
|
18
|
+
}, { maxFps: 3 }, [lastMessageAt, timeLimitInSeconds]);
|
|
19
|
+
const status = useMemo(() => {
|
|
20
|
+
if (isSelected)
|
|
21
|
+
return ServiceConversationCardStatus.Selected;
|
|
22
|
+
if (isStandby)
|
|
23
|
+
return ServiceConversationCardStatus.Standby;
|
|
24
|
+
if (diffInSeconds > timeLimitInSeconds * 0.8)
|
|
25
|
+
return ServiceConversationCardStatus.Normal;
|
|
26
|
+
if (diffInSeconds > timeLimitInSeconds * 0.5)
|
|
27
|
+
return ServiceConversationCardStatus.NearBreach;
|
|
28
|
+
return ServiceConversationCardStatus.Breached;
|
|
29
|
+
}, [isStandby, isSelected, diffInSeconds, timeLimitInSeconds]);
|
|
30
|
+
return useMemo(() => ({ remainingTimeText, status, diffInSeconds }), [remainingTimeText, status, diffInSeconds]);
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components/ServiceConversationCard/ServiceConversationCard.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './components/ServiceConversationCard/ServiceConversationCard.js';
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@t4h.ds/service-conversation-card",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"author": "Tony <tony.js@zoho.eu>",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist"
|
|
8
|
+
],
|
|
9
|
+
"module": "./dist/service-conversation-card.js",
|
|
10
|
+
"types": "./dist/service-conversation-card.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
"types": "./dist/service-conversation-card.d.ts",
|
|
13
|
+
"import": "./dist/service-conversation-card.js"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc -p tsconfig.build.json",
|
|
17
|
+
"prepublish": "yarn run build"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@t4h.ds/animation-frame": "^0.0.0-experimental-ed9e491",
|
|
21
|
+
"@t4h.ds/avatar": "^0.0.0",
|
|
22
|
+
"@t4h.ds/core": "^0.0.0",
|
|
23
|
+
"@t4h.ds/pressable": "^0.0.0",
|
|
24
|
+
"@t4h.ds/reanimated": "^0.0.0",
|
|
25
|
+
"@types/react": "^19",
|
|
26
|
+
"react": "^19.1.0",
|
|
27
|
+
"typescript": "^5.8.3",
|
|
28
|
+
"vitest": "^3.2.4"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@t4h.ds/animation-frame": "^0.0.0-experimental-ed9e491",
|
|
32
|
+
"@t4h.ds/avatar": "^0.0.0",
|
|
33
|
+
"@t4h.ds/core": "^0.0.0",
|
|
34
|
+
"@t4h.ds/pressable": "^0.0.0",
|
|
35
|
+
"@t4h.ds/reanimated": "^0.0.0",
|
|
36
|
+
"react": "*"
|
|
37
|
+
},
|
|
38
|
+
"optionalDependencies": {
|
|
39
|
+
"react-native": "^0.80.1"
|
|
40
|
+
}
|
|
41
|
+
}
|