@vixoniccom/footbal-score 1.4.6-dev.1 → 1.4.6-dev.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/App.tsx CHANGED
@@ -1,154 +1,49 @@
1
- import React, { useEffect, useRef, useState } from 'react'
2
- import localforage from 'localforage'
1
+ import React from 'react'
2
+ import moment from 'moment'
3
3
  import { FontLoader, FormattedText, Table } from './components'
4
- import { getFixture } from './helpers/getFixture'
5
4
 
6
5
  export type Props = {
7
- data: VixonicData
8
- start: boolean
6
+ appData?: VixonicData
7
+ fixtures: FootballFixture[]
8
+ serviceError?: string
9
9
  }
10
10
 
11
- const LIVE_STATUSES = ['1H', 'HT', '2H', 'P', 'ET', 'BT', 'LIVE', 'INT']
12
- const PRE_MATCH_MS = 5 * 60 * 1000
11
+ export const App: React.FunctionComponent<Props> = ({ appData, fixtures }) => {
12
+ if (!appData) return null
13
13
 
14
- localforage.config({ name: 'request_api_football' })
14
+ const { parameters, downloadsPath } = appData
15
+ const { backgroundImage, padding, msj0 = '', matchesYesterday, matchesTomorrow } = parameters
15
16
 
16
- const log = (msg: string, ...args: any[]) =>
17
- console.log(`[⚽ football-score ${new Date().toLocaleTimeString()}]`, msg, ...args)
17
+ console.log('[App] fixtures received:', fixtures.length, fixtures)
18
18
 
19
- // Returns the timestamp (ms) when the next fetch should happen based on fixture states.
20
- // Persisted in localStorage so re-mounts don't reset the schedule.
21
- function computeNextFetchTime(fixtures: any[], pollIntervalMs: number): number {
22
- const now = Date.now()
23
-
24
- const liveMatches = fixtures.filter(f => LIVE_STATUSES.includes(f.fixture?.status?.short))
25
- if (liveMatches.length > 0) {
26
- const next = new Date(now + pollIntervalMs)
27
- log(`🟢 ${liveMatches.length} partido(s) en vivo → próximo fetch en ${next.toLocaleTimeString()} (${pollIntervalMs / 1000}s)`)
28
- return now + pollIntervalMs
29
- }
30
-
31
- const nextMatchAt = fixtures
32
- .filter(f => f.fixture?.status?.short === 'NS')
33
- .map(f => new Date(f.fixture.date).getTime())
34
- .filter(t => t > now)
35
- .sort((a, b) => a - b)[0]
36
-
37
- if (nextMatchAt !== undefined) {
38
- const wakeUp = nextMatchAt - PRE_MATCH_MS
39
- if (wakeUp <= now) {
40
- const next = new Date(now + pollIntervalMs)
41
- log(`🟡 Dentro de ventana pre-partido → próximo fetch en ${next.toLocaleTimeString()}`)
42
- return now + pollIntervalMs
43
- }
44
- log(`⏰ Próximo partido: ${new Date(nextMatchAt).toLocaleTimeString()} → fetch a las ${new Date(wakeUp).toLocaleTimeString()} (10 min antes)`)
45
- return wakeUp
46
- }
47
-
48
- const tomorrow = new Date()
49
- tomorrow.setDate(tomorrow.getDate() + 1)
50
- tomorrow.setHours(0, 15, 0, 0)
51
- log(`💤 Sin partidos pendientes → próximo check: ${tomorrow.toLocaleString()}`)
52
- return tomorrow.getTime()
53
- }
54
-
55
- export const App: React.FunctionComponent<Props> = ({ data, start }) => {
56
- const [dataFixture, setDataFixture] = useState<any[]>([])
57
- const [leagueState, setLeagueState] = useState<string>('')
58
- const { parameters, downloadsPath } = data
59
- const { backgroundImage, padding, msj0 = '', league = '9', debugTimezone = false } = parameters
60
19
  const backgroundImageState = backgroundImage?.filename
61
20
  ? `url("${downloadsPath}/${backgroundImage.filename}")`
62
21
  : ''
63
22
 
64
- const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
65
- // Refs keep the timeout callback from closing over stale props
66
- const leagueRef = useRef(league)
67
- const paramsRef = useRef(parameters)
68
- useEffect(() => {
69
- leagueRef.current = league
70
- paramsRef.current = parameters
71
- })
72
-
73
- const clearSchedule = () => {
74
- if (timeoutRef.current !== null) {
75
- clearTimeout(timeoutRef.current)
76
- timeoutRef.current = null
77
- }
78
- }
79
-
80
- const fetchAndSchedule = async () => {
81
- const { matchesYesterday, matchesTomorrow, updateTime: ut = 300000 } = paramsRef.current
82
- const cached: any = await localforage.getItem('football-score')
83
-
84
- log(`🔄 Fetching fixtures (liga ${leagueRef.current})...`)
85
- let fixtures: any[]
86
- try {
87
- fixtures = await getFixture(leagueRef.current, matchesYesterday, matchesTomorrow)
88
- log(`✅ ${fixtures.length} fixture(s) recibidos`)
89
- } catch (err) {
90
- console.error('[football-score] API error:', err)
91
- fixtures = cached?.data ?? []
92
- log(`❌ API falló → usando cache (${fixtures.length} fixtures)`)
93
- }
94
-
95
- const nextFetchTime = computeNextFetchTime(fixtures, ut)
23
+ const serviceUtcOffset = fixtures[0]
24
+ ? moment.parseZone(fixtures[0].fixture.date).utcOffset()
25
+ : 0
96
26
 
97
- try {
98
- await localforage.setItem('football-score', { date: Date.now(), data: fixtures, nextFetchTime })
99
- } catch (err) {
100
- console.error('[football-score] localStorage write error', err)
101
- }
27
+ const today = moment().utcOffset(serviceUtcOffset).format('YYYY-MM-DD')
28
+ const yesterdayStr = moment().utcOffset(serviceUtcOffset).subtract(1, 'days').format('YYYY-MM-DD')
29
+ const tomorrowStr = moment().utcOffset(serviceUtcOffset).add(1, 'days').format('YYYY-MM-DD')
102
30
 
103
- setDataFixture(fixtures)
31
+ console.log('[App] date context — today:', today, 'yesterday:', yesterdayStr, 'tomorrow:', tomorrowStr, 'utcOffset:', serviceUtcOffset)
32
+ console.log('[App] filter config — matchesYesterday:', matchesYesterday, 'matchesTomorrow:', matchesTomorrow)
104
33
 
105
- clearSchedule()
106
- timeoutRef.current = setTimeout(fetchAndSchedule, Math.max(0, nextFetchTime - Date.now()))
107
- }
108
-
109
- const init = async () => {
110
- const cached: any = await localforage.getItem('football-score')
111
- const now = Date.now()
112
-
113
- if (!cached?.data) {
114
- log(`🆕 Sin cache → fetch inmediato`)
115
- fetchAndSchedule()
116
- return
117
- }
118
-
119
- setDataFixture(cached.data)
120
-
121
- if (!cached.nextFetchTime || cached.nextFetchTime <= now) {
122
- log(`🔁 Mount check: nextFetchTime ya pasó (${cached.nextFetchTime ? new Date(cached.nextFetchTime).toLocaleTimeString() : 'null'}) → fetch inmediato`)
123
- fetchAndSchedule()
124
- } else {
125
- const mins = Math.round((cached.nextFetchTime - now) / 60000)
126
- log(`💾 Mount check: usando cache → próximo fetch en ${new Date(cached.nextFetchTime).toLocaleTimeString()} (~${mins} min)`)
127
- }
128
- }
129
-
130
- useEffect(() => {
131
- if (!start) return
132
- setLeagueState(league)
133
- init()
134
- return clearSchedule
135
- }, [data])
136
-
137
- useEffect(() => {
138
- if (leagueState === '' || leagueState === league) return
139
- setLeagueState(league)
140
- clearSchedule()
141
- localforage.clear()
142
- fetchAndSchedule()
143
- }, [league])
34
+ const filteredFixtures = fixtures.filter(f => {
35
+ const fixtureDate = moment.parseZone(f.fixture.date).format('YYYY-MM-DD')
36
+ if (fixtureDate === today) return true
37
+ if (fixtureDate === yesterdayStr && matchesYesterday) return true
38
+ if (fixtureDate === tomorrowStr && matchesTomorrow) return true
39
+ return false
40
+ })
144
41
 
145
- const timezone = (() => {
146
- try { return Intl.DateTimeFormat().resolvedOptions().timeZone } catch { return 'UTC' }
147
- })()
42
+ console.log('[App] filteredFixtures count:', filteredFixtures.length)
148
43
 
149
44
  return (
150
45
  <>
151
- {dataFixture.length > 0 ?
46
+ {filteredFixtures.length > 0 ?
152
47
  <div style={{
153
48
  position: 'absolute',
154
49
  top: 0,
@@ -165,7 +60,7 @@ export const App: React.FunctionComponent<Props> = ({ data, start }) => {
165
60
  <FormattedText text={parameters.title0 || 'Partidos Mundial'} format={parameters.titleFormat} />
166
61
  </div>
167
62
  }
168
- <Table dataFixture={dataFixture} parameters={parameters} />
63
+ <Table dataFixture={filteredFixtures} parameters={parameters} />
169
64
  </div> :
170
65
  <div style={{
171
66
  position: 'absolute',
@@ -185,25 +80,6 @@ export const App: React.FunctionComponent<Props> = ({ data, start }) => {
185
80
  </div>
186
81
  </div>
187
82
  }
188
- {debugTimezone && (
189
- <div style={{
190
- position: 'fixed',
191
- bottom: 8,
192
- right: 8,
193
- background: 'rgba(0,0,0,0.75)',
194
- color: '#00ff00',
195
- fontSize: 10,
196
- fontFamily: 'monospace',
197
- padding: '4px 8px',
198
- borderRadius: 4,
199
- zIndex: 9999,
200
- lineHeight: 1.4,
201
- pointerEvents: 'none',
202
- whiteSpace: 'nowrap',
203
- }}>
204
- TZ: {timezone}
205
- </div>
206
- )}
207
83
  </>
208
84
  )
209
85
  }
@@ -11,11 +11,11 @@ interface Props {
11
11
  export const Results: React.FunctionComponent<Props> = ({ parameters, goals, score }) => {
12
12
  return (
13
13
  <div style={{ display: 'flex', justifyContent: 'center' }}>
14
- {isNumber(score.penalty.home) ? <FormattedText text={`(${score.penalty.home})`} format={parameters.goalsFormat} /> : ''}
14
+ {isNumber(score.penalty?.home) ? <FormattedText text={`(${score.penalty.home})`} format={parameters.goalsFormat} /> : ''}
15
15
  <FormattedText text={goals.home ? goals.home : 0} format={parameters.goalsFormat} />
16
16
  <FormattedText text={'-'} format={parameters.goalsFormat} />
17
17
  <FormattedText text={goals.away ? goals.away : 0} format={parameters.goalsFormat} />
18
- {isNumber(score.penalty.away) ? <FormattedText text={`(${score.penalty.away})`} format={parameters.goalsFormat} /> : ''}
18
+ {isNumber(score.penalty?.away) ? <FormattedText text={`(${score.penalty.away})`} format={parameters.goalsFormat} /> : ''}
19
19
  </div>
20
20
  )
21
21
  }
@@ -1,3 +1,4 @@
1
+ import moment from 'moment'
1
2
  import React, { useEffect, useState } from 'react'
2
3
  import { Results, Status, Time } from './'
3
4
  import { FormattedText } from '../../'
@@ -71,7 +72,7 @@ export const Row: React.FunctionComponent<Props> = ({ item, parameters }) => {
71
72
  justifyContent: 'center'
72
73
  }}>
73
74
  <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
74
- <Time time={Number(fixture.timestamp)} parameters={parameters} />
75
+ <Time time={moment.parseZone(fixture.date).unix()} utcOffset={moment.parseZone(fixture.date).utcOffset()} parameters={parameters} />
75
76
  </div>
76
77
  <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
77
78
  <Status status={fixture.status.short} parameters={parameters} />
@@ -4,14 +4,15 @@ import { FormattedText } from '../../'
4
4
 
5
5
  interface Props {
6
6
  time: number
7
+ utcOffset: number
7
8
  parameters: VixonicParameters
8
9
  }
9
10
 
10
- export const Time: React.FunctionComponent<Props> = ({ time, parameters }) => {
11
+ export const Time: React.FunctionComponent<Props> = ({ time, utcOffset, parameters }) => {
11
12
  return (
12
13
  <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
13
- <FormattedText text={moment.unix(time).format('DD-MM')} format={parameters.timeFormat} />
14
- <FormattedText text={moment.unix(time).format('h:mm a')} format={parameters.timeFormat} />
14
+ <FormattedText text={moment.unix(time).utcOffset(utcOffset).format('DD-MM')} format={parameters.timeFormat} />
15
+ <FormattedText text={moment.unix(time).utcOffset(utcOffset).format('h:mm a')} format={parameters.timeFormat} />
15
16
  </div>
16
17
  )
17
18
  }
@@ -1,3 +1,4 @@
1
+ import moment from 'moment'
1
2
  import React, { useEffect, useState } from 'react'
2
3
  import { Row } from './components'
3
4
 
@@ -9,11 +10,16 @@ type Props = {
9
10
  export const Table: React.FunctionComponent<Props> = ({ dataFixture, parameters }) => {
10
11
  const { itemsPerPage, durationPerPage } = parameters
11
12
  const rows = Number(itemsPerPage || 4)
12
- const orderedData = dataFixture.sort((a, b) => a.fixture.timestamp - b.fixture.timestamp)
13
+ const orderedData = dataFixture.sort((a, b) => moment.parseZone(a.fixture.date).unix() - moment.parseZone(b.fixture.date).unix())
13
14
  const [page, setPage] = useState<number>(1)
14
15
  const [items, setItems] = useState<string[][]>(orderedData.slice(0, rows))
15
16
  const total = orderedData.length
16
17
 
18
+ useEffect(() => {
19
+ setPage(1)
20
+ setItems(orderedData.slice(0, rows))
21
+ }, [dataFixture])
22
+
17
23
  useEffect(() => {
18
24
  const timer = setTimeout(() => {
19
25
  handlePagination()
@@ -0,0 +1,27 @@
1
+ export class FootballLoader {
2
+ public onData?: (fixtures: FootballFixture[]) => void = undefined
3
+ public onError?: (error: string) => void = undefined
4
+
5
+ update(appData: VixonicData) {
6
+ const serviceId = appData.parameters.footballService?.id
7
+ console.log('[FootballLoader] serviceId:', serviceId)
8
+
9
+ if (!serviceId || !appData.services[serviceId]) {
10
+ console.log('[FootballLoader] service not found in appData.services. Available keys:', Object.keys(appData.services))
11
+ return
12
+ }
13
+
14
+ const rawData = appData.services[serviceId].data as FootballServiceData | undefined
15
+ console.log('[FootballLoader] rawData:', JSON.stringify(rawData, null, 2))
16
+
17
+ const fixtures = rawData?.fixtures ?? []
18
+ console.log('[FootballLoader] fixtures count:', fixtures.length)
19
+ if (fixtures.length > 0) {
20
+ console.log('[FootballLoader] first fixture:', JSON.stringify(fixtures[0], null, 2))
21
+ }
22
+
23
+ this.onData?.(fixtures)
24
+ }
25
+
26
+ stop() {}
27
+ }
@@ -0,0 +1,28 @@
1
+ declare interface FootballFixture {
2
+ fixture: {
3
+ id: number
4
+ date: string
5
+ status: { long: string; short: string; elapsed: number | null }
6
+ venue: { name: string; city: string }
7
+ }
8
+ league: {
9
+ id: number
10
+ name: string
11
+ country: string
12
+ logo: string
13
+ round: string
14
+ }
15
+ teams: {
16
+ home: { id: number; name: string; logo: string; winner: boolean | null }
17
+ away: { id: number; name: string; logo: string; winner: boolean | null }
18
+ }
19
+ goals: { home: number | null; away: number | null }
20
+ score: {
21
+ halftime: { home: number | null; away: number | null }
22
+ fulltime: { home: number | null; away: number | null }
23
+ }
24
+ }
25
+
26
+ declare interface FootballServiceData {
27
+ fixtures: FootballFixture[]
28
+ }
@@ -1,46 +1 @@
1
- import moment from 'moment'
2
- import axios from 'axios'
3
- import { mockFixtures } from './mockFixtures'
4
-
5
- if (!process.env.FOOTBALL_API_KEY || !process.env.FOOTBALL_API_HOST || !process.env.FOOTBALL_API_URL)
6
- console.warn('Missing API credentials')
7
- const FOOTBALL_API_KEY = process.env.FOOTBALL_API_KEY
8
- const FOOTBALL_API_HOST = process.env.FOOTBALL_API_HOST
9
- const FOOTBALL_API_URL = process.env.FOOTBALL_API_URL
10
- const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000
11
-
12
- const USE_MOCK = false
13
-
14
- export const getFixture = async (league: string, yesterday?: boolean, tomorrow?: boolean) => {
15
- if (USE_MOCK) return mockFixtures
16
-
17
- const today = moment().format('YYYY-MM-DD')
18
- const year = moment().format('YYYY')
19
- const from = yesterday ? moment(Date.now() - MILLISECONDS_PER_DAY).format('YYYY-MM-DD') : today
20
- const to = tomorrow ? moment(Date.now() + MILLISECONDS_PER_DAY).format('YYYY-MM-DD') : today
21
- const timezone = (() => {
22
- try {
23
- const tz = Intl.DateTimeFormat().resolvedOptions().timeZone
24
- console.log(`[football-score] Timezone resolved: ${tz}`)
25
- return tz
26
- } catch (error) {
27
- console.warn('[football-score] Failed to resolve timezone, defaulting to UTC', error)
28
- return 'UTC'
29
- }
30
- })()
31
-
32
- const options = {
33
- method: 'GET',
34
- url: FOOTBALL_API_URL,
35
- params: { from, to, league, season: year, timezone },
36
- headers: { 'X-RapidAPI-Key': FOOTBALL_API_KEY, 'X-RapidAPI-Host': FOOTBALL_API_HOST },
37
- }
38
-
39
- try {
40
- const response = await axios.request(options)
41
- return response.data.response
42
- } catch (error) {
43
- console.error(error)
44
- return []
45
- }
46
- }
1
+ export const resolvedTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone
package/src/main.ts CHANGED
@@ -1,27 +1,56 @@
1
- import ReactDOM from 'react-dom'
2
1
  import React from 'react'
3
- import { App, Props } from './App'
4
-
2
+ import ReactDOM from 'react-dom'
3
+ import { App } from './App'
4
+ import { FootballLoader } from './football-loader'
5
5
  const { ipcRenderer } = require('electron')
6
- let start: boolean = false
6
+
7
+ let appData: VixonicData | undefined = undefined
8
+ let fixtures: FootballFixture[] = []
9
+ let serviceError: string | undefined = undefined
10
+
11
+ const footballLoader = new FootballLoader()
12
+
13
+ footballLoader.onData = (update) => {
14
+ fixtures = update
15
+ render()
16
+ }
17
+
18
+ footballLoader.onError = (error) => {
19
+ serviceError = error
20
+ render()
21
+ }
7
22
 
8
23
  ipcRenderer.on('preload', (_event: any, data: VixonicData) => {
9
- render(data)
24
+ console.log('[main] preload received. parameters:', JSON.stringify(data.parameters, null, 2))
25
+ console.log('[main] preload services keys:', Object.keys(data.services))
26
+ appData = data
27
+ footballLoader.update(data)
28
+ render()
10
29
  })
11
30
 
12
31
  ipcRenderer.on('start', (_event: any, data: VixonicData) => {
13
- start = true
14
- render(data)
32
+ console.log('[main] start received. parameters:', JSON.stringify(data.parameters, null, 2))
33
+ console.log('[main] start services keys:', Object.keys(data.services))
34
+ appData = data
35
+ footballLoader.update(data)
36
+ render()
15
37
  })
16
38
 
17
39
  ipcRenderer.on('update', (_event: any, data: VixonicData) => {
18
- render(data)
40
+ console.log('[main] update received. services keys:', Object.keys(data.services))
41
+ appData = data
42
+ footballLoader.update(data)
43
+ render()
19
44
  })
20
45
 
21
46
  ipcRenderer.on('finish', (_event: any) => {
47
+ footballLoader.stop()
22
48
  console.log('finish')
23
49
  })
24
50
 
25
- function render(data: VixonicData) {
26
- ReactDOM.render(React.createElement<Props>(App, {data, start}), document.getElementById('root'))
51
+ function render() {
52
+ ReactDOM.render(
53
+ React.createElement(App, { appData, fixtures, serviceError }),
54
+ document.getElementById('root')
55
+ )
27
56
  }
@@ -5,5 +5,5 @@ declare type VixonicData = {
5
5
  }
6
6
 
7
7
  declare type VixonicParameters = Partial<{
8
- league: '1' | '9' | '4' | '22' | '15' | '1220' | '128' | '265', matchesYesterday: boolean, matchesTomorrow: boolean, timeStart: '12m' | '1m' | '2m' | '3m' | '4m' | '5m' | '6m' | '7m' | '8m' | '9m' | '10m' | '11m' | '12pm' | '1pm' | '2pm' | '3pm' | '4pm' | '5pm' | '6pm' | '7pm' | '8pm' | '9pm' | '10pm' | '11pm', timeEnd: '12m' | '1m' | '2m' | '3m' | '4m' | '5m' | '6m' | '7m' | '8m' | '9m' | '10m' | '11m' | '12pm' | '1pm' | '2pm' | '3pm' | '4pm' | '5pm' | '6pm' | '7pm' | '8pm' | '9pm' | '10pm' | '11pm', updateTime: 300000 | 600000 | 1800000 | 3600000 | 21600000 | 43200000 | 86400000, titleEnabled: boolean, title0: string, titleFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, msj0: string, formatMjs: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, alignVerticalMsj: 'flex-start' | 'center' | 'flex-end', itemsPerPage: number, durationPerPage: number, backgroundImage: {id?: string, filename?: string, extension?: string}, padding: string, columnGap: string, rowGap: string, timeFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthTime: string, statusFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthStatus: string, goalsFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthScore: string, descriptionEnabled: boolean, descriptionFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthDescription: string, imageSize: number, widthImage: string, debugTimezone: boolean
8
+ footballService: { id: string }, matchesYesterday: boolean, matchesTomorrow: boolean, titleEnabled: boolean, title0: string, titleFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, msj0: string, formatMjs: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, alignVerticalMsj: 'flex-start' | 'center' | 'flex-end', itemsPerPage: number, durationPerPage: number, backgroundImage: {id?: string, filename?: string, extension?: string}, padding: string, columnGap: string, rowGap: string, timeFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthTime: string, statusFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthStatus: string, goalsFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthScore: string, descriptionEnabled: boolean, descriptionFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthDescription: string, imageSize: number, widthImage: string
9
9
  }>
@@ -1,9 +1,8 @@
1
1
  {
2
2
  "parameters": {
3
+ "footballService": { "id": "f7e3a1b2-c4d5-6789-abcd-ef0123456789" },
3
4
  "league": 1,
4
5
  "debugTimezone" : false,
5
- "timeStart": "10 am",
6
- "timeEnd": "1 am",
7
6
  "matchesYesterday": false,
8
7
  "matchesTomorrow": false,
9
8
  "itemsPerPage": 2
@@ -0,0 +1,109 @@
1
+ {
2
+ "services": {
3
+ "f7e3a1b2-c4d5-6789-abcd-ef0123456789": {
4
+ "id": "f7e3a1b2-c4d5-6789-abcd-ef0123456789",
5
+ "data": {
6
+ "fixtures": [
7
+ {
8
+ "fixture": {
9
+ "id": 1001,
10
+ "date": "2026-06-22T18:00:00+00:00",
11
+ "status": { "long": "Match Finished", "short": "FT", "elapsed": 90 },
12
+ "venue": { "name": "Camp Nou", "city": "Barcelona" }
13
+ },
14
+ "league": {
15
+ "id": 140,
16
+ "name": "La Liga",
17
+ "country": "Spain",
18
+ "logo": "https://media.api-sports.io/football/leagues/140.png",
19
+ "round": "Regular Season - 38"
20
+ },
21
+ "teams": {
22
+ "home": { "id": 529, "name": "Barcelona", "logo": "https://media.api-sports.io/football/teams/529.png", "winner": true },
23
+ "away": { "id": 541, "name": "Real Madrid", "logo": "https://media.api-sports.io/football/teams/541.png", "winner": false }
24
+ },
25
+ "goals": { "home": 3, "away": 1 },
26
+ "score": {
27
+ "halftime": { "home": 1, "away": 0 },
28
+ "fulltime": { "home": 3, "away": 1 }
29
+ }
30
+ },
31
+ {
32
+ "fixture": {
33
+ "id": 1002,
34
+ "date": "2026-06-22T20:45:00+00:00",
35
+ "status": { "long": "Second Half", "short": "2H", "elapsed": 67 },
36
+ "venue": { "name": "Anfield", "city": "Liverpool" }
37
+ },
38
+ "league": {
39
+ "id": 39,
40
+ "name": "Premier League",
41
+ "country": "England",
42
+ "logo": "https://media.api-sports.io/football/leagues/39.png",
43
+ "round": "Regular Season - 38"
44
+ },
45
+ "teams": {
46
+ "home": { "id": 40, "name": "Liverpool", "logo": "https://media.api-sports.io/football/teams/40.png", "winner": null },
47
+ "away": { "id": 42, "name": "Arsenal", "logo": "https://media.api-sports.io/football/teams/42.png", "winner": null }
48
+ },
49
+ "goals": { "home": 2, "away": 2 },
50
+ "score": {
51
+ "halftime": { "home": 1, "away": 1 },
52
+ "fulltime": { "home": null, "away": null }
53
+ }
54
+ },
55
+ {
56
+ "fixture": {
57
+ "id": 1003,
58
+ "date": "2026-06-23T19:00:00+00:00",
59
+ "status": { "long": "Not Started", "short": "NS", "elapsed": null },
60
+ "venue": { "name": "Allianz Arena", "city": "Munich" }
61
+ },
62
+ "league": {
63
+ "id": 78,
64
+ "name": "Bundesliga",
65
+ "country": "Germany",
66
+ "logo": "https://media.api-sports.io/football/leagues/78.png",
67
+ "round": "Regular Season - 34"
68
+ },
69
+ "teams": {
70
+ "home": { "id": 157, "name": "Bayern Munich", "logo": "https://media.api-sports.io/football/teams/157.png", "winner": null },
71
+ "away": { "id": 165, "name": "Borussia Dortmund", "logo": "https://media.api-sports.io/football/teams/165.png", "winner": null }
72
+ },
73
+ "goals": { "home": null, "away": null },
74
+ "score": {
75
+ "halftime": { "home": null, "away": null },
76
+ "fulltime": { "home": null, "away": null }
77
+ }
78
+ },
79
+ {
80
+ "fixture": {
81
+ "id": 1004,
82
+ "date": "2026-06-22T17:30:00+00:00",
83
+ "status": { "long": "Match Finished", "short": "FT", "elapsed": 90 },
84
+ "venue": { "name": "San Siro", "city": "Milan" }
85
+ },
86
+ "league": {
87
+ "id": 135,
88
+ "name": "Serie A",
89
+ "country": "Italy",
90
+ "logo": "https://media.api-sports.io/football/leagues/135.png",
91
+ "round": "Regular Season - 38"
92
+ },
93
+ "teams": {
94
+ "home": { "id": 489, "name": "AC Milan", "logo": "https://media.api-sports.io/football/teams/489.png", "winner": false },
95
+ "away": { "id": 505, "name": "Inter", "logo": "https://media.api-sports.io/football/teams/505.png", "winner": true }
96
+ },
97
+ "goals": { "home": 0, "away": 2 },
98
+ "score": {
99
+ "halftime": { "home": 0, "away": 1 },
100
+ "fulltime": { "home": 0, "away": 2 }
101
+ }
102
+ }
103
+ ]
104
+ },
105
+ "updatedAt": 1750608000000,
106
+ "__typename": "FootballAppService"
107
+ }
108
+ }
109
+ }