refactor: streamline date and title handling in NewWeatherPage and NewWeeklyCheckInPage components for improved user experience
Some checks failed
Deploy with Docker Compose / deploy (push) Has been cancelled

This commit is contained in:
Julien Froidefond
2026-02-04 11:02:52 +01:00
parent ef0772f894
commit e8ffccd286
4 changed files with 68 additions and 57 deletions

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useState, useEffect } from 'react'; import { useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { import {
Card, Card,
@@ -19,16 +19,15 @@ export default function NewWeatherPage() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]); const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
const [title, setTitle] = useState(''); const [title, setTitle] = useState(() => getWeekYearLabel(new Date(new Date().toISOString().split('T')[0])));
const [isTitleManuallyEdited, setIsTitleManuallyEdited] = useState(false);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault(); e.preventDefault();
setError(null); setError(null);
setLoading(true); setLoading(true);
const formData = new FormData(e.currentTarget); const date = selectedDate ? new Date(selectedDate) : undefined;
const dateStr = formData.get('date') as string;
const date = dateStr ? new Date(dateStr) : undefined;
if (!title) { if (!title) {
setError('Veuillez remplir le titre'); setError('Veuillez remplir le titre');
@@ -47,13 +46,19 @@ export default function NewWeatherPage() {
router.push(`/weather/${result.data?.id}`); router.push(`/weather/${result.data?.id}`);
} }
// Default date to today function handleDateChange(e: React.ChangeEvent<HTMLInputElement>) {
const today = new Date().toISOString().split('T')[0]; const newDate = e.target.value;
setSelectedDate(newDate);
// Only update title if user hasn't manually modified it
if (!isTitleManuallyEdited) {
setTitle(getWeekYearLabel(new Date(newDate)));
}
}
// Update title when date changes function handleTitleChange(e: React.ChangeEvent<HTMLInputElement>) {
useEffect(() => { setTitle(e.target.value);
setTitle(getWeekYearLabel(new Date(selectedDate))); setIsTitleManuallyEdited(true);
}, [selectedDate]); }
return ( return (
<main className="mx-auto max-w-2xl px-4 py-8"> <main className="mx-auto max-w-2xl px-4 py-8">
@@ -81,7 +86,7 @@ export default function NewWeatherPage() {
name="title" name="title"
placeholder="Ex: Météo S05 - 2026" placeholder="Ex: Météo S05 - 2026"
value={title} value={title}
onChange={(e) => setTitle(e.target.value)} onChange={handleTitleChange}
required required
/> />
@@ -93,8 +98,8 @@ export default function NewWeatherPage() {
id="date" id="date"
name="date" name="date"
type="date" type="date"
defaultValue={today} value={selectedDate}
onChange={(e) => setSelectedDate(e.target.value)} onChange={handleDateChange}
required required
className="w-full rounded-lg border border-border bg-input px-3 py-2 text-foreground outline-none focus:border-primary focus:ring-2 focus:ring-primary/20" className="w-full rounded-lg border border-border bg-input px-3 py-2 text-foreground outline-none focus:border-primary focus:ring-2 focus:ring-primary/20"
/> />

View File

@@ -1,6 +1,6 @@
'use client'; 'use client';
import { useState, useEffect } from 'react'; import { useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { import {
Card, Card,
@@ -19,7 +19,8 @@ export default function NewWeeklyCheckInPage() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]); const [selectedDate, setSelectedDate] = useState(new Date().toISOString().split('T')[0]);
const [title, setTitle] = useState(''); const [title, setTitle] = useState(() => getWeekYearLabel(new Date(new Date().toISOString().split('T')[0])));
const [isTitleManuallyEdited, setIsTitleManuallyEdited] = useState(false);
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault(); e.preventDefault();
@@ -28,8 +29,7 @@ export default function NewWeeklyCheckInPage() {
const formData = new FormData(e.currentTarget); const formData = new FormData(e.currentTarget);
const participant = formData.get('participant') as string; const participant = formData.get('participant') as string;
const dateStr = formData.get('date') as string; const date = selectedDate ? new Date(selectedDate) : undefined;
const date = dateStr ? new Date(dateStr) : undefined;
if (!title || !participant) { if (!title || !participant) {
setError('Veuillez remplir tous les champs'); setError('Veuillez remplir tous les champs');
@@ -48,13 +48,19 @@ export default function NewWeeklyCheckInPage() {
router.push(`/weekly-checkin/${result.data?.id}`); router.push(`/weekly-checkin/${result.data?.id}`);
} }
// Default date to today function handleDateChange(e: React.ChangeEvent<HTMLInputElement>) {
const today = new Date().toISOString().split('T')[0]; const newDate = e.target.value;
setSelectedDate(newDate);
// Only update title if user hasn't manually modified it
if (!isTitleManuallyEdited) {
setTitle(getWeekYearLabel(new Date(newDate)));
}
}
// Update title when date changes function handleTitleChange(e: React.ChangeEvent<HTMLInputElement>) {
useEffect(() => { setTitle(e.target.value);
setTitle(getWeekYearLabel(new Date(selectedDate))); setIsTitleManuallyEdited(true);
}, [selectedDate]); }
return ( return (
<main className="mx-auto max-w-2xl px-4 py-8"> <main className="mx-auto max-w-2xl px-4 py-8">
@@ -83,7 +89,7 @@ export default function NewWeeklyCheckInPage() {
name="title" name="title"
placeholder="Ex: Check-in semaine du 15 janvier" placeholder="Ex: Check-in semaine du 15 janvier"
value={title} value={title}
onChange={(e) => setTitle(e.target.value)} onChange={handleTitleChange}
required required
/> />
@@ -102,8 +108,8 @@ export default function NewWeeklyCheckInPage() {
id="date" id="date"
name="date" name="date"
type="date" type="date"
defaultValue={today} value={selectedDate}
onChange={(e) => setSelectedDate(e.target.value)} onChange={handleDateChange}
required required
className="w-full rounded-lg border border-border bg-input px-3 py-2 text-foreground outline-none focus:border-primary focus:ring-2 focus:ring-primary/20" className="w-full rounded-lg border border-border bg-input px-3 py-2 text-foreground outline-none focus:border-primary focus:ring-2 focus:ring-primary/20"
/> />

View File

@@ -128,23 +128,23 @@ export function WeatherCard({ sessionId, currentUserId, entry, canEdit }: Weathe
</td> </td>
{/* Performance */} {/* Performance */}
<td className="px-4 py-3"> <td className="w-24 px-2 py-3">
{canEditThis ? ( {canEditThis ? (
<div className="relative"> <div className="relative mx-auto w-fit">
<select <select
value={performanceEmoji || ''} value={performanceEmoji || ''}
onChange={(e) => handleEmojiChange('performance', e.target.value || null)} onChange={(e) => handleEmojiChange('performance', e.target.value || null)}
className="w-full appearance-none rounded-lg border border-border bg-card px-3 py-2.5 pr-10 text-lg text-foreground transition-colors hover:bg-card-hover focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20" className="w-16 appearance-none rounded-lg border border-border bg-card px-2 py-2.5 pr-8 text-center text-lg text-foreground transition-colors hover:bg-card-hover focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
> >
{WEATHER_EMOJIS.map(({ emoji, label }) => ( {WEATHER_EMOJIS.map(({ emoji }) => (
<option key={emoji || 'none'} value={emoji}> <option key={emoji || 'none'} value={emoji}>
{emoji} {label} {emoji}
</option> </option>
))} ))}
</select> </select>
<div className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2"> <div className="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2">
<svg <svg
className="h-4 w-4 text-muted" className="h-3 w-3 text-muted"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -159,23 +159,23 @@ export function WeatherCard({ sessionId, currentUserId, entry, canEdit }: Weathe
</td> </td>
{/* Moral */} {/* Moral */}
<td className="px-4 py-3"> <td className="w-24 px-2 py-3">
{canEditThis ? ( {canEditThis ? (
<div className="relative"> <div className="relative mx-auto w-fit">
<select <select
value={moralEmoji || ''} value={moralEmoji || ''}
onChange={(e) => handleEmojiChange('moral', e.target.value || null)} onChange={(e) => handleEmojiChange('moral', e.target.value || null)}
className="w-full appearance-none rounded-lg border border-border bg-card px-3 py-2.5 pr-10 text-lg text-foreground transition-colors hover:bg-card-hover focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20" className="w-16 appearance-none rounded-lg border border-border bg-card px-2 py-2.5 pr-8 text-center text-lg text-foreground transition-colors hover:bg-card-hover focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
> >
{WEATHER_EMOJIS.map(({ emoji, label }) => ( {WEATHER_EMOJIS.map(({ emoji }) => (
<option key={emoji || 'none'} value={emoji}> <option key={emoji || 'none'} value={emoji}>
{emoji} {label} {emoji}
</option> </option>
))} ))}
</select> </select>
<div className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2"> <div className="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2">
<svg <svg
className="h-4 w-4 text-muted" className="h-3 w-3 text-muted"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -190,23 +190,23 @@ export function WeatherCard({ sessionId, currentUserId, entry, canEdit }: Weathe
</td> </td>
{/* Flux */} {/* Flux */}
<td className="px-4 py-3"> <td className="w-24 px-2 py-3">
{canEditThis ? ( {canEditThis ? (
<div className="relative"> <div className="relative mx-auto w-fit">
<select <select
value={fluxEmoji || ''} value={fluxEmoji || ''}
onChange={(e) => handleEmojiChange('flux', e.target.value || null)} onChange={(e) => handleEmojiChange('flux', e.target.value || null)}
className="w-full appearance-none rounded-lg border border-border bg-card px-3 py-2.5 pr-10 text-lg text-foreground transition-colors hover:bg-card-hover focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20" className="w-16 appearance-none rounded-lg border border-border bg-card px-2 py-2.5 pr-8 text-center text-lg text-foreground transition-colors hover:bg-card-hover focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
> >
{WEATHER_EMOJIS.map(({ emoji, label }) => ( {WEATHER_EMOJIS.map(({ emoji }) => (
<option key={emoji || 'none'} value={emoji}> <option key={emoji || 'none'} value={emoji}>
{emoji} {label} {emoji}
</option> </option>
))} ))}
</select> </select>
<div className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2"> <div className="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2">
<svg <svg
className="h-4 w-4 text-muted" className="h-3 w-3 text-muted"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -221,23 +221,23 @@ export function WeatherCard({ sessionId, currentUserId, entry, canEdit }: Weathe
</td> </td>
{/* Création de valeur */} {/* Création de valeur */}
<td className="px-4 py-3"> <td className="w-24 px-2 py-3">
{canEditThis ? ( {canEditThis ? (
<div className="relative"> <div className="relative mx-auto w-fit">
<select <select
value={valueCreationEmoji || ''} value={valueCreationEmoji || ''}
onChange={(e) => handleEmojiChange('valueCreation', e.target.value || null)} onChange={(e) => handleEmojiChange('valueCreation', e.target.value || null)}
className="w-full appearance-none rounded-lg border border-border bg-card px-3 py-2.5 pr-10 text-lg text-foreground transition-colors hover:bg-card-hover focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20" className="w-16 appearance-none rounded-lg border border-border bg-card px-2 py-2.5 pr-8 text-center text-lg text-foreground transition-colors hover:bg-card-hover focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20"
> >
{WEATHER_EMOJIS.map(({ emoji, label }) => ( {WEATHER_EMOJIS.map(({ emoji }) => (
<option key={emoji || 'none'} value={emoji}> <option key={emoji || 'none'} value={emoji}>
{emoji} {label} {emoji}
</option> </option>
))} ))}
</select> </select>
<div className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2"> <div className="pointer-events-none absolute right-2 top-1/2 -translate-y-1/2">
<svg <svg
className="h-4 w-4 text-muted" className="h-3 w-3 text-muted"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -252,7 +252,7 @@ export function WeatherCard({ sessionId, currentUserId, entry, canEdit }: Weathe
</td> </td>
{/* Notes */} {/* Notes */}
<td className="px-4 py-3 min-w-[300px]"> <td className="px-4 py-3 min-w-[400px]">
{canEditThis ? ( {canEditThis ? (
<Textarea <Textarea
value={notes} value={notes}

View File

@@ -233,7 +233,7 @@ export function WeatherShareModal({
disabled={isPending || (shareType === 'user' && !email) || (shareType === 'team' && !teamId)} disabled={isPending || (shareType === 'user' && !email) || (shareType === 'team' && !teamId)}
className="w-full" className="w-full"
> >
{isPending ? 'Partage...' : shareType === 'team' ? 'Partager à l&apos;équipe' : 'Partager'} {isPending ? 'Partage...' : shareType === 'team' ? "Partager à l'équipe" : 'Partager'}
</Button> </Button>
</form> </form>
)} )}