guardia-messenger/app/(tabs)/cowork_sr.tsx

102 lines
5.9 KiB
TypeScript

import React, { useState, useEffect, useRef } from 'react';
import { View, Text, ScrollView, TextInput, TouchableOpacity, StyleSheet, KeyboardAvoidingView, Platform } from 'react-native';
import { ITSM_BASE } from '../../services/api';
interface CoworkMsg { id: string; author: string; role: string; text: string; ts: string; type: 'chat' | 'action' | 'status' }
const SAMPLE_MSGS: CoworkMsg[] = [
{ id: '1', author: '김철수', role: 'engineer', text: 'db-01 서버 CPU 90% 넘어갔어요. 확인 부탁드립니다.', ts: '10:02', type: 'chat' },
{ id: '2', author: 'AI', role: 'ai', text: '원인 분석 완료: db-01 슬로우 쿼리 20개 감지. 쿼리 최적화 또는 서버 재시작 권장.', ts: '10:02', type: 'action' },
{ id: '3', author: '이영희', role: 'pm', text: 'SR 우선순위 Critical로 변경했습니다.', ts: '10:03', type: 'status' },
];
export default function CoworkSRScreen() {
const [srId] = useState('SR-2042');
const [messages, setMessages] = useState<CoworkMsg[]>(SAMPLE_MSGS);
const [input, setInput] = useState('');
const [participants] = useState([{ name: '김철수', role: 'engineer', online: true }, { name: '이영희', role: 'pm', online: true }, { name: 'AI', role: 'ai', online: true }]);
const scrollRef = useRef<ScrollView>(null);
const send = async () => {
if (!input.trim()) return;
const msg: CoworkMsg = { id: Date.now().toString(), author: '나', role: 'engineer', text: input, ts: new Date().toLocaleTimeString('ko', { hour: '2-digit', minute: '2-digit' }), type: 'chat' };
setMessages(prev => [...prev, msg]);
setInput('');
try {
await fetch(`${ITSM_BASE}/api/sr-chat/${srId}/messages`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: input }),
});
} catch {}
setTimeout(() => scrollRef.current?.scrollToEnd(), 100);
};
const roleColor = (role: string) => ({ engineer: '#00A0C8', pm: '#ffbb00', ai: '#44bb44', sm: '#bb44bb' })[role] || '#888';
return (
<KeyboardAvoidingView style={s.container} behavior={Platform.OS === 'ios' ? 'padding' : undefined}>
<View style={s.header}>
<Text style={s.srTitle}>{srId} </Text>
<View style={s.participants}>
{participants.map((p, i) => (
<View key={i} style={[s.avatar, { backgroundColor: roleColor(p.role) + '33', borderColor: roleColor(p.role) }]}>
<Text style={[s.avatarText, { color: roleColor(p.role) }]}>{p.name[0]}</Text>
{p.online && <View style={s.onlineDot} />}
</View>
))}
</View>
</View>
<ScrollView ref={scrollRef} style={s.messages} onContentSizeChange={() => scrollRef.current?.scrollToEnd()}>
{messages.map(msg => (
<View key={msg.id} style={[s.msgRow, msg.author === '나' && s.msgRowRight]}>
{msg.author !== '나' && (
<View style={[s.msgAvatar, { backgroundColor: roleColor(msg.role) + '33' }]}>
<Text style={[s.msgAvatarText, { color: roleColor(msg.role) }]}>{msg.author[0]}</Text>
</View>
)}
<View style={[s.msgBubble, msg.type === 'action' && s.msgBubbleAction, msg.type === 'status' && s.msgBubbleStatus, msg.author === '나' && s.msgBubbleSelf]}>
{msg.author !== '나' && <Text style={[s.msgAuthor, { color: roleColor(msg.role) }]}>{msg.author}</Text>}
<Text style={s.msgText}>{msg.text}</Text>
<Text style={s.msgTs}>{msg.ts}</Text>
</View>
</View>
))}
</ScrollView>
<View style={s.inputRow}>
<TextInput style={s.input} value={input} onChangeText={setInput} placeholder="메시지 입력..." placeholderTextColor="#555" returnKeyType="send" onSubmitEditing={send} />
<TouchableOpacity style={s.sendBtn} onPress={send}>
<Text style={s.sendText}></Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
);
}
const s = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0E1A' },
header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 14, borderBottomWidth: 1, borderBottomColor: '#333' },
srTitle: { color: '#fff', fontWeight: '700', fontSize: 16 },
participants: { flexDirection: 'row', gap: 6 },
avatar: { width: 30, height: 30, borderRadius: 15, borderWidth: 1, alignItems: 'center', justifyContent: 'center', position: 'relative' },
avatarText: { fontSize: 13, fontWeight: '700' },
onlineDot: { position: 'absolute', bottom: 0, right: 0, width: 8, height: 8, borderRadius: 4, backgroundColor: '#44bb44', borderWidth: 1, borderColor: '#0A0E1A' },
messages: { flex: 1, padding: 12 },
msgRow: { flexDirection: 'row', marginBottom: 12, alignItems: 'flex-end' },
msgRowRight: { justifyContent: 'flex-end' },
msgAvatar: { width: 28, height: 28, borderRadius: 14, alignItems: 'center', justifyContent: 'center', marginRight: 8 },
msgAvatarText: { fontSize: 12, fontWeight: '700' },
msgBubble: { backgroundColor: '#1A1F2E', borderRadius: 12, padding: 10, maxWidth: '75%', borderWidth: 1, borderColor: '#333' },
msgBubbleAction: { borderColor: '#44bb44', backgroundColor: '#44bb4422' },
msgBubbleStatus: { borderColor: '#ffbb00', backgroundColor: '#ffbb0022' },
msgBubbleSelf: { backgroundColor: '#003366', borderColor: '#00A0C8' },
msgAuthor: { fontSize: 11, fontWeight: '700', marginBottom: 4 },
msgText: { color: '#fff', fontSize: 14 },
msgTs: { color: '#555', fontSize: 10, marginTop: 4, textAlign: 'right' },
inputRow: { flexDirection: 'row', padding: 12, borderTopWidth: 1, borderTopColor: '#333' },
input: { flex: 1, backgroundColor: '#1A1F2E', borderRadius: 10, color: '#fff', paddingHorizontal: 14, paddingVertical: 10, borderWidth: 1, borderColor: '#333', marginRight: 10 },
sendBtn: { backgroundColor: '#00A0C8', paddingHorizontal: 16, borderRadius: 10, justifyContent: 'center' },
sendText: { color: '#fff', fontWeight: '700' },
});