fix: optimize view of group bottom bar in mobile mode

This commit is contained in:
2026-09-23 20:02:10 +03:30
parent a48deb95da
commit f614da943f

View File

@@ -6,12 +6,13 @@ import { resolveAssetUrl } from '@/lib/api/assets'
import { import {
RefreshCw, RefreshCw,
Image as ImageIcon, Image as ImageIcon,
Camera,
Mic, Mic,
Square, Square,
Send, Send,
Activity, Activity,
Smile, Smile,
Plus,
X,
} from 'lucide-react' } from 'lucide-react'
import { Button, Badge } from '@/components/common' import { Button, Badge } from '@/components/common'
import { getDeviceMemberRoleLabel } from '@/features/team/constants' import { getDeviceMemberRoleLabel } from '@/features/team/constants'
@@ -57,6 +58,7 @@ export function GroupFeed({ deviceId }: GroupFeedProps) {
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [text, setText] = useState('') const [text, setText] = useState('')
const [showMore, setShowMore] = useState(false)
const [showEmoji, setShowEmoji] = useState(false) const [showEmoji, setShowEmoji] = useState(false)
const [sending, setSending] = useState(false) const [sending, setSending] = useState(false)
const [recording, setRecording] = useState(false) const [recording, setRecording] = useState(false)
@@ -69,7 +71,6 @@ export function GroupFeed({ deviceId }: GroupFeedProps) {
const recordTimerRef = useRef<ReturnType<typeof setInterval> | null>(null) const recordTimerRef = useRef<ReturnType<typeof setInterval> | null>(null)
const bottomRef = useRef<HTMLDivElement>(null) const bottomRef = useRef<HTMLDivElement>(null)
const galleryInputRef = useRef<HTMLInputElement>(null) const galleryInputRef = useRef<HTMLInputElement>(null)
const cameraInputRef = useRef<HTMLInputElement>(null)
const recordSecondsRef = useRef(0) const recordSecondsRef = useRef(0)
useEffect(() => { useEffect(() => {
@@ -210,6 +211,7 @@ export function GroupFeed({ deviceId }: GroupFeedProps) {
recordSecondsRef.current = 0 recordSecondsRef.current = 0
recorder.start() recorder.start()
setRecording(true) setRecording(true)
setShowMore(false)
recordTimerRef.current = setInterval(() => { recordTimerRef.current = setInterval(() => {
recordSecondsRef.current += 1 recordSecondsRef.current += 1
@@ -273,16 +275,6 @@ export function GroupFeed({ deviceId }: GroupFeedProps) {
<div className="flex items-center justify-between px-4 py-3 border-b border-gray-100 bg-gray-50"> <div className="flex items-center justify-between px-4 py-3 border-b border-gray-100 bg-gray-50">
<h3 className="text-sm font-semibold text-gray-800">گفتگو</h3> <h3 className="text-sm font-semibold text-gray-800">گفتگو</h3>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
icon={Activity}
onClick={sendSnapshot}
loading={sending}
tooltip="ارسال وضعیت سنسورها و رله‌ها"
>
وضعیت سنسورها
</Button>
<button <button
type="button" type="button"
onClick={refresh} onClick={refresh}
@@ -328,6 +320,16 @@ export function GroupFeed({ deviceId }: GroupFeedProps) {
<div ref={bottomRef} /> <div ref={bottomRef} />
</div> </div>
{/* More actions panel */}
{showMore && (
<div className="flex items-center gap-1 px-3 py-2 border-t border-gray-100 bg-white">
<MoreAction icon={Smile} label="ایموجی" active={showEmoji} onClick={() => setShowEmoji(v => !v)} />
<MoreAction icon={ImageIcon} label="عکس" onClick={() => { setShowMore(false); galleryInputRef.current?.click() }} />
<MoreAction icon={Mic} label="ویس" onClick={startRecording} />
<MoreAction icon={Activity} label="وضعیت سنسورها" disabled={sending} onClick={sendSnapshot} />
</div>
)}
{/* Emoji row */} {/* Emoji row */}
{showEmoji && ( {showEmoji && (
<div className="flex flex-wrap gap-1 px-4 py-2 border-t border-gray-100 bg-white"> <div className="flex flex-wrap gap-1 px-4 py-2 border-t border-gray-100 bg-white">
@@ -346,47 +348,25 @@ export function GroupFeed({ deviceId }: GroupFeedProps) {
{/* Composer */} {/* Composer */}
<div className="border-t border-gray-100 p-3 bg-white"> <div className="border-t border-gray-100 p-3 bg-white">
<input
ref={galleryInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={handleGalleryFile}
/>
<div className="flex items-end gap-2"> <div className="flex items-end gap-2">
<button <button
type="button" type="button"
onClick={() => setShowEmoji(v => !v)} onClick={() => setShowMore(v => !v)}
aria-label="ایموجی" aria-label={showMore ? 'بستن گزینه‌ها' : 'گزینه‌های بیشتر'}
className="p-2 rounded-lg text-gray-500 hover:text-gray-700 hover:bg-gray-100 transition-colors" className={cn(
'p-2 rounded-lg transition-colors',
showMore ? 'bg-green-50 text-green-600' : 'text-gray-500 hover:text-gray-700 hover:bg-gray-100'
)}
> >
<Smile className="w-5 h-5" /> {showMore ? <X className="w-5 h-5" /> : <Plus className="w-5 h-5" />}
</button>
<input
ref={galleryInputRef}
type="file"
accept="image/*"
className="hidden"
onChange={handleGalleryFile}
/>
<button
type="button"
onClick={() => galleryInputRef.current?.click()}
aria-label="انتخاب عکس از گالری"
className="p-2 rounded-lg text-gray-500 hover:text-gray-700 hover:bg-gray-100 transition-colors"
>
<ImageIcon className="w-5 h-5" />
</button>
<input
ref={cameraInputRef}
type="file"
accept="image/*"
capture="environment"
className="hidden"
onChange={handleGalleryFile}
/>
<button
type="button"
onClick={() => cameraInputRef.current?.click()}
aria-label="عکس با دوربین"
className="p-2 rounded-lg text-gray-500 hover:text-gray-700 hover:bg-gray-100 transition-colors"
>
<Camera className="w-5 h-5" />
</button> </button>
<textarea <textarea
@@ -400,26 +380,17 @@ export function GroupFeed({ deviceId }: GroupFeedProps) {
<button <button
type="button" type="button"
onClick={recording ? stopRecording : startRecording} onClick={recording ? stopRecording : sendText}
aria-label={recording ? 'توقف ضبط' : 'ضبط ویس'} disabled={!recording && (!text.trim() || sending)}
aria-label={recording ? 'توقف ضبط' : 'ارسال'}
className={cn( className={cn(
'p-2 rounded-lg transition-colors', 'p-2 rounded-lg transition-colors',
recording recording
? 'bg-red-50 text-red-600 animate-pulse' ? 'bg-red-500 text-white'
: 'text-gray-500 hover:text-gray-700 hover:bg-gray-100' : 'bg-green-600 text-white hover:bg-green-700 disabled:opacity-40'
)} )}
> >
{recording ? <Square className="w-5 h-5" /> : <Mic className="w-5 h-5" />} {recording ? <Square className="w-5 h-5" /> : <Send className="w-5 h-5" />}
</button>
<button
type="button"
onClick={sendText}
disabled={!text.trim() || sending}
aria-label="ارسال"
className="p-2 rounded-lg bg-green-600 text-white hover:bg-green-700 disabled:opacity-40 transition-colors"
>
<Send className="w-5 h-5" />
</button> </button>
</div> </div>
@@ -450,6 +421,36 @@ export function GroupFeed({ deviceId }: GroupFeedProps) {
) )
} }
function MoreAction({
icon: Icon,
label,
active,
disabled,
onClick,
}: {
icon: React.ComponentType<{ className?: string }>
label: string
active?: boolean
disabled?: boolean
onClick: () => void
}) {
return (
<button
type="button"
onClick={onClick}
disabled={disabled}
aria-label={label}
className={cn(
'flex flex-col items-center gap-1 px-2 py-1.5 rounded-lg transition-colors disabled:opacity-40',
active ? 'bg-green-50 text-green-600' : 'text-gray-500 hover:text-gray-700 hover:bg-gray-100'
)}
>
<Icon className="w-5 h-5" />
<span className="text-[10px] leading-none">{label}</span>
</button>
)
}
function PostBubble({ post, onImageClick }: { post: DevicePostDto; onImageClick: (src: string) => void }) { function PostBubble({ post, onImageClick }: { post: DevicePostDto; onImageClick: (src: string) => void }) {
const fullName = `${post.authorName} ${post.authorFamily}`.trim() || 'بدون نام' const fullName = `${post.authorName} ${post.authorFamily}`.trim() || 'بدون نام'
const isSnapshot = post.postType === 3 const isSnapshot = post.postType === 3