feat : add codition for stats build

This commit is contained in:
2026-09-12 15:01:22 +07:00 Unverified
parent 6cc0945e2c
commit 54ae6a4b02
15 changed files with 2951 additions and 98 deletions
+86 -20
View File
@@ -248,6 +248,14 @@ type ArtifactProfile = {
goblet: string[];
circlet: string[];
substats: string[];
recommendedConditions: Array<{
name?: string | null;
note: string | null;
sands: string[];
goblet: string[];
circlet: string[];
substats: string[];
}>;
targets: Array<{ stat: string; value: string }>;
targetConditions: Array<{
name?: string | null;
@@ -745,7 +753,7 @@ function StatSelect({
: undefined}
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectContent className="w-max min-w-(--anchor-width) max-w-(--available-width)">
<SelectGroup>
{options.map((option) => (
<SelectItem
@@ -1756,10 +1764,18 @@ function ArtifactEditor({
})) ?? [],
})),
);
const [sands, setSands] = useState(profile?.sands ?? []);
const [goblet, setGoblet] = useState(profile?.goblet ?? []);
const [circlet, setCirclet] = useState(profile?.circlet ?? []);
const [substats, setSubstats] = useState(profile?.substats ?? []);
const [recommendedGroups, setRecommendedGroups] = useState(() => {
const source = profile?.recommendedConditions.length
? profile.recommendedConditions
: [{ name: null, note: null, sands: profile?.sands ?? [], goblet: profile?.goblet ?? [], circlet: profile?.circlet ?? [], substats: profile?.substats ?? [] }];
return source.map((group, index) => ({ ...group, clientId: `recommended-condition-${index}`, name: group.name ?? "", note: group.note ?? "" }));
});
const [activeRecommendedGroupIndex, setActiveRecommendedGroupIndex] = useState(0);
const [renamingRecommendedGroupIndex, setRenamingRecommendedGroupIndex] = useState<number | null>(null);
const recommended = recommendedGroups[activeRecommendedGroupIndex];
const { sands, goblet, circlet, substats } = recommended;
const setRecommendedStat = (slot: "sands" | "goblet" | "circlet" | "substats", values: string[]) =>
setRecommendedGroups((current) => current.map((group, index) => index === activeRecommendedGroupIndex ? { ...group, [slot]: values } : group));
const [targetGroups, setTargetGroups] = useState(() => {
const source = profile?.targetConditions.length
? profile.targetConditions
@@ -1812,10 +1828,11 @@ function ArtifactEditor({
note: row.note?.trim() || null,
})),
})),
sands,
goblet,
circlet,
substats,
sands: recommendedGroups[0].sands,
goblet: recommendedGroups[0].goblet,
circlet: recommendedGroups[0].circlet,
substats: recommendedGroups[0].substats,
recommendedGroups: recommendedGroups.map(({ name, note, sands, goblet, circlet, substats }) => ({ name: name.trim() || null, note: note.trim() || null, sands, goblet, circlet, substats })),
targetGroups: targetGroups.map((group) => ({
name: group.name.trim() || null,
note: group.note.trim() || null,
@@ -1836,6 +1853,8 @@ function ArtifactEditor({
goblet,
circlet,
substats,
recommendedGroups,
activeRecommendedGroupIndex,
targetGroups,
activeTargetGroupIndex,
talents,
@@ -1865,10 +1884,8 @@ function ArtifactEditor({
),
restoreLocalDraft: (draft) => {
setGroups(draft.groups);
setSands(draft.sands);
setGoblet(draft.goblet);
setCirclet(draft.circlet);
setSubstats(draft.substats);
setRecommendedGroups(draft.recommendedGroups?.length ? draft.recommendedGroups : [{ clientId: createClientId("recommended-condition"), name: "", note: "", sands: draft.sands, goblet: draft.goblet, circlet: draft.circlet, substats: draft.substats }]);
setActiveRecommendedGroupIndex(Math.min(draft.activeRecommendedGroupIndex ?? 0, Math.max(0, (draft.recommendedGroups?.length ?? 1) - 1)));
setTargetGroups(draft.targetGroups.map((group) => ({ ...group, name: group.name ?? "" })));
setActiveTargetGroupIndex(draft.activeTargetGroupIndex ?? 0);
setTalents(draft.talents);
@@ -2177,36 +2194,79 @@ function ArtifactEditor({
))}
</CardContent>
</Card>
<Card className="order-3">
<CardHeader className="bg-primary text-center text-primary-foreground">
<CardTitle>Recommended Stats</CardTitle>
<Card className="order-3 overflow-hidden pt-0">
<CardHeader className="bg-primary py-4 text-center text-primary-foreground">
<CardTitle className="text-2xl">Recommended Stats</CardTitle>
</CardHeader>
<CardContent>
<FieldGroup>
<div className="flex flex-wrap gap-2">
<SortableConditionSwitcher
ariaLabel="เลือกเงื่อนไขค่าสถานะแนะนำ"
items={recommendedGroups}
activeIndex={activeRecommendedGroupIndex}
onActiveIndexChange={setActiveRecommendedGroupIndex}
onRename={setRenamingRecommendedGroupIndex}
onReorder={(from, to) => {
const reordered = reorderConditions(recommendedGroups, activeRecommendedGroupIndex, from, to);
setRecommendedGroups(reordered.items);
setActiveRecommendedGroupIndex(reordered.activeIndex);
}}
/>
<ConditionRenameDialog
open={renamingRecommendedGroupIndex !== null}
fallback={`Condition ${(renamingRecommendedGroupIndex ?? 0) + 1}`}
value={renamingRecommendedGroupIndex === null ? "" : recommendedGroups[renamingRecommendedGroupIndex]?.name ?? ""}
onOpenChange={(open) => { if (!open) setRenamingRecommendedGroupIndex(null); }}
onChange={(name) => setRecommendedGroups(recommendedGroups.map((group, index) => index === renamingRecommendedGroupIndex ? { ...group, name } : group))}
/>
<SimpleTooltip text="เพิ่ม Condition">
<Button type="button" size="icon" variant="outline" aria-label="เพิ่ม Condition" disabled={recommendedGroups.length >= 20} onClick={() => {
setRecommendedGroups([...recommendedGroups, { clientId: createClientId("recommended-condition"), name: "", note: "", sands: [], goblet: [], circlet: [], substats: [] }]);
setActiveRecommendedGroupIndex(recommendedGroups.length);
}}><PlusIcon /></Button>
</SimpleTooltip>
<SimpleTooltip text="Duplicate Condition">
<Button type="button" size="icon" variant="outline" aria-label="Duplicate Condition" disabled={recommendedGroups.length >= 20} onClick={() => {
const source = recommendedGroups[activeRecommendedGroupIndex];
if (!source) return;
setRecommendedGroups([...recommendedGroups, { clientId: createClientId("recommended-condition"), name: source.name, note: source.note, sands: [...source.sands], goblet: [...source.goblet], circlet: [...source.circlet], substats: [...source.substats] }]);
setActiveRecommendedGroupIndex(recommendedGroups.length);
}}><CopyIcon /></Button>
</SimpleTooltip>
</div>
<Field>
<FieldLabel htmlFor={`recommended-condition-note-${recommendedGroups[activeRecommendedGroupIndex]?.clientId}`}>Condition note (optional)</FieldLabel>
<Textarea id={`recommended-condition-note-${recommendedGroups[activeRecommendedGroupIndex]?.clientId}`} value={recommendedGroups[activeRecommendedGroupIndex]?.note ?? ""} onChange={(event) => setRecommendedGroups(recommendedGroups.map((group, index) => index === activeRecommendedGroupIndex ? { ...group, note: event.target.value } : group))} />
</Field>
<div className="grid overflow-hidden rounded-xl border sm:grid-cols-3">
<StatSelect
label="Sands / นาฬิกา"
options={mainStatOptions.sands}
value={sands}
max={3}
onChange={setSands}
onChange={(values) => setRecommendedStat("sands", values)}
elementIconUrls={elementIconUrls}
priorityColors
/>
<StatSelect
label="Goblet / ถ้วย"
options={mainStatOptions.goblet}
value={goblet}
max={3}
onChange={setGoblet}
onChange={(values) => setRecommendedStat("goblet", values)}
elementIconUrls={elementIconUrls}
priorityColors
/>
<StatSelect
label="Circlet / หมวก"
options={mainStatOptions.circlet}
value={circlet}
max={3}
onChange={setCirclet}
onChange={(values) => setRecommendedStat("circlet", values)}
elementIconUrls={elementIconUrls}
priorityColors
/>
</div>
<StatSelect
@@ -2214,10 +2274,16 @@ function ArtifactEditor({
options={substatOptions}
value={substats}
max={substatOptions.length}
onChange={setSubstats}
onChange={(values) => setRecommendedStat("substats", values)}
elementIconUrls={elementIconUrls}
priorityColors
/>
{recommendedGroups.length > 1 ? (
<Button type="button" variant="ghost" onClick={() => {
setRecommendedGroups(recommendedGroups.filter((_, index) => index !== activeRecommendedGroupIndex));
setActiveRecommendedGroupIndex(Math.max(0, activeRecommendedGroupIndex - 1));
}}><Trash2Icon data-icon="inline-start" />ลบ Condition นี้</Button>
) : null}
</FieldGroup>
</CardContent>
</Card>
+23 -17
View File
@@ -50,6 +50,7 @@ interface LoginCardProps {
export function LoginCard({ turnstileSiteKey }: LoginCardProps) {
const router = useRouter();
const turnstileEnabled = process.env.NODE_ENV !== "development";
const turnstileContainerRef = useRef<HTMLDivElement>(null);
const turnstileWidgetIdRef = useRef<string | null>(null);
const [loading, setLoading] = useState(false);
@@ -58,6 +59,7 @@ export function LoginCard({ turnstileSiteKey }: LoginCardProps) {
const renderTurnstile = useCallback(() => {
if (
!turnstileEnabled ||
!window.turnstile ||
!turnstileContainerRef.current ||
turnstileWidgetIdRef.current
@@ -80,7 +82,7 @@ export function LoginCard({ turnstileSiteKey }: LoginCardProps) {
},
},
);
}, [turnstileSiteKey]);
}, [turnstileEnabled, turnstileSiteKey]);
function resetTurnstile() {
setTurnstileToken(null);
@@ -91,7 +93,7 @@ export function LoginCard({ turnstileSiteKey }: LoginCardProps) {
async function signIn(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!turnstileToken) return;
if (turnstileEnabled && !turnstileToken) return;
setLoading(true);
setError(null);
@@ -105,7 +107,9 @@ export function LoginCard({ turnstileSiteKey }: LoginCardProps) {
password,
rememberMe: true,
fetchOptions: {
headers: { "x-captcha-response": turnstileToken },
headers: turnstileEnabled && turnstileToken
? { "x-captcha-response": turnstileToken }
: undefined,
},
});
if (result.error) {
@@ -161,19 +165,21 @@ export function LoginCard({ turnstileSiteKey }: LoginCardProps) {
required
/>
</Field>
<Field>
<div ref={turnstileContainerRef} className="min-h-[65px]" />
<Script
src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
strategy="afterInteractive"
onLoad={renderTurnstile}
onReady={renderTurnstile}
onError={() => {
setTurnstileToken(null);
setError("โหลดระบบยืนยันตัวตนไม่สำเร็จ โปรดลองอีกครั้ง");
}}
/>
</Field>
{turnstileEnabled ? (
<Field>
<div ref={turnstileContainerRef} className="min-h-[65px]" />
<Script
src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"
strategy="afterInteractive"
onLoad={renderTurnstile}
onReady={renderTurnstile}
onError={() => {
setTurnstileToken(null);
setError("โหลดระบบยืนยันตัวตนไม่สำเร็จ โปรดลองอีกครั้ง");
}}
/>
</Field>
) : null}
{error ? (
<Alert variant="destructive">
<CircleAlertIcon aria-hidden="true" />
@@ -185,7 +191,7 @@ export function LoginCard({ turnstileSiteKey }: LoginCardProps) {
className="w-full"
size="lg"
type="submit"
disabled={loading || !turnstileToken}
disabled={loading || (turnstileEnabled && !turnstileToken)}
>
{loading ? (
<Spinner data-icon="inline-start" />