import { createSelector, createSlice } from '@reduxjs/toolkit'; import type { RootState } from './index'; export type ChallengeDefinition = { id: string; title: string; startDate: string; // YYYY-MM-DD endDate: string; // YYYY-MM-DD participantsCount: number; participantsUnit: string; image: string; avatars: string[]; }; export type ChallengeViewModel = ChallengeDefinition & { dateRange: string; participantsLabel: string; }; type ChallengesState = { entities: Record; order: string[]; }; const initialChallenges: ChallengeDefinition[] = [ { id: 'joyful-dog-run', title: '遛狗跑步,欢乐一路', startDate: '2024-09-01', endDate: '2024-09-30', participantsCount: 6364, participantsUnit: '跑者', image: 'https://images.unsplash.com/photo-1525253086316-d0c936c814f8?auto=format&fit=crop&w=1200&q=80', avatars: [ 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1544723795-3fb6469f5b39?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1544723795-3fbce826f51f?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1502823403499-6ccfcf4fb453?auto=format&fit=crop&w=200&q=80', ], }, { id: 'penguin-swim', title: '企鹅宝宝的游泳预备班', startDate: '2024-09-01', endDate: '2024-09-30', participantsCount: 3334, participantsUnit: '游泳者', image: 'https://images.unsplash.com/photo-1531297484001-80022131f5a1?auto=format&fit=crop&w=1200&q=80', avatars: [ 'https://images.unsplash.com/photo-1525134479668-1bee5c7c6845?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1530268729831-4b0b9e170218?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1520813792240-56fc4a3765a7?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1463453091185-61582044d556?auto=format&fit=crop&w=200&q=80', ], }, { id: 'hydration-hippo', title: '学河马饮,做补水人', startDate: '2024-09-01', endDate: '2024-09-30', participantsCount: 9009, participantsUnit: '饮水者', image: 'https://images.unsplash.com/photo-1481931098730-318b6f776db0?auto=format&fit=crop&w=1200&q=80', avatars: [ 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1544723660-4bfa6584218e?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1544723795-3fbfb7c6a9f1?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1544723795-432537f48b2b?auto=format&fit=crop&w=200&q=80', ], }, { id: 'autumn-cycling', title: '炎夏渐散,踏板骑秋', startDate: '2024-09-01', endDate: '2024-09-30', participantsCount: 4617, participantsUnit: '骑行者', image: 'https://images.unsplash.com/photo-1509395176047-4a66953fd231?auto=format&fit=crop&w=1200&q=80', avatars: [ 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1521572267360-ee0c2909d518?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=200&q=80', ], }, { id: 'falcon-core', title: '燃卡加练甄秋腰', startDate: '2024-09-01', endDate: '2024-09-30', participantsCount: 11995, participantsUnit: '健身爱好者', image: 'https://images.unsplash.com/photo-1494871262121-6adf66e90adf?auto=format&fit=crop&w=1200&q=80', avatars: [ 'https://images.unsplash.com/photo-1524504388940-b1c1722653e1?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1520813792240-56fc4a3765a7?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1502685104226-ee32379fefbe?auto=format&fit=crop&w=200&q=80', 'https://images.unsplash.com/photo-1521572267360-ee0c2909d518?auto=format&fit=crop&w=200&q=80', ], }, ]; const initialState: ChallengesState = { entities: initialChallenges.reduce>((acc, challenge) => { acc[challenge.id] = challenge; return acc; }, {}), order: initialChallenges.map((challenge) => challenge.id), }; const challengesSlice = createSlice({ name: 'challenges', initialState, reducers: {}, }); export default challengesSlice.reducer; const selectChallengesState = (state: RootState) => state.challenges; export const selectChallengeEntities = createSelector([selectChallengesState], (state) => state.entities); export const selectChallengeOrder = createSelector([selectChallengesState], (state) => state.order); const formatNumberWithSeparator = (value: number): string => value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); const formatDateLabel = (value: string): string => { const [year, month, day] = value.split('-'); if (!month || !day) { return value; } const monthNumber = parseInt(month, 10); const dayNumber = parseInt(day, 10); const paddedDay = Number.isNaN(dayNumber) ? day : dayNumber.toString().padStart(2, '0'); if (Number.isNaN(monthNumber)) { return value; } return `${monthNumber}月${paddedDay}日`; }; const toViewModel = (challenge: ChallengeDefinition): ChallengeViewModel => ({ ...challenge, dateRange: `${formatDateLabel(challenge.startDate)} - ${formatDateLabel(challenge.endDate)}`, participantsLabel: `${formatNumberWithSeparator(challenge.participantsCount)} ${challenge.participantsUnit}`, }); export const selectChallengeList = createSelector( [selectChallengeEntities, selectChallengeOrder], (entities, order) => order.map((id) => entities[id]).filter(Boolean) as ChallengeDefinition[], ); export const selectChallengeCards = createSelector([selectChallengeList], (challenges) => challenges.map((challenge) => toViewModel(challenge)) ); export const selectChallengeById = (id: string) => createSelector([selectChallengeEntities], (entities) => entities[id]); export const selectChallengeViewById = (id: string) => createSelector([selectChallengeEntities], (entities) => { const challenge = entities[id]; return challenge ? toViewModel(challenge) : undefined; });