fix: optimize view of group bottom bar in mobile mode
This commit is contained in:
@@ -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,16 +348,6 @@ 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">
|
||||||
<div className="flex items-end gap-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowEmoji(v => !v)}
|
|
||||||
aria-label="ایموجی"
|
|
||||||
className="p-2 rounded-lg text-gray-500 hover:text-gray-700 hover:bg-gray-100 transition-colors"
|
|
||||||
>
|
|
||||||
<Smile className="w-5 h-5" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<input
|
<input
|
||||||
ref={galleryInputRef}
|
ref={galleryInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
@@ -363,30 +355,18 @@ export function GroupFeed({ deviceId }: GroupFeedProps) {
|
|||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={handleGalleryFile}
|
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
|
<div className="flex items-end gap-2">
|
||||||
ref={cameraInputRef}
|
|
||||||
type="file"
|
|
||||||
accept="image/*"
|
|
||||||
capture="environment"
|
|
||||||
className="hidden"
|
|
||||||
onChange={handleGalleryFile}
|
|
||||||
/>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => cameraInputRef.current?.click()}
|
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'
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Camera className="w-5 h-5" />
|
{showMore ? <X className="w-5 h-5" /> : <Plus 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
|
||||||
|
|||||||
Reference in New Issue
Block a user