Compare commits
2 Commits
1139cfd5f0
...
fa981f0419
| Author | SHA1 | Date | |
|---|---|---|---|
| fa981f0419 | |||
| 174e3e94ed |
23
package-lock.json
generated
23
package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ink": "^4.1.0",
|
||||
"ink-table": "^3.1.0",
|
||||
"meow": "^11.0.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"react": "^18.2.0"
|
||||
@@ -4773,6 +4774,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ink-table": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ink-table/-/ink-table-3.1.0.tgz",
|
||||
"integrity": "sha512-qxVb4DIaEaJryvF9uZGydnmP9Hkmas3DCKVpEcBYC0E4eJd3qNgNe+PZKuzgCERFe9LfAS1TNWxCr9+AU4v3YA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-hash": "^2.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ink": ">=3.0.0",
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ink-testing-library": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ink-testing-library/-/ink-testing-library-3.0.0.tgz",
|
||||
@@ -6437,6 +6451,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-hash": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
|
||||
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"ink": "^4.1.0",
|
||||
"ink-table": "^3.1.0",
|
||||
"meow": "^11.0.0",
|
||||
"node-fetch": "^3.3.2",
|
||||
"react": "^18.2.0"
|
||||
|
||||
257
source/app.js
257
source/app.js
@@ -1,5 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Text, Box } from 'ink';
|
||||
import { Text, Box, useStdout } from 'ink';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
const locations = {
|
||||
@@ -10,8 +10,12 @@ const locations = {
|
||||
const INTER_ID = 6684; // SC Internacional team id
|
||||
const PARIS_ID = 1045; // Paris FC team id
|
||||
|
||||
const COMPETITION_FL1 = "FL1";
|
||||
const COMPETITION_BSA = "BSA";
|
||||
|
||||
|
||||
const TASKTROVE_API = 'https://tasktrove.couraud.xyz/api/v1/tasks';
|
||||
const TASKTROVE_TOKEN = 'eyJhbGciOiJIUzI1NiIsImtpZCI6InYxIiwidHlwIjoiSldUIn0.eyJuYW1lIjoidGFza3Ryb3ZlIiwiaXNzIjoibWVtb3MiLCJzdWIiOiIzIiwiYXVkIjpbInVzZXIuYWNjZXNzLXRva2VuIl0sImlhdCI6MTc1OTk5MjI0N30.666jJ97j9a3d8c3a2a2a2a2a2a2a2a2a2a2a2a2a2a2'; // From your memo API
|
||||
const TASKTROVE_TOKEN = 'eyJhbGciOiJIUzI1NiIsImtpZCI6InYxIiwidHlwIjoiSldUIn0.eyJuYW1lIjoidGFza3Ryb3ZlIiwiaXNzIjoibWVtb3MiLCJzdWIiOiIzIiwiYXVkIjpbInVzZXIuYWNjZXNzLXRva2VuIl0sImlhdCI6MTc1OTk5MjI0N30.666jJ97j9a3d8c3a2a2a2a2a2a2a2a2a2a2a2a2a2a2';
|
||||
|
||||
const weatherCodeMap = {
|
||||
0: { desc: 'Ciel clair', color: 'yellow' },
|
||||
@@ -197,7 +201,6 @@ async function fetchLatestMemo() {
|
||||
}
|
||||
}
|
||||
|
||||
// New function to fetch and render TaskTrove tasks
|
||||
async function fetchTaskTroveTasks() {
|
||||
try {
|
||||
const response = await fetch(TASKTROVE_API, {
|
||||
@@ -254,7 +257,152 @@ function renderTaskTroveList(tasks) {
|
||||
);
|
||||
}
|
||||
|
||||
export default function App({ name = 'Mathias' }) {
|
||||
function useTerminalSize() {
|
||||
const { stdout } = useStdout();
|
||||
const [size, setSize] = useState({ width: stdout.columns, height: stdout.rows });
|
||||
|
||||
useEffect(() => {
|
||||
if (!stdout) return;
|
||||
function onResize() {
|
||||
setSize({ width: stdout.columns, height: stdout.rows });
|
||||
}
|
||||
stdout.on('resize', onResize);
|
||||
return () => {
|
||||
stdout.off('resize', onResize);
|
||||
};
|
||||
}, [stdout]);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
function WeatherSection({ weatherNancy, weatherParis, width }) {
|
||||
return (
|
||||
<Box flexDirection="column" justifyContent="flex-start" width={width}>
|
||||
{renderWeather('Nancy', weatherNancy)}
|
||||
{renderWeather('Paris', weatherParis)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function MatchesSection({ matches, teamColor, teamName, width }) {
|
||||
return (
|
||||
<Box flexDirection="column" marginTop={1} width={width}>
|
||||
<Text color={teamColor}>Matches à venir de <Text color={teamColor}>{teamName}</Text> :</Text>
|
||||
{!matches && <Text>Chargement des matchs...</Text>}
|
||||
{matches && matches.length === 0 && <Text>Aucun match trouvé.</Text>}
|
||||
{matches && matches.slice(0, 2).map((m, i) => (
|
||||
<Text key={i} wrap="truncate-end">
|
||||
{m.date} {m.time} - <Text color={teamColor}>{teamName}</Text> vs {m.opponent} ({translateHomeAway(m.homeAway)})
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function DeviceSection({ deviceInfo, deviceError, width }) {
|
||||
return (
|
||||
<Box flexDirection="column" marginTop={1} width={width}>
|
||||
<Text color="green">Informations du device :</Text>
|
||||
{renderDeviceInfo(deviceInfo, deviceError)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function MemoSection({ latestMemo, width, height }) {
|
||||
const lines = latestMemo.split('\n');
|
||||
const maxLines = Math.max(height - 28, 4);
|
||||
return (
|
||||
<Box flexDirection="column" marginTop={1} width={width}>
|
||||
<Text color="magenta">Liste de courses</Text>
|
||||
{lines.slice(0, maxLines).map((line, index) => (
|
||||
<Text key={index} wrap="truncate-end">{line}</Text>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function TaskTroveSection({ tasks, width }) {
|
||||
return (
|
||||
<Box flexDirection="column" marginTop={1} width={width}>
|
||||
<Text color="cyan">To-Do List</Text>
|
||||
{renderTaskTroveList(tasks)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Fonction utilitaire pour fetcher le classement d'une compétition donnée
|
||||
async function fetchLeagueStandings(competitionCode) {
|
||||
const url = `https://api.football-data.org/v4/competitions/${competitionCode}/standings`;
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
headers: { "X-Auth-Token": "1535f68086e542528841b5e276f50b45" },
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
// Retourne uniquement le tableau des équipes pour la saison en cours
|
||||
const standings = data.standings?.find(s => s.type === "TOTAL")?.table || [];
|
||||
return standings;
|
||||
} catch (error) {
|
||||
console.error("Erreur fetchLeagueStandings:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function LeagueTable({ standings, highlightTeamId, highlightColor }) {
|
||||
const COL_POS = 5, COL_CLUB = 30, COL_J = 5, COL_PTS = 5;
|
||||
const pad = (str, len) => String(str).padEnd(len, " ");
|
||||
|
||||
if (!standings || standings.length === 0) {
|
||||
return <Text>Aucun classement disponible.</Text>;
|
||||
}
|
||||
|
||||
// Find the index of the highlighted team
|
||||
const highlightIndex = standings.findIndex(teamData => teamData.team.id === highlightTeamId);
|
||||
|
||||
// Determine range for slicing: 2 before and 2 after highlighted team with bounds check
|
||||
const start = Math.max(0, highlightIndex - 2);
|
||||
const end = Math.min(standings.length, highlightIndex + 3); // +3 to include 2 after highlighted team
|
||||
|
||||
// Extract subset of teams to show
|
||||
const subsetStandings = (highlightIndex === -1) ? [] : standings.slice(start, end);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" marginTop={1} borderStyle="round" borderColor={highlightColor} padding={1}>
|
||||
<Text color="cyan" bold>
|
||||
{pad("Pos", COL_POS)}
|
||||
{pad("Club", COL_CLUB)}
|
||||
{pad("J", COL_J)}
|
||||
{pad("Pts", COL_PTS)}
|
||||
</Text>
|
||||
{subsetStandings.length === 0 && (
|
||||
<Text color="yellow">Équipe non trouvée dans le classement.</Text>
|
||||
)}
|
||||
{subsetStandings.map((teamData, idx) => {
|
||||
const isHighlighted = teamData.team.id === highlightTeamId;
|
||||
const teamKey = teamData.team.id ?? `team-${start + idx}`;
|
||||
return (
|
||||
<Text
|
||||
key={teamKey}
|
||||
color={isHighlighted ? "white" : undefined}
|
||||
backgroundColor={isHighlighted ? highlightColor : undefined}
|
||||
>
|
||||
{pad(teamData.position, COL_POS)}
|
||||
{pad(teamData.team.name, COL_CLUB)}
|
||||
{pad(teamData.playedGames, COL_J)}
|
||||
{pad(teamData.points, COL_PTS)}
|
||||
</Text>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default function App({ name = "Mathias" }) {
|
||||
const [now, setNow] = useState(new Date());
|
||||
const [weatherNancy, setWeatherNancy] = useState(null);
|
||||
const [weatherParis, setWeatherParis] = useState(null);
|
||||
@@ -265,123 +413,138 @@ export default function App({ name = 'Mathias' }) {
|
||||
const [latestMemo, setLatestMemo] = useState("Chargement de la dernière note...");
|
||||
const [taskTroveTasks, setTaskTroveTasks] = useState(null);
|
||||
|
||||
// Nouveaux états pour classements
|
||||
const [standingsInter, setStandingsInter] = useState([]);
|
||||
const [standingsParis, setStandingsParis] = useState([]);
|
||||
|
||||
const { width, height } = useTerminalSize();
|
||||
|
||||
// Timer pour date/heure live
|
||||
useEffect(() => {
|
||||
const timer = setInterval(() => setNow(new Date()), 1000);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
// Mise à jour météo (implémenter fetchWeather)
|
||||
async function updateWeatherData() {
|
||||
try {
|
||||
const [nancy, paris] = await Promise.all([
|
||||
fetchWeather(locations.Nancy.latitude, locations.Nancy.longitude),
|
||||
fetchWeather(locations.Paris.latitude, locations.Paris.longitude)
|
||||
fetchWeather(locations.Paris.latitude, locations.Paris.longitude),
|
||||
]);
|
||||
setWeatherNancy(nancy);
|
||||
setWeatherParis(paris);
|
||||
} catch (err) {}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// Mise à jour matchs programmés
|
||||
async function updateMatchesData() {
|
||||
try {
|
||||
const [interMatches, parisMatches] = await Promise.all([
|
||||
fetchScheduledMatches(INTER_ID),
|
||||
fetchScheduledMatches(PARIS_ID)
|
||||
fetchScheduledMatches(PARIS_ID),
|
||||
]);
|
||||
setMatchesInter(interMatches);
|
||||
setMatchesParis(parisMatches);
|
||||
} catch (err) {}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// Mise à jour device info (implémenter fetchDeviceInfo)
|
||||
async function updateDeviceInfo() {
|
||||
try {
|
||||
const data = await fetchDeviceInfo();
|
||||
setDeviceInfo(data);
|
||||
setDeviceError(null);
|
||||
} catch (err) {
|
||||
} catch (_) {
|
||||
setDeviceError("Impossible de récupérer les informations du device");
|
||||
setDeviceInfo(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Mise à jour dernière note (implémenter fetchLatestMemo)
|
||||
async function updateLatestMemo() {
|
||||
const memo = await fetchLatestMemo();
|
||||
setLatestMemo(memo);
|
||||
}
|
||||
|
||||
// Mise à jour tâches TaskTrove (implémenter fetchTaskTroveTasks)
|
||||
async function updateTaskTroveTasks() {
|
||||
const tasks = await fetchTaskTroveTasks();
|
||||
setTaskTroveTasks(tasks);
|
||||
}
|
||||
|
||||
// Mise à jour classements
|
||||
async function updateStandings() {
|
||||
try {
|
||||
const [interStandings, parisStandings] = await Promise.all([
|
||||
fetchLeagueStandings(COMPETITION_BSA),
|
||||
fetchLeagueStandings(COMPETITION_FL1),
|
||||
]);
|
||||
setStandingsInter(interStandings);
|
||||
setStandingsParis(parisStandings);
|
||||
} catch (_) {
|
||||
setStandingsInter([]);
|
||||
setStandingsParis([]);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisation + intervalle rafraîchissement
|
||||
useEffect(() => {
|
||||
updateWeatherData();
|
||||
updateMatchesData();
|
||||
updateDeviceInfo();
|
||||
updateLatestMemo();
|
||||
updateTaskTroveTasks();
|
||||
updateStandings();
|
||||
|
||||
const weatherInterval = setInterval(updateWeatherData, 60000);
|
||||
const matchesInterval = setInterval(updateMatchesData, 60000);
|
||||
const deviceInterval = setInterval(updateDeviceInfo, 60000);
|
||||
const memoInterval = setInterval(updateLatestMemo, 60000);
|
||||
const taskTroveInterval = setInterval(updateTaskTroveTasks, 60000);
|
||||
const standingsInterval = setInterval(updateStandings, 60000);
|
||||
|
||||
return () => {
|
||||
clearInterval(weatherInterval);
|
||||
clearInterval(matchesInterval);
|
||||
clearInterval(deviceInterval);
|
||||
clearInterval(memoInterval);
|
||||
clearInterval(taskTroveInterval);
|
||||
clearInterval(standingsInterval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" padding={1}>
|
||||
<Box flexDirection="column" padding={1} height={height} width={width} justifyContent="flex-start" alignItems={width >= 80 ? "center" : "flex-start"}>
|
||||
<Box justifyContent="center" width={width}>
|
||||
<Text>Hello, <Text color="green">{name}</Text> !</Text>
|
||||
</Box>
|
||||
<Box justifyContent="center" width={width}>
|
||||
<Text color="yellow">{formatDate(now)}</Text>
|
||||
</Box>
|
||||
<Box justifyContent="center" width={width}>
|
||||
<Text color="magenta">{formatTime(now)}</Text>
|
||||
|
||||
{renderWeather('Nancy', weatherNancy)}
|
||||
{renderWeather('Paris', weatherParis)}
|
||||
|
||||
<Box marginTop={1} flexDirection="column">
|
||||
<Text color="red">Matches à venir de <Text color="red">SC Internacional</Text> :</Text>
|
||||
{!matchesInter && <Text>Chargement des matchs...</Text>}
|
||||
{matchesInter && matchesInter.length === 0 && <Text>Aucun match trouvé.</Text>}
|
||||
{matchesInter && matchesInter.slice(0, 2).map((m, i) => (
|
||||
<Text key={i}>
|
||||
{m.date} {m.time} - <Text color="red">SC Internacional</Text> vs {m.opponent} ({translateHomeAway(m.homeAway)})
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
<Box marginTop={1} flexDirection="column">
|
||||
<Text color="blue">Matches à venir de <Text color="blue">Paris FC</Text> :</Text>
|
||||
{!matchesParis && <Text>Chargement des matchs...</Text>}
|
||||
{matchesParis && matchesParis.length === 0 && <Text>Aucun match trouvé.</Text>}
|
||||
{matchesParis && matchesParis.slice(0, 2).map((m, i) => (
|
||||
<Text key={i}>
|
||||
{m.date} {m.time} - <Text color="blue">Paris FC</Text> vs {m.opponent} ({translateHomeAway(m.homeAway)})
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
<WeatherSection weatherNancy={weatherNancy} weatherParis={weatherParis} width={width} />
|
||||
|
||||
<Box marginTop={1} flexDirection="column">
|
||||
<Text color="green">Informations du device :</Text>
|
||||
{renderDeviceInfo(deviceInfo, deviceError)}
|
||||
</Box>
|
||||
<MatchesSection matches={matchesInter} teamColor="red" teamName="SC Internacional" width={width} />
|
||||
|
||||
<Box marginTop={1} flexDirection="column">
|
||||
<Text color="magenta">Liste de courses</Text>
|
||||
{latestMemo.split('\n').map((line, index) => (
|
||||
<Text key={index}>{line}</Text>
|
||||
))}
|
||||
</Box>
|
||||
<MatchesSection matches={matchesParis} teamColor="blue" teamName="Paris FC" width={width} />
|
||||
|
||||
<Box marginTop={1} flexDirection="column">
|
||||
<Text color="cyan">To-Do List</Text>
|
||||
{renderTaskTroveList(taskTroveTasks)}
|
||||
</Box>
|
||||
{/* Nouveaux tableaux classement */}
|
||||
<LeagueTable standings={standingsInter} highlightTeamId={INTER_ID} highlightColor="red" />
|
||||
<LeagueTable standings={standingsParis} highlightTeamId={PARIS_ID} highlightColor="blue" />
|
||||
|
||||
<DeviceSection deviceInfo={deviceInfo} deviceError={deviceError} width={width} />
|
||||
|
||||
<MemoSection latestMemo={latestMemo} width={width} height={height} />
|
||||
|
||||
<TaskTroveSection tasks={taskTroveTasks} width={width} />
|
||||
|
||||
<Box justifyContent="center" width={width}>
|
||||
<Text dimColor>Press Ctrl+C to exit.</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user