quyx@nextosd.com 3 ヶ月 前
コミット
04e4850b73
1 ファイル変更480 行追加0 行削除
  1. 480 0
      src/pages/Partner/Order/ChatPartner.tsx

+ 480 - 0
src/pages/Partner/Order/ChatPartner.tsx

@@ -0,0 +1,480 @@
+import React, { useState, useRef, useEffect } from "react";
+
+// 聊天组件 props 类型
+type ChatProps = {
+  onClose: () => void;
+};
+
+// 聊天消息类型定义
+type ChatMessage = {
+  id: string;
+  sender: "other" | "me";
+  content: string;
+  timestamp: string;
+  attachment?: {
+    name: string;
+    url: string;
+  };
+};
+
+// 聊天组件 props 类型
+type ChatComponentProps = {
+  messageList: ChatMessage[];
+  onClose: () => void;
+};
+
+const ChatComponent: React.FC<ChatComponentProps> = ({ messageList, onClose }) => {
+  const messagesEndRef = useRef<HTMLDivElement>(null);
+  // 新增:下拉选中值状态
+  const [selectedBase, setSelectedBase] = useState("羽田ベース"); 
+  // 新增:下拉假数据选项
+  const baseOptions = ["羽田ベース", "成田ベース", "大阪ベース", "福岡ベース"]; 
+
+  const baseMessageStyle = {
+    display: "flex",
+    marginBottom: "12px",
+    width: "100%",
+  } as React.CSSProperties;
+
+  const senderInfoStyle = {
+    display: "flex",
+    alignItems: "center",
+    marginBottom: "4px",
+  } as React.CSSProperties;
+
+  const iconStyle = {
+    width: "20px",
+    height: "20px",
+    marginRight: "4px",
+  } as React.CSSProperties;
+
+  const senderNameStyle = {
+    fontSize: "14px",
+    fontWeight: "bold",
+  } as React.CSSProperties;
+
+  const bubbleStyle = {
+    padding: "10px 14px",
+    borderRadius: "8px",
+    boxShadow: "0 1px 2px rgba(0,0,0,0.1)",
+    maxWidth: "70%",
+    wordWrap: "break-word",
+    position: "relative", 
+  } as React.CSSProperties;
+
+  const contentStyle = {
+    marginBottom: "0",
+    lineHeight: "1.5",
+    fontSize: "14px",
+    textAlign: "left",
+    paddingTop: "5px",
+  } as React.CSSProperties;
+
+  const attachmentContainerStyle = {
+    display: "flex",
+    alignItems: "center",
+    marginBottom: "4px",
+    gap: "8px",
+  } as React.CSSProperties;
+
+  const fileNameWrapStyle = {
+    border: "1px solid #ccc",
+    borderRadius: "4px",
+    padding: "6px 8px",
+    display: "inline-block",
+  } as React.CSSProperties;
+
+  const attachmentNameStyle = {
+    fontSize: "14px",
+    color: "#000",
+    whiteSpace: "nowrap",
+    overflow: "hidden",
+    textOverflow: "ellipsis",
+    maxWidth: "200px",
+  } as React.CSSProperties;
+
+  const downloadLinkStyle = {
+    fontSize: "14px",
+    color: "#0066cc",
+    textDecoration: "underline",
+    cursor: "pointer",
+    whiteSpace: "nowrap",
+  } as React.CSSProperties;
+
+  const timestampStyle = {
+    fontSize: "14px",
+    color: "#000",
+  } as React.CSSProperties;
+
+  useEffect(() => {
+    messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
+  }, [messageList]);
+
+  // 新增:下拉选项改变时的处理函数
+  const handleBaseChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
+    setSelectedBase(e.target.value);
+    // 这里可扩展选中后要执行的逻辑,比如调用接口等,当前仅打印示例
+    console.log("当前选中的ベース:", e.target.value); 
+  };
+
+  return (
+    <div>
+      <div
+        style={{
+          marginBottom: "10px",
+          display: "flex",
+          alignItems: "center",
+          marginLeft: "-10px",
+        }}
+      >
+        <img
+          src="/return.svg"
+          alt="返回箭头"
+          style={{ width: "25px", height: "25px" }}
+        />
+        <span
+          style={{ fontSize: "14px", cursor: "pointer" }}
+          onClick={onClose}
+        > 戻る </span>
+      </div>
+
+      {/* 新增的下拉列表部分 */}
+      <div style={{ marginBottom: "10px", marginLeft: "15px" }}>
+        <label htmlFor="baseSelect" style={{ marginRight: "5px" }}>ベース選択:</label>
+        <select 
+          id="baseSelect" 
+          style={{ padding: "6px 12px", border: "1px solid #ccc" }} 
+          value={selectedBase}
+          onChange={handleBaseChange}
+        >
+          {baseOptions.map((option) => (
+            <option key={option} value={option}>{option}</option>
+          ))}
+        </select>
+      </div>
+
+      {messageList.map((msg) => (
+        <div
+          key={msg.id}
+          style={{
+           ...baseMessageStyle,
+            justifyContent: msg.sender === "other"? "flex-end" : "flex-start",
+            display: "flex",
+            flexDirection: "column",
+            alignItems: msg.sender === "other"? "flex-end" : "flex-start",
+            paddingLeft: "40px", 
+          }}
+        >
+          {msg.sender === "other" && (
+            <div style={senderInfoStyle}>
+               <span style={senderNameStyle}>AAA株式会社</span>
+              <img
+                src="/partner.png"
+                alt="AAA株式会社图标"
+                style={iconStyle}
+              />
+            </div>
+          )}
+          {msg.sender === "me" && (
+            <div style={senderInfoStyle}>
+              <img
+                src="/base.png"
+                alt="羽田ベース图标"
+                style={{...iconStyle, marginLeft: "4px", marginRight: "0" }}
+              />
+              <span style={senderNameStyle}>羽田ベース</span>
+            </div>
+          )}
+
+          {msg.content.trim()!== "" && (
+            <div
+              style={{
+               ...bubbleStyle,
+                backgroundColor: msg.sender === "other"? "#d9d9d9" : "#deebf7",
+                marginBottom: "4px",
+                marginTop: "15px", 
+              }}
+            >
+              {msg.sender === "other" && (
+                <div
+                  style={{
+                    content: '""',
+                    position: "absolute",
+                    right: "12px", 
+                    top: "-23px", 
+                    borderTop: "20px solid transparent", 
+                    borderBottom: "20px solid transparent",
+                    borderRight: "15px solid #d9d9d9", 
+                    transform: "translateY(20%)", 
+                  }}
+                />
+              )}
+              {msg.sender === "me" && (
+                <div
+                  style={{
+                    content: '""',
+                    position: "absolute",
+                    left: "12px", 
+                    top: "-23px", 
+                    borderTop: "20px solid transparent", 
+                    borderBottom: "20px solid transparent",
+                    borderLeft: "15px solid #deebf7", 
+                    transform: "translateY(20%)", 
+                  }}
+                />
+              )}
+              <div style={contentStyle}>
+                {msg.content.split("\n").map((line, index) => (
+                  <React.Fragment key={index}>
+                    {line}
+                    {index < msg.content.split("\n").length - 1 && <br />}
+                  </React.Fragment>
+                ))}
+              </div>
+            </div>
+          )}
+          {msg.attachment && (
+            <div style={attachmentContainerStyle}>
+              <div style={fileNameWrapStyle}>
+                <span style={attachmentNameStyle}>{msg.attachment.name}</span>
+              </div>
+              <a
+                href={msg.attachment.url}
+                download={msg.attachment.name}
+                style={downloadLinkStyle}
+              >
+                ダウンロード
+              </a>
+            </div>
+          )}
+          <div
+            style={{
+             ...timestampStyle,
+              marginLeft: msg.sender === "other"? "10px" : "0",
+              marginRight: msg.sender === "me"? "10px" : "0",
+              textAlign: msg.sender === "other"? "left" : "right",
+            }}
+          >
+            {msg.timestamp}
+          </div>
+        </div>
+      ))}
+      <div ref={messagesEndRef} />
+    </div>
+  );
+};
+
+// 输入区域组件
+const InputArea: React.FC<{ onSendMessage: (content: string, file: File | null) => void }> = ({ onSendMessage }) => {
+  const [inputValue, setInputValue] = useState("");
+  const [selectedFile, setSelectedFile] = useState<File | null>(null);
+
+  const handleSend = () => {
+    if (inputValue.trim() || selectedFile) {
+      onSendMessage(inputValue.trim(), selectedFile);
+      setInputValue("");
+      setSelectedFile(null);
+    }
+  };
+
+  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+    if (e.target.files && e.target.files.length > 0) {
+      setSelectedFile(e.target.files[0]);
+    }
+  };
+
+  const handleRemoveFile = () => {
+    setSelectedFile(null);
+  };
+
+  const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
+    if (e.key === 'Enter' &&!e.shiftKey) {
+      e.preventDefault();
+      handleSend();
+    }
+  };
+
+  return (
+    <div
+      style={{
+        width: "100%",
+        margin: "0",
+        padding: "16px",
+        display: "flex",
+        flexDirection: "column",
+        gap: "8px",
+        boxSizing: "border-box",
+      }}
+    >
+      <textarea
+        value={inputValue}
+        onChange={(e) => setInputValue(e.target.value)}
+        onKeyDown={handleKeyDown}
+        placeholder="メッセージを入力する..."
+        style={{
+          width: "100%",
+          height: "180px",
+          padding: "8px",
+          borderRadius: "4px",
+          border: "1px solid #ccc",
+          resize: "none",
+          boxSizing: "border-box",
+        }}
+      />
+      {selectedFile && (
+        <div
+          style={{
+            display: "flex",
+            alignItems: "center",
+            justifyContent: "space-between",
+            backgroundColor: "#f9f9f9",
+            padding: "8px",
+            borderRadius: "4px",
+          }}
+        >
+          <div style={{ display: "flex", alignItems: "center" }}>
+            <span style={{ marginRight: "8px", color: "#0066cc" }}>
+              {selectedFile.name.split('.').pop()?.toUpperCase() || 'FILE'}
+            </span>
+            <span>{selectedFile.name}</span>
+          </div>
+          <span
+            style={{
+              cursor: "pointer",
+              color: "#000",
+              fontWeight: "bold",
+              fontSize: "16px",
+            }}
+            onClick={handleRemoveFile}
+          >
+            ×
+          </span>
+        </div>
+      )}
+      <div
+        style={{
+          display: "flex",
+          alignItems: "center",
+          justifyContent: "space-between",
+        }}
+      >
+        <label
+          htmlFor="fileInput"
+          style={{
+            color: "#0066cc",
+            textDecoration: "underline",
+            cursor: "pointer",
+          }}
+        >
+          ファイルアップロード
+        </label>
+        <input
+          type="file"
+          id="fileInput"
+          onChange={handleFileChange}
+          style={{
+            display: "none",
+          }}
+        />
+        <button
+          style={{
+            padding: "8px 16px",
+            backgroundColor: "#000",
+            color: "#fff",
+            border: "none",
+            borderRadius: "4px",
+            cursor: "pointer",
+          }}
+          onClick={handleSend}
+        >
+          送信
+        </button>
+      </div>
+    </div>
+  );
+};
+
+// 示例数据
+const initialMessages: ChatMessage[] = [
+  {
+    id: "1",
+    sender: "other",
+    content: "実積を確認しました。\n21日は追加で稼働していませんが、実積に反映されていません。",
+    timestamp: "2025-7-1 12:21",
+  },
+  {
+    id: "2",
+    sender: "me",
+    content: "ご確認ありがとうございます。\n恐れ入りますが、運行の証跡がかかるものをアップロードいただけますでしょうか。",
+    timestamp: "2025-7-1 12:25",
+  },
+  {
+    id: "3",
+    sender: "other",
+    content: "1ファイルを送ります。\nこちらでご確認お願いします。",
+    timestamp: "2025-7-1 13:07",
+    attachment: {
+      name: "6月運行証明.xlsx",
+      url: "#",
+    },
+  },
+  {
+    id: "4",
+    sender: "me",
+    content: "ありがとうございます。\n内容確認して、再度ご連絡させていただきます。",
+    timestamp: "2025-7-1 13:31",
+  },
+];
+
+const Chat: React.FC<ChatProps> = ({ onClose }) => {
+  const [messages, setMessages] = useState<ChatMessage[]>(initialMessages);
+
+  const handleSendMessage = (content: string, file: File | null) => {
+    const newMessage: ChatMessage = {
+      id: Date.now().toString(),
+      sender: "me",
+      content,
+      timestamp: new Date()
+       .toLocaleString("ja-JP", {
+          year: "numeric",
+          month: "long",
+          day: "numeric",
+          hour: "2-digit",
+          minute: "2-digit",
+        })
+       .replace(/[年月]/g, "-")
+       .replace("日", ""),
+    };
+
+    if (file) {
+      newMessage.attachment = {
+        name: file.name,
+        url: "#", 
+      };
+    }
+
+    setMessages([...messages, newMessage]);
+  };
+
+  return (
+    <div style={{ 
+      height: '100%', 
+      display: 'flex', 
+      flexDirection: 'column',
+      margin: 0,
+      padding: 0
+    }}>
+      <div className="chat-messages-container" style={{
+        padding: '16px',
+        boxSizing: 'border-box',
+        flex: 1,
+        overflow: 'auto'
+      }}>
+        <ChatComponent messageList={messages} onClose={onClose} />
+      </div>
+      <InputArea onSendMessage={handleSendMessage} />
+    </div>
+  );
+};
+
+export default Chat;