Commit 63b84398 by Ivan

feat: 时间范围过滤器

parent 82241f04
...@@ -13,6 +13,7 @@ class Admin::ImagesController < ApplicationController ...@@ -13,6 +13,7 @@ class Admin::ImagesController < ApplicationController
tag: @tag.as_json(only: [ :id, :name, :catalog ], methods: [ :images_count ]), tag: @tag.as_json(only: [ :id, :name, :catalog ], methods: [ :images_count ]),
images: @images.as_json(include: [ :user, :tags ], methods: [ :file_url, :thumbnail_url, :medium_url ]), images: @images.as_json(include: [ :user, :tags ], methods: [ :file_url, :thumbnail_url, :medium_url ]),
filters: params[:q] || {}, filters: params[:q] || {},
tags: Tag.all.to_json,
pagination: { pagination: {
current_page: @images.current_page, current_page: @images.current_page,
total_pages: @images.total_pages, total_pages: @images.total_pages,
...@@ -32,6 +33,7 @@ class Admin::ImagesController < ApplicationController ...@@ -32,6 +33,7 @@ class Admin::ImagesController < ApplicationController
total_pages: @images.total_pages, total_pages: @images.total_pages,
total_count: @images.total_count total_count: @images.total_count
}, },
tags: Tag.all.to_json,
pending_count: Image.pending.count, pending_count: Image.pending.count,
approved_count: Image.approved.count, approved_count: Image.approved.count,
rejected_count: Image.rejected.count rejected_count: Image.rejected.count
...@@ -74,7 +76,7 @@ class Admin::ImagesController < ApplicationController ...@@ -74,7 +76,7 @@ class Admin::ImagesController < ApplicationController
if params[:image] && params[:image][:tag_ids].present? if params[:image] && params[:image][:tag_ids].present?
@image.set_tags_by_ids(params[:image][:tag_ids]) @image.set_tags_by_ids(params[:image][:tag_ids])
end end
# Add the tag if we're creating from a tag context # Add the tag if we're creating from a tag context
if params[:tag_id].present? && @tag if params[:tag_id].present? && @tag
@image.tags << @tag unless @image.tags.include?(@tag) @image.tags << @tag unless @image.tags.include?(@tag)
......
...@@ -15,6 +15,7 @@ class ImagesController < ApplicationController ...@@ -15,6 +15,7 @@ class ImagesController < ApplicationController
tag: @tag.as_json(only: [ :id, :name, :catalog ], methods: [ :images_count ]), tag: @tag.as_json(only: [ :id, :name, :catalog ], methods: [ :images_count ]),
images: @images.as_json(include: [ :user, :tags ], methods: [ :file_url, :thumbnail_url, :medium_url ]), images: @images.as_json(include: [ :user, :tags ], methods: [ :file_url, :thumbnail_url, :medium_url ]),
filters: params[:q] || {}, filters: params[:q] || {},
tags: Tag.all.to_json,
pagination: { pagination: {
current_page: @images.current_page, current_page: @images.current_page,
total_pages: @images.total_pages, total_pages: @images.total_pages,
...@@ -29,6 +30,7 @@ class ImagesController < ApplicationController ...@@ -29,6 +30,7 @@ class ImagesController < ApplicationController
render inertia: "images/Index", props: { render inertia: "images/Index", props: {
images: @images.as_json(include: [ :user, :tags ], methods: [ :file_url, :thumbnail_url, :medium_url ]), images: @images.as_json(include: [ :user, :tags ], methods: [ :file_url, :thumbnail_url, :medium_url ]),
filters: params[:q] || {}, filters: params[:q] || {},
tags: Tag.all.to_json,
pagination: { pagination: {
current_page: @images.current_page, current_page: @images.current_page,
total_pages: @images.total_pages, total_pages: @images.total_pages,
......
import { useState, useEffect } from 'react'
import * as Popover from '@radix-ui/react-popover'
import * as Select from '@radix-ui/react-select'
import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon, XMarkIcon, ChevronDownIcon } from '@heroicons/react/24/solid'
import dayjs from 'dayjs'
// 日期选择器组件
export default function DateRangePicker({ startDate, endDate, onChange, onStartDateChange, onEndDateChange }) {
const [currentMonth, setCurrentMonth] = useState(dayjs())
const [nextMonth, setNextMonth] = useState(dayjs().add(1, 'month'))
const [selectedStartDate, setSelectedStartDate] = useState(startDate ? dayjs(startDate) : null)
const [selectedEndDate, setSelectedEndDate] = useState(endDate ? dayjs(endDate) : null)
// 确保初始值设置正确
useEffect(() => {
if (startDate && !selectedStartDate) {
setSelectedStartDate(dayjs(startDate))
}
if (endDate && !selectedEndDate) {
setSelectedEndDate(dayjs(endDate))
}
}, [startDate, endDate])
const [isOpen, setIsOpen] = useState(false)
const monthOptions = [
'一月', '二月', '三月', '四月', '五月', '六月',
'七月', '八月', '九月', '十月', '十一月', '十二月'
]
const yearOptions = Array.from({ length: 10 }, (_, i) => dayjs().year() - 5 + i)
// 快速选择选项
const quickSelectOptions = [
{ label: '今天', value: 'today' },
{ label: '昨天', value: 'yesterday' },
{ label: '本周', value: 'this_week' },
{ label: '上周', value: 'last_week' },
{ label: '最近7天', value: 'last_7_days' },
{ label: '本月', value: 'this_month' },
{ label: '上月', value: 'last_month' },
{ label: '本年', value: 'this_year' },
{ label: '上年', value: 'last_year' }
]
// 获取月份的天数数组
const getDaysInMonth = (year, month) => {
const date = dayjs().year(year).month(month).startOf('month')
const days = []
const firstDay = date.day() // 0 是周日,1 是周一
// 添加上个月的日期填充第一周
const prevMonth = date.subtract(1, 'month')
const prevMonthDays = prevMonth.daysInMonth()
for (let i = firstDay - 1; i >= 0; i--) {
days.push({
date: prevMonth.date(prevMonthDays - i),
isCurrentMonth: false
})
}
// 添加当前月的日期
const daysInMonth = date.daysInMonth()
for (let i = 1; i <= daysInMonth; i++) {
days.push({
date: date.date(i),
isCurrentMonth: true
})
}
// 添加下个月的日期填充最后一周
const lastDayOfMonth = date.date(daysInMonth).day() // 0 是周日,6 是周六
const daysToAdd = 6 - lastDayOfMonth
const nextMonth = date.add(1, 'month').date(1)
for (let i = 1; i <= daysToAdd; i++) {
days.push({
date: nextMonth.date(i),
isCurrentMonth: false
})
}
return days
}
// 处理月份变更
const handlePrevMonth = () => {
setCurrentMonth(currentMonth.subtract(1, 'month'))
setNextMonth(nextMonth.subtract(1, 'month'))
}
const handleNextMonth = () => {
setCurrentMonth(currentMonth.add(1, 'month'))
setNextMonth(nextMonth.add(1, 'month'))
}
// 处理年份变更
const handleYearChange = (calendarIndex, year) => {
if (calendarIndex === 0) {
const newCurrentMonth = currentMonth.year(parseInt(year))
setCurrentMonth(newCurrentMonth)
setNextMonth(newCurrentMonth.add(1, 'month'))
} else {
const newNextMonth = nextMonth.year(parseInt(year))
setNextMonth(newNextMonth)
setCurrentMonth(newNextMonth.subtract(1, 'month'))
}
}
// 处理月份变更
const handleMonthChange = (calendarIndex, month) => {
if (calendarIndex === 0) {
const newCurrentMonth = currentMonth.month(parseInt(month))
setCurrentMonth(newCurrentMonth)
setNextMonth(newCurrentMonth.add(1, 'month'))
} else {
const newNextMonth = nextMonth.month(parseInt(month))
setNextMonth(newNextMonth)
setCurrentMonth(newNextMonth.subtract(1, 'month'))
}
}
// 处理日期选择
const handleDateSelect = (date) => {
if (!selectedStartDate || (selectedStartDate && selectedEndDate)) {
// 选择开始日期
setSelectedStartDate(date)
setSelectedEndDate(null)
// 确保调用父组件的回调函数,传递正确的日期字符串
const formattedDate = formatDate(date)
onChange(formattedDate, '')
} else {
if (date.isBefore(selectedStartDate)) {
// 如果选择的日期早于已选的开始日期,则调整范围
setSelectedStartDate(date)
setSelectedEndDate(selectedStartDate)
const formattedStartDate = formatDate(date)
const formattedEndDate = formatDate(selectedStartDate)
onChange(formattedStartDate, formattedEndDate)
} else {
// 选择结束日期,保持开始日期不变
setSelectedEndDate(date)
const formattedStartDate = formatDate(selectedStartDate)
const formattedEndDate = formatDate(date)
// 重要:同时再次传递开始日期,确保不会丢失
onChange(formattedStartDate, formattedEndDate)
}
}
}
// 处理快速选择
const handleQuickSelect = (option) => {
const today = dayjs().startOf('day')
let start, end
switch (option) {
case 'today':
start = end = today
break
case 'yesterday':
start = end = today.subtract(1, 'day')
break
case 'this_week':
start = today.startOf('week') // dayjs 默认周日为一周的开始
end = today.endOf('week')
break
case 'last_week':
start = today.subtract(1, 'week').startOf('week')
end = today.subtract(1, 'week').endOf('week')
break
case 'last_7_days':
start = today.subtract(6, 'day')
end = today
break
case 'this_month':
start = today.startOf('month')
end = today.endOf('month')
break
case 'last_month':
start = today.subtract(1, 'month').startOf('month')
end = today.subtract(1, 'month').endOf('month')
break
case 'this_year':
start = today.startOf('year')
end = today.endOf('year')
break
case 'last_year':
start = today.subtract(1, 'year').startOf('year')
end = today.subtract(1, 'year').endOf('year')
break
default:
return
}
setSelectedStartDate(start)
setSelectedEndDate(end)
// 确保调用父组件的回调函数,传递正确的日期字符串
const formattedStartDate = formatDate(start)
const formattedEndDate = formatDate(end)
onStartDateChange(formattedStartDate)
onEndDateChange(formattedEndDate)
// 调试日志
setIsOpen(false)
}
// 格式化日期为 YYYY-MM-DD
const formatDate = (date) => {
if (!date) return ''
// 确保返回正确格式的日期字符串
const formatted = date.format('YYYY-MM-DD')
return formatted
}
// 格式化显示日期
const formatDisplayDate = (date) => {
if (!date) return ''
return date.format('YYYY-MM-DD')
}
// 判断日期是否在选中范围内
const isInRange = (date) => {
if (!selectedStartDate || !selectedEndDate) return false
return date.isAfter(selectedStartDate) && date.isBefore(selectedEndDate)
}
// 判断是否是今天
const isToday = (date) => {
return date.format('YYYY-MM-DD') === dayjs().format('YYYY-MM-DD')
}
// 渲染日历
const renderCalendar = (year, month) => {
const days = getDaysInMonth(year, month)
const weekdays = ['日', '一', '二', '三', '四', '五', '六']
return (
<div className="calendar">
<div className="grid grid-cols-7 gap-1">
{weekdays.map((day, index) => (
<div key={index} className="text-center text-xs font-medium text-gray-500 py-2">
{day}
</div>
))}
{days.map((day, index) => {
const isSelected = selectedStartDate && day.date.format('YYYY-MM-DD') === selectedStartDate.format('YYYY-MM-DD') ||
selectedEndDate && day.date.format('YYYY-MM-DD') === selectedEndDate.format('YYYY-MM-DD')
const inRange = isInRange(day.date)
return (
<button
key={index}
type="button"
onClick={() => handleDateSelect(day.date)}
className={`
h-8 w-8 rounded-full flex items-center justify-center text-sm
${!day.isCurrentMonth ? 'text-gray-400' : ''}
${isToday(day.date) && !isSelected ? 'border border-indigo-500' : ''}
${isSelected ? 'bg-indigo-600 text-white' : ''}
${inRange ? 'bg-indigo-100' : ''}
${day.isCurrentMonth && !isSelected && !inRange ? 'hover:bg-gray-100' : ''}
`}
>
{day.date.date()}
</button>
)
})}
</div>
</div>
)
}
return (
<div className="relative">
<div className="flex items-center">
<div className="relative w-full">
<Popover.Root open={isOpen} onOpenChange={setIsOpen}>
<Popover.Trigger asChild>
<button
type="button"
className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md bg-white px-3 py-2 text-left border flex items-center justify-between"
>
<span>
{selectedStartDate && selectedEndDate
? `${formatDisplayDate(selectedStartDate)} 至 ${formatDisplayDate(selectedEndDate)}`
: selectedStartDate
? `${formatDisplayDate(selectedStartDate)} 至 未选择`
: '选择日期范围'}
</span>
<CalendarIcon className="h-5 w-5 text-gray-400" />
</button>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content
className="bg-white rounded-md shadow-lg p-4 w-[700px] border border-gray-200 z-50"
sideOffset={5}
>
<div className="flex flex-col">
<div className="flex justify-between items-center mb-4">
<div className="text-sm font-medium text-gray-700">选择日期范围</div>
<Popover.Close className="rounded-full p-1 hover:bg-gray-100">
<XMarkIcon className="h-4 w-4 text-gray-500" />
</Popover.Close>
</div>
<div className="flex space-x-4">
{/* 左侧快速选择 */}
<div className="w-1/4 border-r pr-4">
<div className="text-sm font-medium text-gray-700 mb-2">快速选择</div>
<div className="space-y-1">
{quickSelectOptions.map((option) => (
<button
key={option.value}
type="button"
className="block w-full text-left px-2 py-1 text-sm rounded hover:bg-gray-100"
onClick={() => handleQuickSelect(option.value)}
>
{option.label}
</button>
))}
</div>
</div>
{/* 右侧日历 */}
<div className="w-3/4">
<div className="flex justify-between items-center mb-4">
<button
type="button"
onClick={handlePrevMonth}
className="p-1 rounded-full hover:bg-gray-100"
>
<ChevronLeftIcon className="h-5 w-5 text-gray-500" />
</button>
<div className="flex space-x-4">
<div className="flex items-center space-x-2">
<Select.Root value={currentMonth.year().toString()} onValueChange={(value) => handleYearChange(0, value)}>
<Select.Trigger className="inline-flex items-center justify-center rounded px-2 py-1 text-sm leading-none h-8 gap-1 bg-white text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none">
<Select.Value />
<Select.Icon>
<ChevronDownIcon className="h-4 w-4" />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content className="overflow-hidden bg-white rounded-md shadow-lg z-[9999]">
<Select.Viewport className="p-1">
<Select.Group>
{yearOptions.map(year => (
<Select.Item key={year} value={year.toString()} className="text-sm leading-none rounded-md flex items-center h-8 pr-9 pl-6 relative select-none data-[highlighted]:outline-none data-[highlighted]:bg-indigo-100 data-[highlighted]:text-indigo-900">
<Select.ItemText>{year}</Select.ItemText>
</Select.Item>
))}
</Select.Group>
</Select.Viewport>
</Select.Content>
</Select.Portal>
</Select.Root>
<Select.Root value={currentMonth.month().toString()} onValueChange={(value) => handleMonthChange(0, value)}>
<Select.Trigger className="inline-flex items-center justify-center rounded px-2 py-1 text-sm leading-none h-8 gap-1 bg-white text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none">
<Select.Value placeholder={monthOptions[currentMonth.month()]} />
<Select.Icon>
<ChevronDownIcon className="h-4 w-4" />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content className="overflow-hidden bg-white rounded-md shadow-lg z-[9999]">
<Select.Viewport className="p-1">
<Select.Group>
{monthOptions.map((month, index) => (
<Select.Item key={index} value={index.toString()} className="text-sm leading-none rounded-md flex items-center h-8 pr-9 pl-6 relative select-none data-[highlighted]:outline-none data-[highlighted]:bg-indigo-100 data-[highlighted]:text-indigo-900">
<Select.ItemText>{month}</Select.ItemText>
</Select.Item>
))}
</Select.Group>
</Select.Viewport>
</Select.Content>
</Select.Portal>
</Select.Root>
</div>
<div className="flex items-center space-x-2">
<Select.Root value={nextMonth.year().toString()} onValueChange={(value) => handleYearChange(1, value)}>
<Select.Trigger className="inline-flex items-center justify-center rounded px-2 py-1 text-sm leading-none h-8 gap-1 bg-white text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none">
<Select.Value />
<Select.Icon>
<ChevronDownIcon className="h-4 w-4" />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content className="overflow-hidden bg-white rounded-md shadow-lg z-[9999]">
<Select.Viewport className="p-1">
<Select.Group>
{yearOptions.map(year => (
<Select.Item key={year} value={year.toString()} className="text-sm leading-none rounded-md flex items-center h-8 pr-9 pl-6 relative select-none data-[highlighted]:outline-none data-[highlighted]:bg-indigo-100 data-[highlighted]:text-indigo-900">
<Select.ItemText>{year}</Select.ItemText>
</Select.Item>
))}
</Select.Group>
</Select.Viewport>
</Select.Content>
</Select.Portal>
</Select.Root>
<Select.Root value={nextMonth.month().toString()} onValueChange={(value) => handleMonthChange(1, value)}>
<Select.Trigger className="inline-flex items-center justify-center rounded px-2 py-1 text-sm leading-none h-8 gap-1 bg-white text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none">
<Select.Value placeholder={monthOptions[nextMonth.month()]} />
<Select.Icon>
<ChevronDownIcon className="h-4 w-4" />
</Select.Icon>
</Select.Trigger>
<Select.Portal>
<Select.Content className="overflow-hidden bg-white rounded-md shadow-lg z-[9999]">
<Select.Viewport className="p-1">
<Select.Group>
{monthOptions.map((month, index) => (
<Select.Item key={index} value={index.toString()} className="text-sm leading-none rounded-md flex items-center h-8 pr-9 pl-6 relative select-none data-[highlighted]:outline-none data-[highlighted]:bg-indigo-100 data-[highlighted]:text-indigo-900">
<Select.ItemText>{month}</Select.ItemText>
</Select.Item>
))}
</Select.Group>
</Select.Viewport>
</Select.Content>
</Select.Portal>
</Select.Root>
</div>
</div>
<button
type="button"
onClick={handleNextMonth}
className="p-1 rounded-full hover:bg-gray-100"
>
<ChevronRightIcon className="h-5 w-5 text-gray-500" />
</button>
</div>
<div className="flex space-x-4">
<div className="w-1/2">
{renderCalendar(currentMonth.year(), currentMonth.month())}
</div>
<div className="w-1/2">
{renderCalendar(nextMonth.year(), nextMonth.month())}
</div>
</div>
</div>
</div>
</div>
</Popover.Content>
</Popover.Portal>
</Popover.Root>
</div>
</div>
</div>
)
}
...@@ -10,7 +10,7 @@ export default function ImageUploadForm({ ...@@ -10,7 +10,7 @@ export default function ImageUploadForm({
onSubmit, onSubmit,
submitButtonText = '上传图片', submitButtonText = '上传图片',
processingButtonText = '上传中...', processingButtonText = '上传中...',
showTagsField = false, showTagsField = true,
isEdit = false, isEdit = false,
image = null, image = null,
tags = [], tags = [],
......
...@@ -12,34 +12,74 @@ export default function TagSearchInput({ value, onChange, allTags = {}, placehol ...@@ -12,34 +12,74 @@ export default function TagSearchInput({ value, onChange, allTags = {}, placehol
const [filteredTags, setFilteredTags] = useState({}) const [filteredTags, setFilteredTags] = useState({})
const [internalValue, setInternalValue] = useState(value || []) const [internalValue, setInternalValue] = useState(value || [])
// 只在组件初始化或外部value变化时更新selectedTags // 处理标签数据和选中标签
useEffect(() => { useEffect(() => {
if (isEqual(value, internalValue)) { // 处理标签数据,支持数组或已分组的对象格式
setInternalValue(value || []) let processedTags = {}
if (value) { let tagsData = allTags
const tagIds = value
// 如果是 JSON 字符串,先解析
if (typeof allTags === 'string') {
try {
tagsData = JSON.parse(allTags)
} catch (e) {
console.error('Failed to parse tags JSON:', e)
tagsData = []
}
}
// 如果传入的是数组,需要按catalog属性分组
if (Array.isArray(tagsData)) {
// 按catalog属性分组标签
const groupedByCategory = {}
tagsData.forEach(tag => {
const catalog = tag.catalog || ''
if (!groupedByCategory[catalog]) {
groupedByCategory[catalog] = []
}
groupedByCategory[catalog].push(tag)
})
processedTags = groupedByCategory
} else {
// 如果已经是分组好的对象,直接使用
processedTags = tagsData || {}
}
// 更新分组后的标签
setAvailableTags(processedTags)
// 处理选中的标签
// 使用 JSON.stringify 比较数组,因于 isEqual 可能不总是可靠的用于空数组
const valueChanged = JSON.stringify(value) !== JSON.stringify(internalValue);
// 如果值发生变化或强制更新,则更新内部值
if (valueChanged) {
const newValue = value || [];
setInternalValue(newValue);
// 如果有标签 ID,则找出对应的标签对象
if (newValue && newValue.length > 0) {
const tagIds = newValue;
// 从所有可用标签中找出已选标签 // 从所有可用标签中找出已选标签
const selected = [] const selected = [];
Object.values(allTags).forEach(tagGroup => { Object.values(processedTags).forEach(tagGroup => {
tagGroup.forEach(tag => { tagGroup.forEach(tag => {
if (tagIds.includes(tag.id)) { if (tagIds.includes(tag.id)) {
selected.push(tag) selected.push(tag);
} }
}) });
}) });
setSelectedTags(selected) setSelectedTags(selected);
} else { } else {
setSelectedTags([]) // 如果标签 ID 为空,则清空选中标签
setSelectedTags([]);
} }
} }
}, [value, allTags]) }, [allTags, value, internalValue])
// 使用传入的标签数据
useEffect(() => {
setAvailableTags(allTags || {})
}, [allTags])
// 根据搜索词过滤标签 // 根据搜索词过滤标签
useEffect(() => { useEffect(() => {
...@@ -97,7 +137,7 @@ export default function TagSearchInput({ value, onChange, allTags = {}, placehol ...@@ -97,7 +137,7 @@ export default function TagSearchInput({ value, onChange, allTags = {}, placehol
} }
return ( return (
<div className="relative"> <div className="relative mt-1">
<Popover.Root open={isOpen} onOpenChange={setIsOpen} modal={false}> <Popover.Root open={isOpen} onOpenChange={setIsOpen} modal={false}>
<Popover.Trigger asChild> <Popover.Trigger asChild>
<div className="flex flex-wrap gap-2 min-h-[2.5rem] p-2 border border-gray-300 rounded-md bg-white cursor-pointer"> <div className="flex flex-wrap gap-2 min-h-[2.5rem] p-2 border border-gray-300 rounded-md bg-white cursor-pointer">
...@@ -123,7 +163,7 @@ export default function TagSearchInput({ value, onChange, allTags = {}, placehol ...@@ -123,7 +163,7 @@ export default function TagSearchInput({ value, onChange, allTags = {}, placehol
))} ))}
{selectedTags.length === 0 && ( {selectedTags.length === 0 && (
<div className="text-gray-400 text-sm">{placeholder}</div> <div className="text-gray-400 text-sm pl-1">{placeholder}</div>
)} )}
</div> </div>
</Popover.Trigger> </Popover.Trigger>
...@@ -172,7 +212,7 @@ export default function TagSearchInput({ value, onChange, allTags = {}, placehol ...@@ -172,7 +212,7 @@ export default function TagSearchInput({ value, onChange, allTags = {}, placehol
{catalog || '未分类'} {catalog || '未分类'}
</h3> </h3>
<div className="space-y-1"> <div className="space-y-1">
{tags.map(tag => { {Array.isArray(tags) ? tags.map(tag => {
const isSelected = selectedTags.some(t => t.id === tag.id) const isSelected = selectedTags.some(t => t.id === tag.id)
return ( return (
<button <button
...@@ -194,7 +234,7 @@ export default function TagSearchInput({ value, onChange, allTags = {}, placehol ...@@ -194,7 +234,7 @@ export default function TagSearchInput({ value, onChange, allTags = {}, placehol
)} )}
</button> </button>
) )
})} }) : <div className="text-sm text-gray-500 py-1">无可用标签</div>}
</div> </div>
</div> </div>
))} ))}
......
...@@ -13,27 +13,6 @@ export default function ComImageEdit({ image, tags, path, isAdmin = false }) { ...@@ -13,27 +13,6 @@ export default function ComImageEdit({ image, tags, path, isAdmin = false }) {
title: image.title || '', title: image.title || '',
tag_ids: initialTagIds, // 使用tag_ids而不是tags tag_ids: initialTagIds, // 使用tag_ids而不是tags
}) })
// 获取所有标签数据并按目录分组
const [groupedTags, setGroupedTags] = useState({})
// 使用控制器返回的所有标签数据并按catalog属性分组
useEffect(() => {
if (Array.isArray(tags)) {
// 按catalog属性分组标签
const groupedByCategory = {}
tags.forEach(tag => {
const catalog = tag.catalog || ''
if (!groupedByCategory[catalog]) {
groupedByCategory[catalog] = []
}
groupedByCategory[catalog].push(tag)
})
setGroupedTags(groupedByCategory)
}
}, [tags])
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault() e.preventDefault()
...@@ -85,7 +64,7 @@ export default function ComImageEdit({ image, tags, path, isAdmin = false }) { ...@@ -85,7 +64,7 @@ export default function ComImageEdit({ image, tags, path, isAdmin = false }) {
<TagSearchInput <TagSearchInput
value={data.tag_ids} value={data.tag_ids}
onChange={(value) => setData('tag_ids', value)} onChange={(value) => setData('tag_ids', value)}
allTags={groupedTags} allTags={tags}
placeholder="点击搜索图标添加标签" placeholder="点击搜索图标添加标签"
/> />
</Form.Control> </Form.Control>
......
...@@ -2,10 +2,16 @@ import { useState, useEffect, useRef, useCallback } from 'react' ...@@ -2,10 +2,16 @@ import { useState, useEffect, useRef, useCallback } from 'react'
import { Head, Link, router } from '@inertiajs/react' import { Head, Link, router } from '@inertiajs/react'
import * as Form from '@radix-ui/react-form' import * as Form from '@radix-ui/react-form'
import * as Collapsible from '@radix-ui/react-collapsible' import * as Collapsible from '@radix-ui/react-collapsible'
import * as Select from '@radix-ui/react-select'
import { uniqBy } from 'lodash-es' import { uniqBy } from 'lodash-es'
import { ComImageCard } from './ComImageCard' import { ComImageCard } from './ComImageCard'
import TagSearchInput from '../TagSearchInput'
import DateRangePicker from '../DateRangePicker'
import { ChevronDownIcon, ChevronUpIcon, XMarkIcon } from '@heroicons/react/24/solid'
export default function ComImageIndex({ title, description, path, images, pagination, filters, auth, backLink, actionButton, showUserName = false }) {
export default function ComImageIndex({ title, description, path, images, pagination, filters, auth, backLink, actionButton, showUserName = false, allTags = {} }) {
const [allImages, setAllImages] = useState(images || []) const [allImages, setAllImages] = useState(images || [])
const [page, setPage] = useState(pagination?.current_page || 1) const [page, setPage] = useState(pagination?.current_page || 1)
const [maxVisiblePage, setMaxVisiblePage] = useState(0) const [maxVisiblePage, setMaxVisiblePage] = useState(0)
...@@ -20,6 +26,7 @@ export default function ComImageIndex({ title, description, path, images, pagina ...@@ -20,6 +26,7 @@ export default function ComImageIndex({ title, description, path, images, pagina
tags_id_in: filters.tags_id_in || [], tags_id_in: filters.tags_id_in || [],
created_at_gteq: filters.created_at_gteq || '', created_at_gteq: filters.created_at_gteq || '',
created_at_lteq: filters.created_at_lteq || '', created_at_lteq: filters.created_at_lteq || '',
status_eq: filters.status_eq || '',
}) })
const [isFilterOpen, setIsFilterOpen] = useState(false) const [isFilterOpen, setIsFilterOpen] = useState(false)
...@@ -28,11 +35,34 @@ export default function ComImageIndex({ title, description, path, images, pagina ...@@ -28,11 +35,34 @@ export default function ComImageIndex({ title, description, path, images, pagina
setSearchParams({ ...searchParams, [name]: value }) setSearchParams({ ...searchParams, [name]: value })
} }
const handleTagsChange = (value) => {
setSearchParams({ ...searchParams, tags_id_in: value })
}
const handleStatusChange = (value) => {
const statusValue = value === 'all' ? '' : value
setSearchParams({ ...searchParams, status_eq: statusValue })
}
const clearStatus = () => {
setSearchParams({ ...searchParams, status_eq: '' })
}
const handleSearch = (e) => { const handleSearch = (e) => {
e.preventDefault() e.preventDefault()
setLoadedPageMap({}) setLoadedPageMap({})
setMaxVisiblePage(0) setMaxVisiblePage(0)
setAllImages([]) setAllImages([])
// 打印搜索参数以便调试
console.log('搜索参数:', {
title_cont: searchParams.title_cont,
tags_name_cont: searchParams.tags_name_cont,
tags_id_in: searchParams.tags_id_in,
created_at_gteq: searchParams.created_at_gteq,
created_at_lteq: searchParams.created_at_lteq,
status_eq: searchParams.status_eq
})
loadPage(1) loadPage(1)
} }
...@@ -44,6 +74,19 @@ export default function ComImageIndex({ title, description, path, images, pagina ...@@ -44,6 +74,19 @@ export default function ComImageIndex({ title, description, path, images, pagina
try { try {
setLoading(true) setLoading(true)
// 打印请求参数以便调试
console.log('发送请求参数:', {
page: currentPage,
q: {
title_cont: searchParams.title_cont,
tags_name_cont: searchParams.tags_name_cont,
tags_id_in: searchParams.tags_id_in,
created_at_gteq: searchParams.created_at_gteq,
created_at_lteq: searchParams.created_at_lteq,
status_eq: searchParams.status_eq
}
})
await router.visit( await router.visit(
path, path,
...@@ -55,8 +98,9 @@ export default function ComImageIndex({ title, description, path, images, pagina ...@@ -55,8 +98,9 @@ export default function ComImageIndex({ title, description, path, images, pagina
title_cont: searchParams.title_cont, title_cont: searchParams.title_cont,
tags_name_cont: searchParams.tags_name_cont, tags_name_cont: searchParams.tags_name_cont,
tags_id_in: searchParams.tags_id_in, tags_id_in: searchParams.tags_id_in,
created_at_gteq: searchParams.created_at_gteq, created_at_gteq: searchParams.created_at_gteq ? searchParams.created_at_gteq : '',
created_at_lteq: searchParams.created_at_lteq created_at_lteq: searchParams.created_at_lteq ? searchParams.created_at_lteq : '',
status_eq: searchParams.status_eq
} }
}, },
preserveState: true, preserveState: true,
...@@ -216,34 +260,108 @@ export default function ComImageIndex({ title, description, path, images, pagina ...@@ -216,34 +260,108 @@ export default function ComImageIndex({ title, description, path, images, pagina
</div> </div>
<div className="sm:col-span-3"> <div className="sm:col-span-3">
<Form.Field name="created_at_gteq"> <Form.Field name="tags_id_in">
<Form.Label className="block text-sm font-medium text-gray-700"> <Form.Label className="block text-sm font-medium text-gray-700">
开始日期 选择标签
</Form.Label> </Form.Label>
<Form.Control asChild> <Form.Control asChild>
<input <TagSearchInput
type="date" value={searchParams.tags_id_in}
name="created_at_gteq" onChange={handleTagsChange}
value={searchParams.created_at_gteq} allTags={allTags}
onChange={handleInputChange} placeholder="选择标签进行筛选"
className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
/> />
</Form.Control> </Form.Control>
</Form.Field> </Form.Field>
</div> </div>
<div className="sm:col-span-3"> <div className="sm:col-span-3">
<Form.Field name="created_at_lteq"> <Form.Field name="status_eq">
<Form.Label className="block text-sm font-medium text-gray-700"> <Form.Label className="block text-sm font-medium text-gray-700">
结束日期 状态
</Form.Label> </Form.Label>
<Form.Control asChild> <Form.Control asChild>
<input <Select.Root
type="date" value={searchParams.status_eq || "all"}
name="created_at_lteq" onValueChange={handleStatusChange}
value={searchParams.created_at_lteq} >
<div className="relative">
<Select.Trigger className="mt-1 inline-flex items-center justify-between w-full px-3 py-2 text-sm border border-gray-300 rounded-md shadow-sm bg-white focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
<Select.Value placeholder="选择状态" />
<Select.Icon>
<ChevronDownIcon className="h-4 w-4 text-gray-500" />
</Select.Icon>
</Select.Trigger>
{searchParams.status_eq && (
<button
type="button"
onClick={clearStatus}
className="absolute right-8 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
aria-label="清空状态筛选"
>
<XMarkIcon className="h-4 w-4" />
</button>
)}
</div>
<Select.Portal>
<Select.Content className="overflow-hidden bg-white rounded-md shadow-lg border border-gray-200 z-50">
<Select.ScrollUpButton className="flex items-center justify-center h-6 bg-white text-gray-700 cursor-default">
<ChevronUpIcon className="h-4 w-4" />
</Select.ScrollUpButton>
<Select.Viewport className="p-1">
<Select.Group>
<Select.Item value="all" className="relative flex items-center h-8 px-6 py-2 text-sm rounded hover:bg-indigo-100 hover:text-indigo-900 cursor-pointer outline-none data-[highlighted]:bg-indigo-100 data-[highlighted]:text-indigo-900">
<Select.ItemText>全部状态</Select.ItemText>
</Select.Item>
<Select.Item value="pending" className="relative flex items-center h-8 px-6 py-2 text-sm rounded hover:bg-indigo-100 hover:text-indigo-900 cursor-pointer outline-none data-[highlighted]:bg-indigo-100 data-[highlighted]:text-indigo-900">
<Select.ItemText>待审核</Select.ItemText>
</Select.Item>
<Select.Item value="approved" className="relative flex items-center h-8 px-6 py-2 text-sm rounded hover:bg-indigo-100 hover:text-indigo-900 cursor-pointer outline-none data-[highlighted]:bg-indigo-100 data-[highlighted]:text-indigo-900">
<Select.ItemText>已通过</Select.ItemText>
</Select.Item>
<Select.Item value="rejected" className="relative flex items-center h-8 px-6 py-2 text-sm rounded hover:bg-indigo-100 hover:text-indigo-900 cursor-pointer outline-none data-[highlighted]:bg-indigo-100 data-[highlighted]:text-indigo-900">
<Select.ItemText>已拒绝</Select.ItemText>
</Select.Item>
</Select.Group>
</Select.Viewport>
<Select.ScrollDownButton className="flex items-center justify-center h-6 bg-white text-gray-700 cursor-default">
<ChevronDownIcon className="h-4 w-4" />
</Select.ScrollDownButton>
</Select.Content>
</Select.Portal>
</Select.Root>
{/* <select
name="status_eq"
value={searchParams.status_eq}
onChange={handleInputChange} onChange={handleInputChange}
className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md" className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
>
<option value="">全部状态</option>
<option value="pending">待审核</option>
<option value="approved">已通过</option>
<option value="rejected">已拒绝</option>
</select> */}
</Form.Control>
</Form.Field>
</div>
<div className="sm:col-span-6">
<Form.Field name="date_range">
<Form.Label className="block text-sm font-medium text-gray-700">
日期范围
</Form.Label>
<Form.Control asChild>
<DateRangePicker
startDate={searchParams.created_at_gteq}
endDate={searchParams.created_at_lteq}
onChange={(startDate, endDate) => {
const newParams = { ...searchParams, created_at_gteq: startDate, created_at_lteq: endDate }
setSearchParams(newParams)
}}
/> />
</Form.Control> </Form.Control>
</Form.Field> </Form.Field>
...@@ -257,8 +375,10 @@ export default function ComImageIndex({ title, description, path, images, pagina ...@@ -257,8 +375,10 @@ export default function ComImageIndex({ title, description, path, images, pagina
setSearchParams({ setSearchParams({
title_cont: '', title_cont: '',
tags_name_cont: '', tags_name_cont: '',
tags_id_in: [],
created_at_gteq: '', created_at_gteq: '',
created_at_lteq: '', created_at_lteq: '',
status_eq: '',
}) })
}} }}
> >
......
...@@ -2,7 +2,7 @@ import { Head } from '@inertiajs/react' ...@@ -2,7 +2,7 @@ import { Head } from '@inertiajs/react'
import Layout from '../../Layout' import Layout from '../../Layout'
import ComImageIndex from '../../../components/images/ComImageIndex' import ComImageIndex from '../../../components/images/ComImageIndex'
export default function AdminImagesIndex({ auth, images, pagination, filters }) { export default function AdminImagesIndex({ auth, images, pagination, filters, tags }) {
return ( return (
<Layout user={auth}> <Layout user={auth}>
<Head title="图片管理" /> <Head title="图片管理" />
...@@ -15,6 +15,7 @@ export default function AdminImagesIndex({ auth, images, pagination, filters }) ...@@ -15,6 +15,7 @@ export default function AdminImagesIndex({ auth, images, pagination, filters })
pagination={pagination} pagination={pagination}
filters={filters} filters={filters}
showUserName={true} showUserName={true}
allTags={tags}
/> />
</Layout> </Layout>
) )
......
...@@ -2,7 +2,7 @@ import { Head, Link } from '@inertiajs/react' ...@@ -2,7 +2,7 @@ import { Head, Link } from '@inertiajs/react'
import Layout from '../../../Layout' import Layout from '../../../Layout'
import ComImageIndex from '../../../../components/images/ComImageIndex' import ComImageIndex from '../../../../components/images/ComImageIndex'
export default function TagsImagesIndex({ auth, tag, images, pagination, filters }) { export default function TagsImagesIndex({ auth, tag, images, pagination, filters, tags }) {
return ( return (
<Layout user={auth}> <Layout user={auth}>
<Head title={`${tag.name} - 标签图片`} /> <Head title={`${tag.name} - 标签图片`} />
...@@ -15,6 +15,7 @@ export default function TagsImagesIndex({ auth, tag, images, pagination, filters ...@@ -15,6 +15,7 @@ export default function TagsImagesIndex({ auth, tag, images, pagination, filters
pagination={pagination} pagination={pagination}
filters={filters} filters={filters}
showUserName={true} showUserName={true}
allTags={tags}
backLink={{ backLink={{
url: '/admin/tags', url: '/admin/tags',
label: '返回标签列表' label: '返回标签列表'
......
...@@ -2,7 +2,7 @@ import Layout from '../Layout' ...@@ -2,7 +2,7 @@ import Layout from '../Layout'
import ComImageIndex from '../../components/images/ComImageIndex' import ComImageIndex from '../../components/images/ComImageIndex'
import { Head } from '@inertiajs/react' import { Head } from '@inertiajs/react'
export default function Index({ auth, images, pagination, filters }) { export default function Index({ auth, images, pagination, filters, tags }) {
return ( return (
<Layout user={auth}> <Layout user={auth}>
<Head title="我的图片" /> <Head title="我的图片" />
...@@ -14,6 +14,7 @@ export default function Index({ auth, images, pagination, filters }) { ...@@ -14,6 +14,7 @@ export default function Index({ auth, images, pagination, filters }) {
images={images} images={images}
pagination={pagination} pagination={pagination}
filters={filters} filters={filters}
allTags={tags}
/> />
</Layout> </Layout>
) )
......
...@@ -2,7 +2,7 @@ import { Head, Link } from '@inertiajs/react' ...@@ -2,7 +2,7 @@ import { Head, Link } from '@inertiajs/react'
import Layout from '../../Layout' import Layout from '../../Layout'
import ComImageIndex from '../../../components/images/ComImageIndex' import ComImageIndex from '../../../components/images/ComImageIndex'
export default function Index({ tag, images, pagination, auth, filters = {} }) { export default function Index({ tag, images, pagination, auth, filters = {}, tags }) {
return ( return (
<Layout user={auth}> <Layout user={auth}>
<Head title={`标签: ${tag.name} - 图片列表`} /> <Head title={`标签: ${tag.name} - 图片列表`} />
...@@ -14,6 +14,7 @@ export default function Index({ tag, images, pagination, auth, filters = {} }) { ...@@ -14,6 +14,7 @@ export default function Index({ tag, images, pagination, auth, filters = {} }) {
images={images} images={images}
pagination={pagination} pagination={pagination}
filters={filters} filters={filters}
allTags={tags}
backLink={{ backLink={{
url: '/tags', url: '/tags',
label: '返回标签列表' label: '返回标签列表'
......
...@@ -16,6 +16,8 @@ class Image < ApplicationRecord ...@@ -16,6 +16,8 @@ class Image < ApplicationRecord
scope :approved, -> { where(status: :approved) } scope :approved, -> { where(status: :approved) }
scope :rejected, -> { where(status: :rejected) } scope :rejected, -> { where(status: :rejected) }
ransacker :status, formatter: ->(value) { statuses[value] }
def self.ransackable_attributes(auth_object = nil) def self.ransackable_attributes(auth_object = nil)
[ "created_at", "id", "id_value", "status", "title", "updated_at", "user_id" ] [ "created_at", "id", "id_value", "status", "title", "updated_at", "user_id" ]
end end
......
...@@ -24,7 +24,9 @@ ...@@ -24,7 +24,9 @@
"@tailwindcss/vite": "^4.0.12", "@tailwindcss/vite": "^4.0.12",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"dayjs": "^1.11.13",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"radix-ui": "^1.1.3",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"tailwindcss": "^4.0.12" "tailwindcss": "^4.0.12"
...@@ -850,6 +852,88 @@ ...@@ -850,6 +852,88 @@
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@radix-ui/react-accessible-icon": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.2.tgz",
"integrity": "sha512-+rnMO0SEfzkcHr93RshkQVpOA26MtGOv4pcS9QUnLg4F8+GDmCJ8c2FEPhPz5e7arf31EzbTqJxFbzg3qen14g==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-visually-hidden": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-accordion": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.3.tgz",
"integrity": "sha512-RIQ15mrcvqIkDARJeERSuXSry2N8uYnxkdDetpfmalT/+0ntOXLkFOsh9iwlAsCv+qcmhZjbdJogIm6WBa6c4A==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-collapsible": "1.1.3",
"@radix-ui/react-collection": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-alert-dialog": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.6.tgz",
"integrity": "sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-dialog": "1.1.6",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-slot": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-arrow": { "node_modules/@radix-ui/react-arrow": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.2.tgz",
...@@ -873,6 +957,29 @@ ...@@ -873,6 +957,29 @@
} }
} }
}, },
"node_modules/@radix-ui/react-aspect-ratio": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.2.tgz",
"integrity": "sha512-TaJxYoCpxJ7vfEkv2PTNox/6zzmpKXT6ewvCuf2tTOIVN45/Jahhlld29Yw4pciOXS2Xq91/rSGEdmEnUWZCqA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.0.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-avatar": { "node_modules/@radix-ui/react-avatar": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.3.tgz",
...@@ -1015,6 +1122,34 @@ ...@@ -1015,6 +1122,34 @@
} }
} }
}, },
"node_modules/@radix-ui/react-context-menu": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.6.tgz",
"integrity": "sha512-aUP99QZ3VU84NPsHeaFt4cQUNgJqFsLLOt/RbbWXszZ6MP0DpDyjkFZORr4RpAEx3sUBk+Kc8h13yGtC5Qw8dg==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-menu": "2.1.6",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-dialog": { "node_modules/@radix-ui/react-dialog": {
"version": "1.1.6", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.6.tgz",
...@@ -1190,6 +1325,37 @@ ...@@ -1190,6 +1325,37 @@
} }
} }
}, },
"node_modules/@radix-ui/react-hover-card": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.6.tgz",
"integrity": "sha512-E4ozl35jq0VRlrdc4dhHrNSV0JqBb4Jy73WAhBEK7JoYnQ83ED5r0Rb/XdVKw89ReAJN38N492BAPBZQ57VmqQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-dismissable-layer": "1.1.5",
"@radix-ui/react-popper": "1.2.2",
"@radix-ui/react-portal": "1.1.4",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-id": { "node_modules/@radix-ui/react-id": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
...@@ -1271,6 +1437,74 @@ ...@@ -1271,6 +1437,74 @@
} }
} }
}, },
"node_modules/@radix-ui/react-menubar": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.6.tgz",
"integrity": "sha512-FHq7+3DlXwh/7FOM4i0G4bC4vPjiq89VEEvNF4VMLchGnaUuUbE5uKXMUCjdKaOghEEMeiKa5XCa2Pk4kteWmg==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-collection": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-menu": "2.1.6",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-roving-focus": "1.1.2",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-navigation-menu": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.5.tgz",
"integrity": "sha512-myMHHQUZ3ZLTi8W381/Vu43Ia0NqakkQZ2vzynMmTUtQQ9kNkjzhOwkZC9TAM5R07OZUVIQyHC06f/9JZJpvvA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-collection": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-dismissable-layer": "1.1.5",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0",
"@radix-ui/react-use-previous": "1.1.0",
"@radix-ui/react-visually-hidden": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popover": { "node_modules/@radix-ui/react-popover": {
"version": "1.1.6", "version": "1.1.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.6.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.6.tgz",
...@@ -1411,6 +1645,62 @@ ...@@ -1411,6 +1645,62 @@
} }
} }
}, },
"node_modules/@radix-ui/react-progress": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.2.tgz",
"integrity": "sha512-u1IgJFQ4zNAUTjGdDL5dcl/U8ntOR6jsnhxKb5RKp5Ozwl88xKR9EqRZOe/Mk8tnx0x5tNUe2F+MzsyjqMg0MA==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-primitive": "2.0.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-radio-group": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.3.tgz",
"integrity": "sha512-xtCsqt8Rp09FK50ItqEqTJ7Sxanz8EM8dnkVIhJrc/wkMMomSmXHvYbhv3E7Zx4oXh98aaLt9W679SUYXg4IDA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-roving-focus": "1.1.2",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-previous": "1.1.0",
"@radix-ui/react-use-size": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-roving-focus": { "node_modules/@radix-ui/react-roving-focus": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.2.tgz",
...@@ -1442,6 +1732,37 @@ ...@@ -1442,6 +1732,37 @@
} }
} }
}, },
"node_modules/@radix-ui/react-scroll-area": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.3.tgz",
"integrity": "sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/number": "1.1.0",
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-select": { "node_modules/@radix-ui/react-select": {
"version": "2.1.6", "version": "2.1.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.6.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.6.tgz",
...@@ -1485,6 +1806,62 @@ ...@@ -1485,6 +1806,62 @@
} }
} }
}, },
"node_modules/@radix-ui/react-separator": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.2.tgz",
"integrity": "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-primitive": "2.0.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slider": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.2.3.tgz",
"integrity": "sha512-nNrLAWLjGESnhqBqcCNW4w2nn7LxudyMzeB6VgdyAnFLC6kfQgnAjSL2v6UkQTnDctJBlxrmxfplWS4iYjdUTw==",
"license": "MIT",
"dependencies": {
"@radix-ui/number": "1.1.0",
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-collection": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0",
"@radix-ui/react-use-previous": "1.1.0",
"@radix-ui/react-use-size": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": { "node_modules/@radix-ui/react-slot": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.2.tgz",
...@@ -1596,6 +1973,123 @@ ...@@ -1596,6 +1973,123 @@
} }
} }
}, },
"node_modules/@radix-ui/react-toggle": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.2.tgz",
"integrity": "sha512-lntKchNWx3aCHuWKiDY+8WudiegQvBpDRAYL8dKLRvKEH8VOpl0XX6SSU/bUBqIRJbcTy4+MW06Wv8vgp10rzQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toggle-group": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.2.tgz",
"integrity": "sha512-JBm6s6aVG/nwuY5eadhU2zDi/IwYS0sDM5ZWb4nymv/hn3hZdkw+gENn0LP4iY1yCd7+bgJaCwueMYJIU3vk4A==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-roving-focus": "1.1.2",
"@radix-ui/react-toggle": "1.1.2",
"@radix-ui/react-use-controllable-state": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-toolbar": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.2.tgz",
"integrity": "sha512-wT20eQ7ScFk+kBMDmHp+lMk18cgxhu35b2Bn5deUcPxiVwfn5vuZgi7NGcHu8ocdkinahmp4FaSZysKDyRVPWQ==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-roving-focus": "1.1.2",
"@radix-ui/react-separator": "1.1.2",
"@radix-ui/react-toggle-group": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-tooltip": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.8.tgz",
"integrity": "sha512-YAA2cu48EkJZdAMHC0dqo9kialOcRStbtiY4nJPaht7Ptrhcvpo+eDChaM6BIs8kL6a8Z5l5poiqLnXcNduOkA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-dismissable-layer": "1.1.5",
"@radix-ui/react-id": "1.1.0",
"@radix-ui/react-popper": "1.2.2",
"@radix-ui/react-portal": "1.1.4",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-slot": "1.1.2",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-visually-hidden": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": { "node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
...@@ -2510,6 +3004,12 @@ ...@@ -2510,6 +3004,12 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
"license": "MIT"
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
...@@ -3492,6 +3992,78 @@ ...@@ -3492,6 +3992,78 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/radix-ui": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.1.3.tgz",
"integrity": "sha512-W8L6soM1vQnIXVvVa31AkQhoZBDPwVoNHhT13R3aB9Qq7ARYIUS9DLaCopRBsbTdZm1NEEPx3rnq659CiNOBDw==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-accessible-icon": "1.1.2",
"@radix-ui/react-accordion": "1.2.3",
"@radix-ui/react-alert-dialog": "1.1.6",
"@radix-ui/react-aspect-ratio": "1.1.2",
"@radix-ui/react-avatar": "1.1.3",
"@radix-ui/react-checkbox": "1.1.4",
"@radix-ui/react-collapsible": "1.1.3",
"@radix-ui/react-collection": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-context-menu": "2.2.6",
"@radix-ui/react-dialog": "1.1.6",
"@radix-ui/react-direction": "1.1.0",
"@radix-ui/react-dismissable-layer": "1.1.5",
"@radix-ui/react-dropdown-menu": "2.1.6",
"@radix-ui/react-focus-guards": "1.1.1",
"@radix-ui/react-focus-scope": "1.1.2",
"@radix-ui/react-form": "0.1.2",
"@radix-ui/react-hover-card": "1.1.6",
"@radix-ui/react-label": "2.1.2",
"@radix-ui/react-menu": "2.1.6",
"@radix-ui/react-menubar": "1.1.6",
"@radix-ui/react-navigation-menu": "1.2.5",
"@radix-ui/react-popover": "1.1.6",
"@radix-ui/react-popper": "1.2.2",
"@radix-ui/react-portal": "1.1.4",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-progress": "1.1.2",
"@radix-ui/react-radio-group": "1.2.3",
"@radix-ui/react-roving-focus": "1.1.2",
"@radix-ui/react-scroll-area": "1.2.3",
"@radix-ui/react-select": "2.1.6",
"@radix-ui/react-separator": "1.1.2",
"@radix-ui/react-slider": "1.2.3",
"@radix-ui/react-slot": "1.1.2",
"@radix-ui/react-switch": "1.1.3",
"@radix-ui/react-tabs": "1.1.3",
"@radix-ui/react-toast": "1.2.6",
"@radix-ui/react-toggle": "1.1.2",
"@radix-ui/react-toggle-group": "1.1.2",
"@radix-ui/react-toolbar": "1.1.2",
"@radix-ui/react-tooltip": "1.1.8",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-escape-keydown": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0",
"@radix-ui/react-use-size": "1.1.0",
"@radix-ui/react-visually-hidden": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/react": { "node_modules/react": {
"version": "19.0.0", "version": "19.0.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
......
...@@ -25,7 +25,9 @@ ...@@ -25,7 +25,9 @@
"@tailwindcss/vite": "^4.0.12", "@tailwindcss/vite": "^4.0.12",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20", "autoprefixer": "^10.4.20",
"dayjs": "^1.11.13",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"radix-ui": "^1.1.3",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"tailwindcss": "^4.0.12" "tailwindcss": "^4.0.12"
......
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