Commit 6e88c8b8 authored by Phạm Quang Bảo's avatar Phạm Quang Bảo

update/menbers_page and sub page

parent 96b115a1
import { NewsItem } from '@app/dai-dien-gioi-chu/lib/types/NewsPage.type';
import Links from '@links/index'
import dayjs from 'dayjs';
import parse from 'html-react-parser'
function NewsContent({ news }: { news: NewsItem }) {
return (
<a
href={`/tin-tuc/${news.id}`}
className="flex flex-col hover:no-underline sm:flex-row gap-2 mb-6 bg-white rounded-lg shadow-sm p-4 border items-start min-w-0"
>
<img
src={`${Links.imageEndpoint}${news.thumbnail}`}
alt={news.title}
className="w-full sm:w-56 md:w-64 h-40 md:h-36 object-cover shrink-0"
/>
<div className="flex-1 min-w-0 pl-0 sm:pl-4">
<p className="text-primary font-semibold text-base md:text-lg hover:underline line-clamp-2 wrap-break-word hover:no-underline">
{news.title}
</p>
<div className="text-sm my-2 text-[#00AED5]">{dayjs(news.release_at).format('DD/MM/YYYY')}</div>
<div className="text-sm text-[#777] line-clamp-3">
<div className="text-sm prose tiptap">{parse(news.description)}</div>
</div>
</div>
</a>
)
}
export default NewsContent;
\ No newline at end of file
"use client";
import React, { useState } from "react";
import { ArrowRight, ArrowLeft } from "lucide-react";
export default function EventCalendar() {
const mockCalendar = {
month: 10,
year: 2025,
highlighted: [6, 9, 12],
};
const [month, setMonth] = useState<number>(mockCalendar.month);
const [year, setYear] = useState<number>(mockCalendar.year);
return (
<div className="bg-white border rounded-md p-4">
<div className="flex items-center justify-between mb-3">
<div className="text-sm font-medium">
THÁNG {month}/{year}
</div>
<div className="flex items-center gap-2">
<div className="group">
<button
aria-label="Tháng trước"
onClick={() => {
// prev month
if (month === 1) {
setMonth(12);
setYear((y) => y - 1);
} else {
setMonth((m) => m - 1);
}
}}
className="group-hover:border-primary p-1 h-10 w-10 flex items-center justify-center rounded-full border-2 border-[#363636] "
>
<ArrowLeft
size={24}
className="group-hover:text-muted-foreground text-[#363636]"
/>
</button>
</div>
<div className="group">
<button
aria-label="Tháng sau"
onClick={() => {
// next month
if (month === 12) {
setMonth(1);
setYear((y) => y + 1);
} else {
setMonth((m) => m + 1);
}
}}
className="p-1 group-hover:border-primary h-10 w-10 flex items-center justify-center rounded-full border-2 border-[#363636]"
>
<ArrowRight
size={24}
className="group-hover:text-muted-foreground"
/>
</button>
</div>
</div>
</div>
<div className="grid grid-cols-7 gap-1 text-center text-xs">
{["CN", "T2", "T3", "T4", "T5", "T6", "T7"].map((d) => (
<div key={d} className="text-gray-400 py-1">
{d}
</div>
))}
{
(() => {
const totalDays = new Date(year, month, 0).getDate()
const firstDayIndex = new Date(year, month - 1, 1).getDay() // 0 (Sun) - 6 (Sat)
// previous month total days
const prevMonthTotalDays = new Date(year, month - 1, 0).getDate()
const totalCells = firstDayIndex + totalDays
const trailingCount = (7 - (totalCells % 7)) % 7
return (
<>
{Array.from({ length: firstDayIndex }).map((_, i) => {
const dayNum = prevMonthTotalDays - (firstDayIndex - 1) + i
return (
<div key={`prev-${i}`} className="py-2 text-sm text-gray-300">
{dayNum}
</div>
)
})}
{Array.from({ length: totalDays }, (_, i) => i + 1).map((day) => {
const isHighlighted = mockCalendar.highlighted.includes(day)
return (
<div
key={day}
className={`py-2 rounded-full w-10 h-10 flex flex-col justify-center items-center text-sm ${isHighlighted ? 'bg-yellow-500 text-white' : 'text-gray-700'
}`}
>
{day}
</div>
)
})}
{Array.from({ length: trailingCount }).map((_, i) => (
<div key={`next-${i}`} className="py-2 text-sm text-gray-300">
{i + 1}
</div>
))}
</>
)
})()
}
</div>
</div>
);
}
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
import React from "react"
type Category = {
title: string
href: string
}
const CATEGORIES: Category[] = [
{ title: "Lợi ích của Hội Viên VCCI", href: "/hoi-vien" },
{ title: "Đăng ký Hội Viên", href: "/hoi-vien/dang-ky-hoi-vien" },
{ title: "Kết nối Hội Viên", href: "/hoi-vien/ket-noi-hoi-vien" },
{ title: "Tin Hội Viên", href: "/hoi-vien/tin-hoi-vien" },
]
const ListCategory: React.FC = () => {
const pathname = usePathname() || ""
const isActive = (href: string) => {
if (href === "/hoi-vien") return pathname === "/hoi-vien"
return pathname === href
}
return (
<div className="border-t border-gray-200 bg-white p-2.5">
<div className="max-w-7xl mx-auto">
<nav aria-label="Danh mục" className="py-3">
<ul className="flex items-center">
{CATEGORIES.map((c) => {
const active = isActive(c.href)
return (
<li key={c.href}>
<Link
href={c.href}
className={
"text-sm font-bold py-3.5 px-5 transition-colors duration-150 " +
(active
? "text-yellow-500 font-semibold decoration-yellow-300 "
: "text-gray-600 hover:text-yellow-500 ")
}
>
{c.title}
</Link>
</li>
)
})}
</ul>
</nav>
</div>
</div>
)
}
export default ListCategory
\ No newline at end of file
"use client"
import React, { useState } from 'react'
import { Checkbox } from '@/components/ui/checkbox'
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
type Category = { id: string; title: string; count: number }
const DEFAULT_CATEGORIES: Category[] = [
{ id: 'ceo', title: 'CEO', count: 4 },
{ id: 'policy', title: 'Hỏi đáp về chính sách', count: 0 },
{ id: 'biz', title: 'Tin Doanh Nghiệp', count: 9 },
{ id: 'member', title: 'Tin Hội Viên', count: 17 },
{ id: 'law', title: 'Văn bản Pháp luật sắp có hiệu lực', count: 30 }
]
export const ListFilter: React.FC<{
categories?: Category[]
onSearch?: (q: string) => void
onReset?: () => void
}> = ({ categories = DEFAULT_CATEGORIES, onSearch, onReset }) => {
const [query, setQuery] = useState('')
const [selected, setSelected] = useState<Record<string, boolean>>(() => {
const map: Record<string, boolean> = {}
categories.forEach((c) => (map[c.id] = false))
return map
})
const toggle = (id: string) => setSelected((s) => ({ ...s, [id]: !s[id] }))
return (
<aside className="p-6 bg-white border rounded-md">
<h3 className="text-lg font-semibold mb-3">Tìm kiếm</h3>
<div className="mb-4">
<Input
placeholder="Tên văn bản ..."
value={query}
className='text-black placeholder:text-gray-400 rounded-none py-2.5 px-2'
onChange={(e) => setQuery(e.target.value)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
onSearch?.(query)
}
}}
/>
</div>
{/* <div className="flex flex-col gap-3 mb-6">
{categories.map((c) => (
<label key={c.id} className="flex items-center gap-3">
<Checkbox checked={!!selected[c.id]} onCheckedChange={() => toggle(c.id)} />
<div className="flex justify-between w-full items-center">
<span className="text-sm">{c.title}</span>
<span className="text-sm text-gray-400">({c.count})</span>
</div>
</label>
))}
</div> */}
<div className="flex gap-3">
<Button className="flex-1 rounded-none font-medium text-lg text-white hover:bg-muted-foreground hover:outline-1 outline-primary hover:text-primary" onClick={() => onSearch?.(query)}>
Tìm kiếm
</Button>
<Button
className="flex-1 rounded-none font-medium text-lg text-white hover:bg-muted-foreground hover:outline-1 outline-primary hover:text-primary"
onClick={() => {
setQuery('')
// restore initial map
const map: Record<string, boolean> = {}
categories.forEach((c) => (map[c.id] = false))
setSelected(map)
onReset?.()
}}
>
Bỏ tìm
</Button>
</div>
</aside>
)
}
export default ListFilter
'use client'
import React from "react";
import ListCategory from "./../components/list-category";
const Page = () => {
return (
<div className="min-h-screen container mx-auto pb-4">
<div className="w-full flex flex-col gap-5">
<ListCategory />
<div className="gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-7 px-40">
<div>
<h1 className="text-center mb-4 text-2xl font-bold text-[#153e8e]">THỦ TỤC GIA NHẬP HỘI VIÊN CHÍNH THỨC</h1>
<p className="text-justify leading-7 mb3">
Điều lệ sửa đổi của Liên đoàn Thương mại và Công nghiệp Việt Nam (VCCI) được Đại hội đại biểu toàn quốc VCCI lần thứ VII thông qua và được Thủ tướng Chính phủ Phê duyệt tại Quyết định số 1496/QĐ-TTg ngày 30/11/2022 đã quy định tất cả các doanh nghiệp, các tổ chức sản xuất, kinh doanh, người sử dụng lao động, các hiệp hội doanh nghiệp có đăng ký và hoạt động hợp pháp ở Việt Nam đều có thể trở thành hội viên của VCCI.
</p>
<p className="text-justify leading-7 mb-3">
Để trở thành hội viên chính thức, tổ chức quan tâm cần gởi VCCI tại Hà Nội hoặc các Chi nhánh, Văn phòng đại diện của VCCI hồ sơ gia nhập gồm:
</p>
<ul className="list-disc pl-10 space-y-1 font-bold">
<li className="text-justify leading-7">Đơn xin gia nhập làm hội viên chính thức VCCI (2 bản theo mẫu của VCCI)</li>
<li className="text-justify leading-7">Giấy phép đăng ký kinh doanh, hoặc giấy phép thành lập hoặc quyết định thành lập (2 bản sao).</li>
</ul>
<p className="text-justify leading-7 my-3">
Khi nhận được đơn, Ban Thường trực sẽ xét và thông báo cho tổ chức liên quan về quyết định kết nạp. Trong vòng 1 tháng kể từ ngày nhận thông báo, tổ chức phải thực hiện đóng lệ phí gia nhập. Chỉ khi nào tổ chức đóng lệ phí gia nhập mới được coi là hội viên chính thức. Theo quyết định của Ban chấp hành VCCI, lệ phí hiện hành được tính như sau:
</p>
<p className="text-justify leading-7 my-3">
Mức lệ phí gia nhập bằng mức hội phí hàng năm, được tính căn cứ vào doanh số của tổ chức trong năm trước theo các mức:
</p>
<ul className="list-disc pl-10 space-y-1 font-bold">
<li className="text-justify leading-7">Doanh số dưới 10 tỉ đồng đóng 3 triệu đồng/năm</li>
<li className="text-justify leading-7">Doanh số từ 10 {`-`} 50 tỉ đồng đóng 7 triệu đồng/năm</li>
<li className="text-justify leading-7">Doanh số trên 50 tỉ đồng đóng 15 triệu đồng/năm</li>
</ul>
<p className="text-justify leading-7 my-3">
Mức lệ phí gia nhập và hội phí trên có thể được điều chỉnh bởi quyết định của Ban chấp hành VCCI trong từng thời gian cụ thể.
</p>
<div>
<p className="text-[#063e8e] mb-3">
Để biết thêm thông tin chi tiết, vui lòng liên hệ:
</p>
<p className="text-[#063e8e]">
<b>Phòng Hội viên Đào tạo và Truyền thông</b>
<br />
<b>C. Thúy {`-`} DĐ: 0903 909 756</b>
<span className="block">
Email: luuthanhthuy72@yahoo.com; hoivien@vcci-hcm.org.vn;
</span>
<span className="block">
Điện thoại: 028.3932.0611 {`-`} Fax: 028.3932.5472
</span>
<span className="block">
Địa chỉ: P. 306, Lầu 3, Tòa nhà VCCI, 171 Võ Thị Sáu, Phường Xuân Hoà, TP. Hồ Chí Minh </span>
</p>
</div>
<p className="text-justify leading-7 my-3">
Biểu mẫu đính kèm
</p>
<ul className="list-disc pl-10 space-y-1">
<li className="text-justify leading-7 ">
<a href="https://vcci-hcm.org.vn/wp-content/uploads/2025/08/Don-dang-ky-tham-gia-nhap-hoi-vien-VCCI_Mau-Doanh-nghiep-1.docx" className="text-[#063e8e] hover:text-yellow-500 italic" download>
Đơn đăng ký tham gia nhập hội viên VCCI (Mẫu Doanh nghiệp)
</a>
</li>
<li className="text-justify leading-7">
<a href="https://vcci-hcm.org.vn/wp-content/uploads/2025/08/Don-dang-ky-tham-gia-nhap-hoi-vien-VCCI_Mau-Hiep-hoi.docx" className="text-[#063e8e] hover:text-yellow-500 italic" download>
Đơn đăng ký tham gia nhập hội viên VCCI (Mẫu Hiệp hội)
</a>
</li>
<li className="text-justify leading-7">
<a href="https://vcci-hcm.org.vn/wp-content/uploads/2025/08/Huong-dan-ho-so-dang-ky-Hoi-vien-VCCI.docx" className="text-[#063e8e] hover:text-yellow-500 italic" download>
Hướng dẫn hồ sơ đăng ký Hội viên VCCI
</a>
</li>
</ul>
</div >
</main>
</div >
</div >
</div >
);
};
export default Page;
\ No newline at end of file
"use client";
import React, { useState } from "react";
import ListCategory from "./../components/list-category";
import ListFilter from "./../components/list-filter";
import NewsContent from "./../components/card-news";
import { Pagination } from '@components/base/pagination'
import Image from "next/image";
import { useGetNews } from '@api/endpoints/news'
import { GetNewsResponseType } from '@api/types/NewsPage.type'
export default function Page() {
const [submitSearch] = useState('')
const [page, setPage] = useState(1)
const pageSize = 5
const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
})
return (
<div className="min-h-screen container mx-auto pb-4">
<div className="w-full flex flex-col gap-5">
<ListCategory />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background">
<div className='pb-5 overflow-hidden'>
{allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} />
))}
<div className='w-full flex justify-center mt-4'>
<Pagination
current={allData?.responseData.currentPage ?? page}
total={allData?.responseData.totalPages ?? 1}
onChange={(p) => setPage(p)}
/>
</div>
</div>
</main>
{/* Sidebar */}
<aside className="space-y-6">
<ListFilter />
</aside>
</div>
</div>
</div>
);
}
\ No newline at end of file
'use client'
import React from "react";
import ListCategory from "./components/list-category";
import EventCalendar from "./components/event-calendar";
const Page = () => {
return (
<div className="min-h-screen container mx-auto pb-4">
<div className="w-full flex flex-col gap-5">
<ListCategory />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-7">
<div>
<h1 className="text-2xl font-bold text-[#153e8e]">Lợi ích của Hội viên VCCI</h1>
<hr className="my-5" />
<p className="text-justify leading-7">
Hội viên chính thức là các doanh nghiệp, các tổ chức sản xuất, kinh doanh, người sử dụng lao động, các hiệp hội doanh nghiệp có đăng ký và hoạt động hợp pháp ở Việt Nam. Hội viên chính thức được thừa hưởng những quyền lợi và sau đây:
</p>
<div>
<img src="/hoi-vien/thong-tin.webp" alt="Thông tin" className="w-12 py-5" />
<strong className="block text-2xl text-gray-600 py-3">TIẾNG NÓI</strong>
<ul className="list-disc pl-10 space-y-1">
<li className="text-justify leading-7">Được hỗ trợ giải quyết các vướng mắc, kiến nghị của doanh nghiệp với các cơ quan quản lý trong quá trình kinh doanh và thực thi pháp luật.</li>
<li className="text-justify leading-7">Được tham dự miễn phí các hội nghị, hội thảo, đối thoại với các cơ quan ban ngành về pháp luật và chính sách hàng năm.</li>
<li className="text-justify leading-7">Được tham dự hội nghị đối thoại thường niên về chính sách thuế, hải quan và thủ tục hành chính.</li>
</ul>
</div>
<div>
<img src="/hoi-vien/thong-tin.webp" alt="Thông tin" className="w-12 py-5" />
<strong className="block text-2xl text-gray-600 py-3">ĐỘ NHẬN DIỆN</strong>
<ul className="list-disc pl-10 space-y-1">
<li className="text-justify leading-7">Hồ sơ doanh nghiệp được đăng tải miễn phí trên Danh bạ Hội viên CONNECTIONS.</li>
<li className="text-justify leading-7">Thông tin doanh nghiệp được giới thiệu miễn phí trên website và các kênh truyền thông của VCCI-HCM.</li>
<li className="text-justify leading-7">Cơ hội trở thành nhà tài trợ cho các sự kiện của VCCI-HCM.</li>
<li className="text-justify leading-7">Được ưu đãi đặc biệt khi sử dụng các dịch vụ truyền thông, quảng bá thương hiệu của VCCI-HCM.</li>
</ul>
</div>
<div>
<img src="/hoi-vien/thong-tin.webp" alt="Thông tin" className="w-12 py-5" />
<strong className="block text-2xl text-gray-600 py-3">MẠNG LƯỚI LIÊN KẾT</strong>
<ul className="list-disc pl-10 space-y-1">
<li className="text-justify leading-7">Được tham dự miễn phí các sự kiện xúc tiến thương mại và đầu tư hàng năm.</li>
<li className="text-justify leading-7">Được ưu đãi khi tham gia các đoàn khảo sát thị trường nước ngoài do VCCI-HCM tổ chức.</li>
<li className="text-justify leading-7">Tương tác trong mạng lưới hội viên VCCI- HCM, tiếp cận các đối tác tin cậy và khách hàng tiềm năng.</li>
</ul>
</div>
<div>
<img src="/hoi-vien/thong-tin.webp" alt="Thông tin" className="w-12 py-5" />
<strong className="block text-2xl text-gray-600 py-3">PHÁT TRIỂN</strong>
<ul className="list-disc pl-10 space-y-1">
<li className="text-justify leading-7">Được tham gia các khóa đào tạo của VCCI- HCM với chi phí ưu đãi.</li>
<li className="text-justify leading-7">Được tiếp cận các dự án hỗ trợ doanh nghiệp phát triển được tài trợ bởi các tổ chức trong nước và quốc tế.</li>
<li className="text-justify leading-7">Được tư vấn bởi các chuyên gia về pháp luật, quan hệ lao động, thị trường,… với chi phí ưu đãi.</li>
</ul>
</div>
<div>
<img src="/hoi-vien/thong-tin.webp" alt="Thông tin" className="w-12 py-5" />
<strong className="block text-2xl text-gray-600 py-3">ĐỘ TIN CẬY</strong>
<ul className="list-disc pl-10 space-y-1">
<li className="text-justify leading-7">Tăng uy tín và độ tin cậy cho doanh nghiệp khi trở thành hội viên VCCI-HCM.</li>
<li className="text-justify leading-7">Tạo thuận lợi cho doanh nghiệp trong các hoạt động hợp tác kinh doanh {`-`} đầu tư.</li>
</ul>
</div>
<div>
<img src="/hoi-vien/thong-tin.webp" alt="Thông tin" className="w-12 py-5" />
<strong className="block text-2xl text-gray-600 py-3">THÔNG TIN</strong>
<ul className="list-disc pl-10 space-y-1">
<li className="text-justify leading-7">Được nhận miễn phí Bản tin điện tử phát hành hàng tháng và Tạp chí VCCI-HCM phát hành hàng quý.</li>
<li className="text-justify leading-7">Được nhận miễn phí Danh bạ Hội viên VCCI-HCM.</li>
<li className="text-justify leading-7">Được nhận các sản phẩm thông tin phục vụ cho doanh nghiệp và các nhà đầu tư.</li>
</ul>
</div>
<div>
<p className="text-[#063e8e] py-5">
Để biết thêm thông tin chi tiết, vui lòng liên hệ:
</p>
<p className="text-[#063e8e]">
<b>Phòng Hội viên và Đào tạo:</b>
<br />
<b>C. Thanh Thúy {`-`} DĐ: 0903 909 756</b>
<span className="block">
Email: luuthanhthuy72@yahoo.com; hoivien@vcci-hcm.org.vn;
</span>
<span className="block">
Điện thoại: 028.3932.0611 {`-`} Fax: 028.3932.5472
</span>
<span className="block">
Địa chỉ: P.306, Lầu 3, Tòa nhà VCCI, 171 Võ Thị Sáu, Quận 3, TP. Hồ Chí Minh
</span>
</p>
</div>
</div >
</main>
{/* Sidebar */}
<aside className="space-y-6">
<EventCalendar />
</aside>
</div >
</div >
</div >
);
};
export default Page;
\ No newline at end of file
"use client";
import React, { useState } from "react";
import ListCategory from "./../components/list-category";
import ListFilter from "./../components/list-filter";
import NewsContent from "./../components/card-news";
import { Pagination } from '@components/base/pagination'
import Image from "next/image";
import { useGetNews } from '@api/endpoints/news'
import { GetNewsResponseType } from '@api/types/NewsPage.type'
export default function Page() {
const [submitSearch] = useState('')
const [page, setPage] = useState(1)
const pageSize = 5
const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
})
return (
<div className="min-h-screen container mx-auto pb-4">
<div className="w-full flex flex-col gap-5">
<ListCategory />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background">
<div className='pb-5 overflow-hidden'>
{allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} />
))}
<div className='w-full flex justify-center mt-4'>
<Pagination
current={allData?.responseData.currentPage ?? page}
total={allData?.responseData.totalPages ?? 1}
onChange={(p) => setPage(p)}
/>
</div>
</div>
</main>
{/* Sidebar */}
<aside className="space-y-6">
<ListFilter />
</aside>
</div>
</div>
</div>
);
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment