add: chats list
This commit is contained in:
@@ -0,0 +1,140 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { motion } from 'framer-motion';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
const users = [
|
||||||
|
{ id: 0, name: '1 علی مصلحی', table: 12, avatar: '/assets/images/user-avatar.png' },
|
||||||
|
{ id: 1, name: '2 علی مصلحی', table: 10, avatar: '/assets/images/user-avatar.png' },
|
||||||
|
{ id: 2, name: '3 علی مصلحی', table: 11, avatar: '/assets/images/user-avatar.png' },
|
||||||
|
{ id: 3, name: '4 علی مصلحی', table: 5, avatar: '/assets/images/user-avatar.png' },
|
||||||
|
{ id: 4, name: '5 علی مصلحی', table: 2, avatar: '/assets/images/user-avatar.png' },
|
||||||
|
{ id: 5, name: '6 علی مصلحی', table: 1, avatar: '/assets/images/user-avatar.png' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function ChatNearby() {
|
||||||
|
const containerRef = useRef<HTMLElement>(null);
|
||||||
|
const [positions, setPositions] = useState<{ top: number; left: number }[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (containerRef.current) {
|
||||||
|
const maxW = containerRef.current.offsetWidth - 50;
|
||||||
|
const maxH = containerRef.current.offsetHeight - 80;
|
||||||
|
const elementSize = 110; // approximate size of each user element
|
||||||
|
const padding = 80; // minimum distance between elements
|
||||||
|
|
||||||
|
const positions: { top: number; left: number }[] = [];
|
||||||
|
|
||||||
|
const isTooClose = (top: number, left: number) => {
|
||||||
|
return positions.some(p => {
|
||||||
|
const dx = p.left - left;
|
||||||
|
const dy = p.top - top;
|
||||||
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
return distance < padding;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
users.forEach(() => {
|
||||||
|
let top = 0;
|
||||||
|
let left = 0;
|
||||||
|
let tries = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
top = Math.random() * (maxH - elementSize);
|
||||||
|
left = Math.random() * (maxW - elementSize);
|
||||||
|
tries++;
|
||||||
|
} while (isTooClose(top, left) && tries < 100);
|
||||||
|
|
||||||
|
positions.push({ top, left });
|
||||||
|
});
|
||||||
|
|
||||||
|
setPositions(positions);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section ref={containerRef} className="h-full relative">
|
||||||
|
<section className="relative h-full w-full mt-7">
|
||||||
|
<motion.div
|
||||||
|
className='size-[1145px] rounded-full fixed top-1/2 left-1/2 -translate-1/2'
|
||||||
|
style={{ background: 'linear-gradient(180deg, #EAECF0 4.52%, #DBDEE8 95.02%)', willChange: 'transform' }}
|
||||||
|
>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{ scale: [1, 0.95, 1] }}
|
||||||
|
transition={{ repeat: Infinity, duration: 13 }}
|
||||||
|
className='size-[824px] rounded-full fixed top-1/2 left-1/2 -translate-1/2'
|
||||||
|
style={{ background: 'linear-gradient(180deg, #EBF0FC 4.52%, #EBF0FC 95.02%)', willChange: 'transform' }}
|
||||||
|
>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{ scale: [0.9, 1] }}
|
||||||
|
transition={{ repeat: Infinity, duration: 4, repeatType: 'reverse' }}
|
||||||
|
className='size-[617px] rounded-full fixed top-1/2 left-1/2 -translate-1/2'
|
||||||
|
style={{ background: 'linear-gradient(180deg, #E6EBFA 0%, #DFE2EC 100%)', willChange: 'transform' }}
|
||||||
|
>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{ scale: [1.02, 0.95] }}
|
||||||
|
transition={{ repeat: Infinity, duration: 2, repeatType: 'reverse' }}
|
||||||
|
className='size-[425px] rounded-full fixed top-1/2 left-1/2 -translate-1/2'
|
||||||
|
style={{ background: 'linear-gradient(180deg, #E1E5EF 0%, #D8DDE8 60.2%, #D2D7E3 100%)', willChange: 'transform' }}
|
||||||
|
>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{ scale: [1.05, 1] }}
|
||||||
|
transition={{ repeat: Infinity, duration: 2, repeatType: 'reverse' }}
|
||||||
|
className='size-[292px] rounded-full fixed top-1/2 left-1/2 -translate-1/2'
|
||||||
|
style={{ background: 'linear-gradient(180deg, #EAEDF5 0%, #E4E7F0 46.52%, #DDDFEB 100%)', willChange: 'transform' }}
|
||||||
|
>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{ scale: [1, 1.05] }}
|
||||||
|
transition={{ repeat: Infinity, duration: 2, repeatType: 'reverse' }}
|
||||||
|
|
||||||
|
className='size-[174px] rounded-full fixed top-1/2 left-1/2 -translate-1/2'
|
||||||
|
style={{ background: 'linear-gradient(180deg, #F3F7FF 0%, #F2F5FC 100%)', willChange: 'transform' }}
|
||||||
|
>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
animate={{ scale: [0, 1.2], opacity: [1, 0] }}
|
||||||
|
transition={{ repeat: Infinity, duration: 2 }}
|
||||||
|
|
||||||
|
className='h-full w-dvh rounded-full fixed top-1/2 left-1/2 -translate-1/2 outline outline-solid outline-blue-400'
|
||||||
|
style={{ willChange: 'transform, opacity' }}
|
||||||
|
>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{positions.length === users.length &&
|
||||||
|
users.map((v, i) => (
|
||||||
|
<Link
|
||||||
|
href={`${v.id}`}
|
||||||
|
prefetch={false}
|
||||||
|
key={v.id}
|
||||||
|
style={{ position: 'absolute', top: positions[i].top, left: positions[i].left }}
|
||||||
|
className="justify-items-center"
|
||||||
|
>
|
||||||
|
<Image height={46} width={46} alt="user avatar" src={v.avatar} />
|
||||||
|
<h6 className="mt-2 text-xs font-medium">{v.name}</h6>
|
||||||
|
<div className="mt-[3px] text-xs2 font-medium bg-disabled-text text-accent rounded-[5px] px-[5px] py-[3px]">
|
||||||
|
میز {v.table}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatNearby;
|
||||||
+148
-132
@@ -1,140 +1,156 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { motion } from 'framer-motion';
|
import SearchBox from '@/components/input/SearchBox';
|
||||||
|
import { ScrollArea } from '@/components/ui/scrollarea';
|
||||||
|
import { Radar2, Trash } from 'iconsax-react';
|
||||||
|
import { Check, CheckCheck } from 'lucide-react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
import React, { useState } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const users = [
|
type Props = object
|
||||||
{ id: 0, name: '1 علی مصلحی', table: 12, avatar: '/assets/images/user-avatar.png' },
|
|
||||||
{ id: 1, name: '2 علی مصلحی', table: 10, avatar: '/assets/images/user-avatar.png' },
|
|
||||||
{ id: 2, name: '3 علی مصلحی', table: 11, avatar: '/assets/images/user-avatar.png' },
|
|
||||||
{ id: 3, name: '4 علی مصلحی', table: 5, avatar: '/assets/images/user-avatar.png' },
|
|
||||||
{ id: 4, name: '5 علی مصلحی', table: 2, avatar: '/assets/images/user-avatar.png' },
|
|
||||||
{ id: 5, name: '6 علی مصلحی', table: 1, avatar: '/assets/images/user-avatar.png' },
|
|
||||||
];
|
|
||||||
|
|
||||||
function ChatIndex() {
|
type ChatEntryModel = {
|
||||||
const containerRef = useRef<HTMLElement>(null);
|
id: string,
|
||||||
const [positions, setPositions] = useState<{ top: number; left: number }[]>([]);
|
username: string,
|
||||||
|
avatarUrl: string,
|
||||||
useEffect(() => {
|
update?: {
|
||||||
if (containerRef.current) {
|
time: string,
|
||||||
const maxW = containerRef.current.offsetWidth - 50;
|
content: string,
|
||||||
const maxH = containerRef.current.offsetHeight - 80;
|
count?: number,
|
||||||
const elementSize = 110; // approximate size of each user element
|
seen?: boolean
|
||||||
const padding = 80; // minimum distance between elements
|
}
|
||||||
|
|
||||||
const positions: { top: number; left: number }[] = [];
|
|
||||||
|
|
||||||
const isTooClose = (top: number, left: number) => {
|
|
||||||
return positions.some(p => {
|
|
||||||
const dx = p.left - left;
|
|
||||||
const dy = p.top - top;
|
|
||||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
||||||
return distance < padding;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
users.forEach(() => {
|
|
||||||
let top = 0;
|
|
||||||
let left = 0;
|
|
||||||
let tries = 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
top = Math.random() * (maxH - elementSize);
|
|
||||||
left = Math.random() * (maxW - elementSize);
|
|
||||||
tries++;
|
|
||||||
} while (isTooClose(top, left) && tries < 100);
|
|
||||||
|
|
||||||
positions.push({ top, left });
|
|
||||||
});
|
|
||||||
|
|
||||||
setPositions(positions);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section ref={containerRef} className="h-full relative">
|
|
||||||
<section className="relative h-full w-full mt-7">
|
|
||||||
<motion.div
|
|
||||||
className='size-[1145px] rounded-full fixed top-1/2 left-1/2 -translate-1/2'
|
|
||||||
style={{ background: 'linear-gradient(180deg, #EAECF0 4.52%, #DBDEE8 95.02%)', willChange: 'transform' }}
|
|
||||||
>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
animate={{ scale: [1, 0.95, 1] }}
|
|
||||||
transition={{ repeat: Infinity, duration: 13 }}
|
|
||||||
className='size-[824px] rounded-full fixed top-1/2 left-1/2 -translate-1/2'
|
|
||||||
style={{ background: 'linear-gradient(180deg, #EBF0FC 4.52%, #EBF0FC 95.02%)', willChange: 'transform' }}
|
|
||||||
>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
animate={{ scale: [0.9, 1] }}
|
|
||||||
transition={{ repeat: Infinity, duration: 4, repeatType: 'reverse' }}
|
|
||||||
className='size-[617px] rounded-full fixed top-1/2 left-1/2 -translate-1/2'
|
|
||||||
style={{ background: 'linear-gradient(180deg, #E6EBFA 0%, #DFE2EC 100%)', willChange: 'transform' }}
|
|
||||||
>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
animate={{ scale: [1.02, 0.95] }}
|
|
||||||
transition={{ repeat: Infinity, duration: 2, repeatType: 'reverse' }}
|
|
||||||
className='size-[425px] rounded-full fixed top-1/2 left-1/2 -translate-1/2'
|
|
||||||
style={{ background: 'linear-gradient(180deg, #E1E5EF 0%, #D8DDE8 60.2%, #D2D7E3 100%)', willChange: 'transform' }}
|
|
||||||
>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
animate={{ scale: [1.05, 1] }}
|
|
||||||
transition={{ repeat: Infinity, duration: 2, repeatType: 'reverse' }}
|
|
||||||
className='size-[292px] rounded-full fixed top-1/2 left-1/2 -translate-1/2'
|
|
||||||
style={{ background: 'linear-gradient(180deg, #EAEDF5 0%, #E4E7F0 46.52%, #DDDFEB 100%)', willChange: 'transform' }}
|
|
||||||
>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
animate={{ scale: [1, 1.05] }}
|
|
||||||
transition={{ repeat: Infinity, duration: 2, repeatType: 'reverse' }}
|
|
||||||
|
|
||||||
className='size-[174px] rounded-full fixed top-1/2 left-1/2 -translate-1/2'
|
|
||||||
style={{ background: 'linear-gradient(180deg, #F3F7FF 0%, #F2F5FC 100%)', willChange: 'transform' }}
|
|
||||||
>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
animate={{ scale: [0, 1.2], opacity: [1, 0] }}
|
|
||||||
transition={{ repeat: Infinity, duration: 2 }}
|
|
||||||
|
|
||||||
className='h-full w-dvh rounded-full fixed top-1/2 left-1/2 -translate-1/2 outline outline-solid outline-blue-400'
|
|
||||||
style={{ willChange: 'transform, opacity' }}
|
|
||||||
>
|
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{positions.length === users.length &&
|
|
||||||
users.map((v, i) => (
|
|
||||||
<Link
|
|
||||||
href={`chat/${v.id}`}
|
|
||||||
prefetch={false}
|
|
||||||
key={v.id}
|
|
||||||
style={{ position: 'absolute', top: positions[i].top, left: positions[i].left }}
|
|
||||||
className="justify-items-center"
|
|
||||||
>
|
|
||||||
<Image height={46} width={46} alt="user avatar" src={v.avatar} />
|
|
||||||
<h6 className="mt-2 text-xs font-medium">{v.name}</h6>
|
|
||||||
<div className="mt-[3px] text-xs2 font-medium bg-disabled-text text-accent rounded-[5px] px-[5px] py-[3px]">
|
|
||||||
میز {v.table}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ChatIndex;
|
function ChatIndex({ }: Props) {
|
||||||
|
const { t } = useTranslation('chat');
|
||||||
|
const [search, setSearch] = useState('');
|
||||||
|
const [selectedChats, setSelectedChats] = useState([]);
|
||||||
|
|
||||||
|
const chatsList: Array<ChatEntryModel> = [
|
||||||
|
{
|
||||||
|
id: '0',
|
||||||
|
username: 'علی مصلحی',
|
||||||
|
avatarUrl: '/assets/images/user-avatar.png',
|
||||||
|
update: {
|
||||||
|
time: '12:03',
|
||||||
|
content: 'چه خبر',
|
||||||
|
count: 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
username: 'علی مصلحی',
|
||||||
|
avatarUrl: '/assets/images/user-avatar.png',
|
||||||
|
update: {
|
||||||
|
time: '12:03',
|
||||||
|
content: 'چه خبر',
|
||||||
|
count: 2,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
username: 'علی مصلحی',
|
||||||
|
avatarUrl: '/assets/images/user-avatar.png',
|
||||||
|
update: {
|
||||||
|
time: '12:03',
|
||||||
|
content: 'چه خبر',
|
||||||
|
seen: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '3',
|
||||||
|
username: 'علی مصلحی',
|
||||||
|
avatarUrl: '/assets/images/user-avatar.png',
|
||||||
|
update: {
|
||||||
|
time: '12:03',
|
||||||
|
content: 'چه خبر',
|
||||||
|
seen: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '4',
|
||||||
|
username: 'علی مصلحی',
|
||||||
|
avatarUrl: '/assets/images/user-avatar.png',
|
||||||
|
update: {
|
||||||
|
time: '12:03',
|
||||||
|
content: 'چه خبر',
|
||||||
|
seen: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '5',
|
||||||
|
username: 'علی مصلحی',
|
||||||
|
avatarUrl: '/assets/images/user-avatar.png',
|
||||||
|
update: {
|
||||||
|
time: '12:03',
|
||||||
|
content: 'چه خبر',
|
||||||
|
seen: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='pt-8 mb-8'>
|
||||||
|
<section className='flex justify-between items-center gap-x-4'>
|
||||||
|
<SearchBox placeholder={t('InputSearch.Placeholder')} value={search} onChange={(e) => setSearch(e.target.value)} />
|
||||||
|
|
||||||
|
<button className='bg-container p-2 rounded-xl active:bg-gray-50'>
|
||||||
|
{selectedChats.length <= 0 ?
|
||||||
|
<Radar2 size={24} className='stroke-primary' />
|
||||||
|
:
|
||||||
|
<Trash size={24} className='stroke-primary' />
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section aria-labelledby={t("ChatsList.Aria-LabelBy")}>
|
||||||
|
<ScrollArea dir='rtl' className='w-full h-full'>
|
||||||
|
<ul className='pt-4'>
|
||||||
|
{chatsList.map((chat, i) => {
|
||||||
|
return (
|
||||||
|
<li key={i}>
|
||||||
|
<Link
|
||||||
|
className='flex items-center gap-x-2 hover:brightness-105 cursor-default active:brightness-102 p-2 bg-background rounded-xl'
|
||||||
|
href={`chat/${chat.id}`}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={chat.avatarUrl}
|
||||||
|
height={49}
|
||||||
|
width={49}
|
||||||
|
alt={`${chat.username}'s avatar`}
|
||||||
|
unoptimized
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='flex flex-col justify-between w-full gap-2'>
|
||||||
|
<span className='text-sm2 font-medium'>{chat.username}</span>
|
||||||
|
<span className='text-xs'>{chat.update?.content}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex flex-col justify-between items-end gap-2'>
|
||||||
|
<span className='text-sm2 text-disabled2'>{chat.update?.time}</span>
|
||||||
|
{chat.update?.count !== undefined && chat.update?.count > 0 ?
|
||||||
|
<span className='text-xs w-5 h-5 relative bg-foreground text-white rounded-full'>
|
||||||
|
<span className='absolute top-1/2 left-1/2 -translate-1/2 pt-1'>{chat.update?.count}</span>
|
||||||
|
</span>
|
||||||
|
:
|
||||||
|
chat.update?.seen !== undefined &&
|
||||||
|
chat.update?.seen ?
|
||||||
|
<CheckCheck className='stroke-foreground' size={16} /> :
|
||||||
|
<Check className='stroke-foreground' size={16} />
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</ScrollArea>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChatIndex
|
||||||
Reference in New Issue
Block a user