Initial project setup with Next.js accounts manager
- Set up Next.js project structure - Added UI components and styling - Configured package dependencies - Added feature documentation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
4
components/shared/index.ts
Normal file
4
components/shared/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { PlatformSelector } from './platform-selector';
|
||||
export { OwnerSelector } from './owner-selector';
|
||||
export { StatusSelector } from './status-selector';
|
||||
export { StatsSelect } from './stats-select';
|
||||
45
components/shared/owner-selector.tsx
Normal file
45
components/shared/owner-selector.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import { StatsSelect } from './stats-select';
|
||||
import { StatsOverview } from '@/lib/types';
|
||||
import { useStatsData } from '@/lib/hooks/use-stats-data';
|
||||
|
||||
interface OwnerSelectorProps {
|
||||
value: string;
|
||||
onValueChange: (value: string) => void;
|
||||
stats: StatsOverview | null;
|
||||
showNoneOption?: boolean;
|
||||
noneOptionLabel?: string;
|
||||
placeholder?: string;
|
||||
inputPlaceholder?: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function OwnerSelector({
|
||||
value,
|
||||
onValueChange,
|
||||
stats,
|
||||
showNoneOption = false,
|
||||
noneOptionLabel = "不修改",
|
||||
placeholder = "选择所有者",
|
||||
inputPlaceholder = "或直接输入所有者ID",
|
||||
className,
|
||||
disabled = false
|
||||
}: OwnerSelectorProps) {
|
||||
const { owners } = useStatsData(stats);
|
||||
|
||||
return (
|
||||
<StatsSelect
|
||||
value={value}
|
||||
onValueChange={onValueChange}
|
||||
placeholder={placeholder}
|
||||
inputPlaceholder={inputPlaceholder}
|
||||
items={owners}
|
||||
showNoneOption={showNoneOption}
|
||||
noneOptionLabel={noneOptionLabel}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
45
components/shared/platform-selector.tsx
Normal file
45
components/shared/platform-selector.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import { StatsSelect } from './stats-select';
|
||||
import { StatsOverview } from '@/lib/types';
|
||||
import { useStatsData } from '@/lib/hooks/use-stats-data';
|
||||
|
||||
interface PlatformSelectorProps {
|
||||
value: string;
|
||||
onValueChange: (value: string) => void;
|
||||
stats: StatsOverview | null;
|
||||
showNoneOption?: boolean;
|
||||
noneOptionLabel?: string;
|
||||
placeholder?: string;
|
||||
inputPlaceholder?: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function PlatformSelector({
|
||||
value,
|
||||
onValueChange,
|
||||
stats,
|
||||
showNoneOption = false,
|
||||
noneOptionLabel = "不修改",
|
||||
placeholder = "选择平台",
|
||||
inputPlaceholder = "或直接输入平台名称",
|
||||
className,
|
||||
disabled = false
|
||||
}: PlatformSelectorProps) {
|
||||
const { platforms } = useStatsData(stats);
|
||||
|
||||
return (
|
||||
<StatsSelect
|
||||
value={value}
|
||||
onValueChange={onValueChange}
|
||||
placeholder={placeholder}
|
||||
inputPlaceholder={inputPlaceholder}
|
||||
items={platforms}
|
||||
showNoneOption={showNoneOption}
|
||||
noneOptionLabel={noneOptionLabel}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
80
components/shared/stats-select.tsx
Normal file
80
components/shared/stats-select.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
"use client";
|
||||
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { StatsDataItem } from '@/lib/hooks/use-stats-data';
|
||||
|
||||
interface StatsSelectProps {
|
||||
value: string;
|
||||
onValueChange: (value: string) => void;
|
||||
placeholder: string;
|
||||
inputPlaceholder: string;
|
||||
items: StatsDataItem[];
|
||||
showNoneOption?: boolean;
|
||||
noneOptionLabel?: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function StatsSelect({
|
||||
value,
|
||||
onValueChange,
|
||||
placeholder,
|
||||
inputPlaceholder,
|
||||
items,
|
||||
showNoneOption = false,
|
||||
noneOptionLabel = "不修改",
|
||||
className,
|
||||
disabled = false
|
||||
}: StatsSelectProps) {
|
||||
const handleSelectChange = (selectedValue: string) => {
|
||||
if (selectedValue === 'none') {
|
||||
onValueChange('');
|
||||
} else {
|
||||
onValueChange(selectedValue);
|
||||
}
|
||||
};
|
||||
|
||||
// 检查当前值是否在选项列表中
|
||||
const isValueInOptions = value && (
|
||||
showNoneOption && value === '' ||
|
||||
items.some(item => item.value === value)
|
||||
);
|
||||
|
||||
// 为Select组件确定显示值
|
||||
const selectValue = () => {
|
||||
if (!value && showNoneOption) return 'none';
|
||||
if (isValueInOptions) return value;
|
||||
return undefined; // 如果值不在选项中,不设置Select的值
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`space-y-2 ${className || ''}`}>
|
||||
<Select
|
||||
value={selectValue()}
|
||||
onValueChange={handleSelectChange}
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder={placeholder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{showNoneOption && (
|
||||
<SelectItem value="none">{noneOptionLabel}</SelectItem>
|
||||
)}
|
||||
{items.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.label} ({item.count} 个账户)
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
placeholder={inputPlaceholder}
|
||||
value={value}
|
||||
onChange={(e) => onValueChange(e.target.value)}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
45
components/shared/status-selector.tsx
Normal file
45
components/shared/status-selector.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import { StatsSelect } from './stats-select';
|
||||
import { StatsOverview } from '@/lib/types';
|
||||
import { useStatsData } from '@/lib/hooks/use-stats-data';
|
||||
|
||||
interface StatusSelectorProps {
|
||||
value: string;
|
||||
onValueChange: (value: string) => void;
|
||||
stats: StatsOverview | null;
|
||||
showNoneOption?: boolean;
|
||||
noneOptionLabel?: string;
|
||||
placeholder?: string;
|
||||
inputPlaceholder?: string;
|
||||
className?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function StatusSelector({
|
||||
value,
|
||||
onValueChange,
|
||||
stats,
|
||||
showNoneOption = false,
|
||||
noneOptionLabel = "不修改",
|
||||
placeholder = "选择状态",
|
||||
inputPlaceholder = "或直接输入状态",
|
||||
className,
|
||||
disabled = false
|
||||
}: StatusSelectorProps) {
|
||||
const { statuses } = useStatsData(stats);
|
||||
|
||||
return (
|
||||
<StatsSelect
|
||||
value={value}
|
||||
onValueChange={onValueChange}
|
||||
placeholder={placeholder}
|
||||
inputPlaceholder={inputPlaceholder}
|
||||
items={statuses}
|
||||
showNoneOption={showNoneOption}
|
||||
noneOptionLabel={noneOptionLabel}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
/>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user