various improvements to make release 1 complete
This commit is contained in:
@@ -12,6 +12,35 @@
|
||||
color: #23282d;
|
||||
}
|
||||
|
||||
/* Form actions with buttons */
|
||||
.swi-foot-form-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.swi-foot-form-actions button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Connection status messages */
|
||||
#connection-status {
|
||||
display: inline-block;
|
||||
margin-left: 15px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
#connection-status.success {
|
||||
color: #046b3a;
|
||||
}
|
||||
|
||||
#connection-status.error {
|
||||
color: #d63638;
|
||||
}
|
||||
|
||||
/* Teams grid layout */
|
||||
.swi-foot-teams-grid {
|
||||
display: grid;
|
||||
|
||||
+96
-37
@@ -2,6 +2,96 @@
|
||||
jQuery(document).ready(function($) {
|
||||
'use strict';
|
||||
|
||||
// Settings form with Save and Test functionality
|
||||
$('#swi-foot-settings-form').on('submit', function(e) {
|
||||
var $form = $(this);
|
||||
var $status = $('#connection-status');
|
||||
var action = $('[name="swi_foot_action"]:focus').val() || 'save_only';
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
// Get form data
|
||||
var formData = new FormData($form[0]);
|
||||
|
||||
// Submit form via WordPress options.php
|
||||
fetch($form.attr('action'), {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
body: formData
|
||||
}).then(function(resp) {
|
||||
// After form saves successfully, test if requested
|
||||
if (action === 'save_and_test') {
|
||||
return test_api_connection($status);
|
||||
} else {
|
||||
$('<div class="notice notice-info is-dismissible"><p>Settings saved!</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
$('<div class="notice notice-error is-dismissible"><p>Error saving settings: ' + err.message + '</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
});
|
||||
});
|
||||
|
||||
// Function to test API connection
|
||||
function test_api_connection($statusElement) {
|
||||
// Get current field values from form
|
||||
var apiUrl = $('input[name="swi_foot_api_base_url"]').val();
|
||||
var username = $('input[name="swi_foot_api_username"]').val();
|
||||
var password = $('input[name="swi_foot_api_password"]').val();
|
||||
|
||||
// Validate credentials first
|
||||
if (!username || !password) {
|
||||
$('<div class="notice notice-error is-dismissible"><p>API credentials not configured. Please configure Username and Password.</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!apiUrl) {
|
||||
$('<div class="notice notice-error is-dismissible"><p>API Base URL not configured.</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 1: Check if URL is reachable with a HEAD request
|
||||
var urlTest = apiUrl.replace(/\/$/, '') + '/';
|
||||
fetch(urlTest, {
|
||||
method: 'HEAD',
|
||||
credentials: 'same-origin',
|
||||
mode: 'no-cors'
|
||||
}).then(function(resp) {
|
||||
// Step 2: Make actual API call via REST endpoint
|
||||
return fetch(swi_foot_ajax.rest_url.replace(/\/$/, '') + '/admin/test-connection', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-WP-Nonce': swi_foot_ajax.rest_nonce
|
||||
},
|
||||
body: JSON.stringify({ test: true })
|
||||
});
|
||||
}).then(function(resp) {
|
||||
return resp.json();
|
||||
}).then(function(response) {
|
||||
if (response && response.success) {
|
||||
$('<div class="notice notice-success is-dismissible"><p>Settings saved and connection test successful!</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
} else {
|
||||
var errorMsg = (response.error || 'Connection failed');
|
||||
var details = response.details ? ': ' + response.details : '';
|
||||
$('<div class="notice notice-error is-dismissible"><p>' + errorMsg + details + '</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
}
|
||||
}).catch(function(err) {
|
||||
$('<div class="notice notice-error is-dismissible"><p>API URL not reachable: ' + apiUrl + '</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
});
|
||||
}
|
||||
|
||||
// Handle button clicks to set which action was used
|
||||
$('button[name="swi_foot_action"]').on('click', function() {
|
||||
$(this).focus();
|
||||
});
|
||||
|
||||
// Refresh teams functionality
|
||||
$('#refresh-teams').on('click', function() {
|
||||
var $button = $(this);
|
||||
@@ -18,13 +108,13 @@ jQuery(document).ready(function($) {
|
||||
return resp.json();
|
||||
}).then(function(response) {
|
||||
if (response && response.success) {
|
||||
$status.text('Teams refreshed successfully!').addClass('success');
|
||||
$status.text('✓ Teams refreshed successfully!').addClass('success');
|
||||
setTimeout(function() { location.reload(); }, 1500);
|
||||
} else {
|
||||
$status.text('Error: ' + (response.error || 'Unknown')).addClass('error');
|
||||
$status.text('✗ Error: ' + (response.error || 'Unknown')).addClass('error');
|
||||
}
|
||||
}).catch(function() {
|
||||
$status.text('Network error occurred.').addClass('error');
|
||||
$status.text('✗ Network error occurred.').addClass('error');
|
||||
}).finally(function() {
|
||||
$button.prop('disabled', false).text('Refresh Teams List');
|
||||
});
|
||||
@@ -48,47 +138,16 @@ jQuery(document).ready(function($) {
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': swi_foot_ajax.rest_nonce }
|
||||
}).then(function(resp) { return resp.json(); }).then(function(response) {
|
||||
if (response && response.success) {
|
||||
$status.text('Cache cleared successfully!').addClass('success');
|
||||
$status.text('✓ Cache cleared successfully!').addClass('success');
|
||||
setTimeout(function() { location.reload(); }, 1000);
|
||||
} else {
|
||||
$status.text('Error clearing cache.').addClass('error');
|
||||
$status.text('✗ Error clearing cache.').addClass('error');
|
||||
}
|
||||
}).catch(function() {
|
||||
$status.text('Error clearing cache.').addClass('error');
|
||||
$status.text('✗ Error clearing cache.').addClass('error');
|
||||
}).finally(function() {
|
||||
$button.prop('disabled', false);
|
||||
});
|
||||
});
|
||||
|
||||
// Test API connection
|
||||
$('#test-connection').on('click', function() {
|
||||
var $button = $(this);
|
||||
var $status = $('#connection-status');
|
||||
|
||||
$button.prop('disabled', true).text('Testing...');
|
||||
$status.text('').removeClass('success error');
|
||||
|
||||
fetch(swi_foot_ajax.rest_url.replace(/\/$/, '') + '/admin/test-connection', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': swi_foot_ajax.rest_nonce }
|
||||
}).then(function(resp) { return resp.json(); }).then(function(response) {
|
||||
if (response && response.success) {
|
||||
$status.text('Connection successful!').addClass('success');
|
||||
} else {
|
||||
$status.text('Connection failed: ' + (response.error || 'Unknown')).addClass('error');
|
||||
}
|
||||
}).catch(function() {
|
||||
$status.text('Network error occurred.').addClass('error');
|
||||
}).finally(function() {
|
||||
$button.prop('disabled', false).text('Test API Connection');
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-save settings notice
|
||||
$('form').on('submit', function() {
|
||||
$('<div class="notice notice-info is-dismissible"><p>Settings saved! The plugin will automatically refresh API tokens as needed.</p></div>')
|
||||
.insertAfter('.wrap h1');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
+71
-1
@@ -143,7 +143,7 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Error messages */
|
||||
/* Error messages - for configuration/setup issues */
|
||||
.swi-foot-error {
|
||||
color: #721c24;
|
||||
padding: 12px 16px;
|
||||
@@ -154,6 +154,39 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Warning messages - yellow style for "data not yet available" (406) */
|
||||
.swi-foot-data-warning {
|
||||
color: #856404;
|
||||
padding: 12px 16px;
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 6px;
|
||||
margin: 15px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Unavailable data messages - grey style for "no data available" */
|
||||
.swi-foot-data-unavailable {
|
||||
color: #6c757d;
|
||||
padding: 12px 16px;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
margin: 15px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Notice messages - informational style (deprecated, use data-warning or data-unavailable) */
|
||||
.swi-foot-notice {
|
||||
color: #6c757d;
|
||||
padding: 12px 16px;
|
||||
background-color: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
margin: 15px 0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.swi-foot-table {
|
||||
@@ -379,3 +412,40 @@
|
||||
max-height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Roster and Bench Player Formatting */
|
||||
|
||||
.swi-foot-position {
|
||||
color: #6c757d;
|
||||
font-size: 0.9em;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.swi-foot-role {
|
||||
color: #6c757d;
|
||||
font-size: 0.9em;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.roster-player-captain {
|
||||
font-weight: 500;
|
||||
color: #d4a017;
|
||||
}
|
||||
|
||||
.swi-foot-captain-badge {
|
||||
display: inline-block;
|
||||
background-color: #d4a017;
|
||||
color: #fff;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
margin-right: 4px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,653 @@
|
||||
// Swiss Football Gutenberg Blocks - Refactored User Flow
|
||||
(function (blocks, element, editor, components, i18n, data) {
|
||||
'use strict';
|
||||
|
||||
const { registerBlockType } = blocks;
|
||||
const { createElement: el, useState, useEffect } = element;
|
||||
const { InspectorControls } = editor;
|
||||
const { PanelBody, SelectControl, TextControl, ToggleControl, Spinner, RangeControl } = components;
|
||||
const { __ } = i18n;
|
||||
|
||||
// Helper function to make AJAX calls
|
||||
function makeAjaxCall(action, data = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jQuery.post(swiFootEditorData.ajax_url, {
|
||||
action: action,
|
||||
nonce: swiFootEditorData.nonce,
|
||||
...data
|
||||
}, function (response) {
|
||||
if (response && response.success) {
|
||||
resolve(response);
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
}).fail(reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Main Swiss Football Block - Refactored Logic
|
||||
registerBlockType('swi-foot/team-data', {
|
||||
title: __('Swiss Football Team Data', 'swi_foot_matchdata'),
|
||||
description: __('Display team standings or match data with guided selection', 'swi_foot_matchdata'),
|
||||
icon: 'universal-access-alt',
|
||||
category: 'widgets',
|
||||
keywords: [
|
||||
__('football', 'swi_foot_matchdata'),
|
||||
__('soccer', 'swi_foot_matchdata'),
|
||||
__('team', 'swi_foot_matchdata'),
|
||||
__('swiss', 'swi_foot_matchdata'),
|
||||
],
|
||||
|
||||
attributes: {
|
||||
selectedTeam: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
dataType: {
|
||||
type: 'string',
|
||||
default: '' // 'match' or 'stats'
|
||||
},
|
||||
selectedMatch: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
shortcodeType: {
|
||||
type: 'string',
|
||||
default: 'match'
|
||||
},
|
||||
format: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
separator: {
|
||||
type: 'string',
|
||||
default: ':'
|
||||
}
|
||||
},
|
||||
|
||||
edit: function (props) {
|
||||
const { attributes, setAttributes } = props;
|
||||
const { selectedTeam, dataType, selectedMatch, shortcodeType, format, separator } = attributes;
|
||||
|
||||
// State for dynamic data
|
||||
const [teams, setTeams] = useState([]);
|
||||
const [matches, setMatches] = useState([]);
|
||||
const [loadingTeams, setLoadingTeams] = useState(true);
|
||||
const [loadingMatches, setLoadingMatches] = useState(false);
|
||||
|
||||
// Load teams on component mount
|
||||
useEffect(() => {
|
||||
makeAjaxCall('swi_foot_get_teams_for_editor')
|
||||
.then(response => {
|
||||
if (response.success && response.data) {
|
||||
setTeams(response.data);
|
||||
}
|
||||
setLoadingTeams(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoadingTeams(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
// Load matches when team is selected and dataType is 'match'
|
||||
useEffect(() => {
|
||||
if (selectedTeam && dataType === 'match') {
|
||||
setLoadingMatches(true);
|
||||
makeAjaxCall('swi_foot_get_matches_for_team', { team_id: selectedTeam })
|
||||
.then(response => {
|
||||
if (response.success && response.data) {
|
||||
setMatches(response.data);
|
||||
}
|
||||
setLoadingMatches(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoadingMatches(false);
|
||||
});
|
||||
} else {
|
||||
setMatches([]);
|
||||
}
|
||||
}, [selectedTeam, dataType]);
|
||||
|
||||
// Build shortcode/content preview
|
||||
let content = '';
|
||||
if (selectedTeam && dataType === 'stats') {
|
||||
content = '[swi_foot_standings team_id="' + selectedTeam + '"]';
|
||||
} else if (selectedTeam && dataType === 'match' && selectedMatch) {
|
||||
if (selectedMatch === 'current') {
|
||||
content = '[swi_foot_' + shortcodeType + ' team_id="' + selectedTeam + '" show_current="true"';
|
||||
} else {
|
||||
content = '[swi_foot_' + shortcodeType + ' match_id="' + selectedMatch + '"';
|
||||
}
|
||||
|
||||
if (format && (shortcodeType === 'match_date' || shortcodeType === 'match_time')) {
|
||||
content += ' format="' + format + '"';
|
||||
}
|
||||
if (separator !== ':' && shortcodeType === 'match_score') {
|
||||
content += ' separator="' + separator + '"';
|
||||
}
|
||||
content += ']';
|
||||
}
|
||||
|
||||
// Data type options
|
||||
const dataTypeOptions = [
|
||||
{ value: '', label: __('Choose what to display...', 'swi_foot_matchdata') },
|
||||
{ value: 'stats', label: __('Team Statistics (Standings/Ranking)', 'swi_foot_matchdata') },
|
||||
{ value: 'match', label: __('Match Data', 'swi_foot_matchdata') }
|
||||
];
|
||||
|
||||
// Match data type options
|
||||
const matchDataOptions = [
|
||||
{ value: 'match', label: __('Complete Match Information', 'swi_foot_matchdata') },
|
||||
{ value: 'match_home_team', label: __('Home Team Name Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_away_team', label: __('Away Team Name Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_date', label: __('Match Date Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_time', label: __('Match Time Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_venue', label: __('Venue/Location Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_score', label: __('Score Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_status', label: __('Match Status Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_league', label: __('League Name Only', 'swi_foot_matchdata') },
|
||||
{ value: 'match_round', label: __('Round Number Only', 'swi_foot_matchdata') }
|
||||
];
|
||||
|
||||
return el('div', { className: 'swi-foot-team-data-block' },
|
||||
el(InspectorControls, {},
|
||||
el(PanelBody, { title: __('Team Selection', 'swi_foot_matchdata') },
|
||||
// Step 1: Team selection
|
||||
loadingTeams ? el(Spinner) : el(SelectControl, {
|
||||
label: __('1. Select Team', 'swi_foot_matchdata'),
|
||||
value: selectedTeam,
|
||||
options: [{ value: '', label: __('Choose a team...', 'swi_foot_matchdata') }].concat(
|
||||
teams.map(team => ({
|
||||
value: team.value,
|
||||
label: team.label
|
||||
}))
|
||||
),
|
||||
onChange: function (value) {
|
||||
setAttributes({
|
||||
selectedTeam: value,
|
||||
dataType: '', // Reset when team changes
|
||||
selectedMatch: ''
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
// Step 2: Data type selection (only show if team is selected)
|
||||
selectedTeam && el(SelectControl, {
|
||||
label: __('2. What would you like to display?', 'swi_foot_matchdata'),
|
||||
value: dataType,
|
||||
options: dataTypeOptions,
|
||||
onChange: function (value) {
|
||||
setAttributes({
|
||||
dataType: value,
|
||||
selectedMatch: '' // Reset match when data type changes
|
||||
});
|
||||
}
|
||||
})
|
||||
),
|
||||
|
||||
// Match-specific settings
|
||||
dataType === 'match' && el(PanelBody, { title: __('Match Settings', 'swi_foot_matchdata') },
|
||||
// Match selection
|
||||
loadingMatches ? el(Spinner) : el(SelectControl, {
|
||||
label: __('3. Select Match', 'swi_foot_matchdata'),
|
||||
value: selectedMatch,
|
||||
options: [{ value: '', label: __('Choose a match...', 'swi_foot_matchdata') }].concat(
|
||||
[{ value: 'current', label: __('Current Match (Next upcoming or recent)', 'swi_foot_matchdata') }],
|
||||
matches.map(match => ({
|
||||
value: match.value,
|
||||
label: match.label
|
||||
}))
|
||||
),
|
||||
onChange: function (value) { setAttributes({ selectedMatch: value }); }
|
||||
}),
|
||||
|
||||
// Match data type (what part of match to show)
|
||||
selectedMatch && el(SelectControl, {
|
||||
label: __('4. What match information to display?', 'swi_foot_matchdata'),
|
||||
value: shortcodeType,
|
||||
options: matchDataOptions,
|
||||
onChange: function (value) { setAttributes({ shortcodeType: value }); }
|
||||
})
|
||||
),
|
||||
|
||||
// Advanced options for match data
|
||||
dataType === 'match' && selectedMatch && el(PanelBody, { title: __('Advanced Options', 'swi_foot_matchdata'), initialOpen: false },
|
||||
(shortcodeType === 'match_date' || shortcodeType === 'match_time') && el(SelectControl, {
|
||||
label: __('Date/Time Format', 'swi_foot_matchdata'),
|
||||
value: format,
|
||||
options: shortcodeType === 'match_date' ? [
|
||||
{ value: '', label: __('Default (WordPress setting)', 'swi_foot_matchdata') },
|
||||
{ value: 'd.m.Y', label: '31.12.2024 (European)' },
|
||||
{ value: 'm/d/Y', label: '12/31/2024 (US)' },
|
||||
{ value: 'F j, Y', label: 'December 31, 2024' },
|
||||
{ value: 'D, M j', label: 'Tue, Dec 31' }
|
||||
] : [
|
||||
{ value: '', label: __('Default (WordPress setting)', 'swi_foot_matchdata') },
|
||||
{ value: 'H:i', label: '14:30 (24-hour)' },
|
||||
{ value: 'g:i A', label: '2:30 PM (12-hour)' },
|
||||
{ value: 'g:i', label: '2:30 (12-hour no AM/PM)' }
|
||||
],
|
||||
onChange: function (value) { setAttributes({ format: value }); }
|
||||
}),
|
||||
|
||||
shortcodeType === 'match_score' && el(SelectControl, {
|
||||
label: __('Score Separator', 'swi_foot_matchdata'),
|
||||
value: separator,
|
||||
options: [
|
||||
{ value: ':', label: '2 : 1 (colon with spaces)' },
|
||||
{ value: '-', label: '2 - 1 (dash with spaces)' },
|
||||
{ value: ' vs ', label: '2 vs 1 (versus)' },
|
||||
{ value: '|', label: '2 | 1 (pipe)' }
|
||||
],
|
||||
onChange: function (value) { setAttributes({ separator: value }); }
|
||||
})
|
||||
)
|
||||
),
|
||||
|
||||
// Main preview area
|
||||
el('div', { className: 'swi-foot-preview' },
|
||||
el('div', {
|
||||
style: {
|
||||
padding: '30px',
|
||||
textAlign: 'center',
|
||||
background: '#f8f9fa',
|
||||
border: '2px dashed #ddd',
|
||||
borderRadius: '8px'
|
||||
}
|
||||
},
|
||||
el('h4', { style: { marginTop: 0 } }, __('Swiss Football Team Data', 'swi_foot_matchdata')),
|
||||
|
||||
!selectedTeam && el('p', { style: { color: '#666' } },
|
||||
__('1. Please select a team to get started', 'swi_foot_matchdata')
|
||||
),
|
||||
|
||||
selectedTeam && !dataType && el('p', { style: { color: '#666' } },
|
||||
__('2. Please choose what you want to display', 'swi_foot_matchdata')
|
||||
),
|
||||
|
||||
selectedTeam && dataType === 'stats' && el('p', { style: { color: '#28a745' } },
|
||||
'✓ ' + __('Will display team standings/ranking', 'swi_foot_matchdata')
|
||||
),
|
||||
|
||||
selectedTeam && dataType === 'match' && !selectedMatch && el('p', { style: { color: '#666' } },
|
||||
__('3. Please select a match', 'swi_foot_matchdata')
|
||||
),
|
||||
|
||||
selectedTeam && dataType === 'match' && selectedMatch && el('div', {},
|
||||
el('p', { style: { color: '#28a745' } },
|
||||
'✓ ' + __('Ready to display match data', 'swi_foot_matchdata')
|
||||
),
|
||||
el('p', { style: { fontSize: '14px', color: '#666' } },
|
||||
__('Generated shortcode:', 'swi_foot_matchdata')
|
||||
),
|
||||
el('code', {
|
||||
style: {
|
||||
display: 'block',
|
||||
padding: '10px',
|
||||
background: '#fff',
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '4px',
|
||||
fontFamily: 'Monaco, Menlo, monospace',
|
||||
fontSize: '12px',
|
||||
wordBreak: 'break-all'
|
||||
}
|
||||
}, content)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
save: function (props) {
|
||||
const { attributes } = props;
|
||||
const { selectedTeam, dataType, selectedMatch, shortcodeType, format, separator } = attributes;
|
||||
|
||||
// Build shortcode for saving
|
||||
let shortcode = '';
|
||||
if (selectedTeam && dataType === 'stats') {
|
||||
shortcode = '[swi_foot_standings team_id="' + selectedTeam + '"]';
|
||||
} else if (selectedTeam && dataType === 'match' && selectedMatch) {
|
||||
if (selectedMatch === 'current') {
|
||||
shortcode = '[swi_foot_' + shortcodeType + ' team_id="' + selectedTeam + '" show_current="true"';
|
||||
} else {
|
||||
shortcode = '[swi_foot_' + shortcodeType + ' match_id="' + selectedMatch + '"';
|
||||
}
|
||||
|
||||
if (format && (shortcodeType === 'match_date' || shortcodeType === 'match_time')) {
|
||||
shortcode += ' format="' + format + '"';
|
||||
}
|
||||
if (separator !== ':' && shortcodeType === 'match_score') {
|
||||
shortcode += ' separator="' + separator + '"';
|
||||
}
|
||||
shortcode += ']';
|
||||
}
|
||||
|
||||
return shortcode;
|
||||
}
|
||||
});
|
||||
|
||||
// Match Roster Block with Side Selector and Bench Option
|
||||
registerBlockType('swi-foot/match-roster', {
|
||||
title: __('Swiss Football Match Roster', 'swi_foot_matchdata'),
|
||||
description: __('Display match roster for home or away team, with optional bench players', 'swi_foot_matchdata'),
|
||||
icon: 'groups',
|
||||
category: 'widgets',
|
||||
keywords: [
|
||||
__('roster', 'swi_foot_matchdata'),
|
||||
__('players', 'swi_foot_matchdata'),
|
||||
__('lineup', 'swi_foot_matchdata'),
|
||||
__('team', 'swi_foot_matchdata'),
|
||||
],
|
||||
|
||||
attributes: {
|
||||
selectedTeam: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
selectedMatch: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
side: {
|
||||
type: 'string',
|
||||
default: 'home' // home or away
|
||||
},
|
||||
withBench: {
|
||||
type: 'boolean',
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
edit: function (props) {
|
||||
const { attributes, setAttributes } = props;
|
||||
const { selectedTeam, selectedMatch, side, withBench } = attributes;
|
||||
|
||||
const [teams, setTeams] = wp.element.useState([]);
|
||||
const [matches, setMatches] = wp.element.useState([]);
|
||||
const [loadingTeams, setLoadingTeams] = wp.element.useState(true);
|
||||
const [loadingMatches, setLoadingMatches] = wp.element.useState(false);
|
||||
|
||||
// Helper AJAX call
|
||||
function makeAjaxCall(action, data = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
jQuery.post(swiFootEditorData.ajax_url, {
|
||||
action: action,
|
||||
nonce: swiFootEditorData.nonce,
|
||||
...data
|
||||
}, function (response) {
|
||||
if (response && response.success) {
|
||||
resolve(response);
|
||||
} else {
|
||||
reject(response);
|
||||
}
|
||||
}).fail(reject);
|
||||
});
|
||||
}
|
||||
|
||||
// Load teams
|
||||
wp.element.useEffect(() => {
|
||||
setLoadingTeams(true);
|
||||
makeAjaxCall('swi_foot_get_teams_for_editor')
|
||||
.then(response => {
|
||||
if (Array.isArray(response.data)) {
|
||||
setTeams(response.data);
|
||||
}
|
||||
setLoadingTeams(false);
|
||||
})
|
||||
.catch(() => setLoadingTeams(false));
|
||||
}, []);
|
||||
|
||||
// Load matches when team changes
|
||||
wp.element.useEffect(() => {
|
||||
if (selectedTeam) {
|
||||
setLoadingMatches(true);
|
||||
makeAjaxCall('swi_foot_get_matches_for_team', { team_id: selectedTeam })
|
||||
.then(response => {
|
||||
if (Array.isArray(response.data)) {
|
||||
setMatches(response.data);
|
||||
}
|
||||
setLoadingMatches(false);
|
||||
})
|
||||
.catch(() => setLoadingMatches(false));
|
||||
}
|
||||
}, [selectedTeam]);
|
||||
|
||||
return el('div', { className: 'swi-foot-roster-block' },
|
||||
el(InspectorControls, {},
|
||||
el(PanelBody, { title: __('Match Selection', 'swi_foot_matchdata') },
|
||||
|
||||
// Team selector
|
||||
loadingTeams ? el(Spinner) : el(SelectControl, {
|
||||
label: __('Select Team', 'swi_foot_matchdata'),
|
||||
value: selectedTeam,
|
||||
options: [{ value: '', label: __('Choose a team...', 'swi_foot_matchdata') }].concat(
|
||||
teams.map(team => ({ value: team.value, label: team.label }))
|
||||
),
|
||||
onChange: function (value) {
|
||||
setAttributes({ selectedTeam: value, selectedMatch: '' });
|
||||
}
|
||||
}),
|
||||
|
||||
// Match selector (shows after team is selected)
|
||||
selectedTeam && (loadingMatches ? el(Spinner) : el(SelectControl, {
|
||||
label: __('Select Match', 'swi_foot_matchdata'),
|
||||
value: selectedMatch,
|
||||
options: [{ value: '', label: __('Choose a match...', 'swi_foot_matchdata') }].concat(
|
||||
[{ value: 'current', label: __('Current Match', 'swi_foot_matchdata') }],
|
||||
matches.map(match => ({ value: match.value, label: match.label }))
|
||||
),
|
||||
onChange: function (value) { setAttributes({ selectedMatch: value }); }
|
||||
})),
|
||||
|
||||
// Side selector (Home/Away)
|
||||
selectedMatch && el(SelectControl, {
|
||||
label: __('Select Side', 'swi_foot_matchdata'),
|
||||
value: side,
|
||||
options: [
|
||||
{ value: 'home', label: __('Home Team', 'swi_foot_matchdata') },
|
||||
{ value: 'away', label: __('Away Team', 'swi_foot_matchdata') }
|
||||
],
|
||||
onChange: function (value) { setAttributes({ side: value }); }
|
||||
}),
|
||||
|
||||
// Include bench toggle
|
||||
selectedMatch && el(ToggleControl, {
|
||||
label: __('Include Bench Players', 'swi_foot_matchdata'),
|
||||
help: __('When enabled, bench players will also be displayed', 'swi_foot_matchdata'),
|
||||
checked: withBench,
|
||||
onChange: function (value) { setAttributes({ withBench: value }); }
|
||||
})
|
||||
)
|
||||
),
|
||||
|
||||
// Block preview
|
||||
el('div', {
|
||||
style: {
|
||||
padding: '30px',
|
||||
textAlign: 'center',
|
||||
background: '#f8f9fa',
|
||||
border: '2px dashed #ddd',
|
||||
borderRadius: '8px'
|
||||
}
|
||||
},
|
||||
el('h4', { style: { marginTop: 0 } }, __('Match Roster', 'swi_foot_matchdata')),
|
||||
(!selectedTeam || !selectedMatch) ?
|
||||
el('p', { style: { color: '#666' } },
|
||||
__('Please select team, match, side, and bench option', 'swi_foot_matchdata')
|
||||
)
|
||||
:
|
||||
el('p', { style: { color: '#28a745' } },
|
||||
'✓ ' + __('Roster will be displayed for ' + side + ' team' + (withBench ? ' (with bench)' : ''), 'swi_foot_matchdata')
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
save: function (props) {
|
||||
const { selectedTeam, selectedMatch, side, withBench } = props.attributes;
|
||||
if (!selectedTeam || !selectedMatch || !side) return '';
|
||||
|
||||
let shortcode = '[swi_foot_roster ';
|
||||
if (selectedMatch === 'current') {
|
||||
shortcode += 'team_id="' + selectedTeam + '" show_current="true"';
|
||||
} else {
|
||||
shortcode += 'match_id="' + selectedMatch + '"';
|
||||
}
|
||||
shortcode += ' side="' + side + '"';
|
||||
if (withBench) {
|
||||
shortcode += ' with_bench="true"';
|
||||
}
|
||||
shortcode += ']';
|
||||
|
||||
return shortcode;
|
||||
}
|
||||
});
|
||||
|
||||
// Match Events Block
|
||||
registerBlockType('swi-foot/match-events', {
|
||||
title: __('Swiss Football Match Events', 'swi_foot_matchdata'),
|
||||
description: __('Display live match events with auto-refresh', 'swi_foot_matchdata'),
|
||||
icon: 'list-view',
|
||||
category: 'widgets',
|
||||
keywords: [
|
||||
__('events', 'swi_foot_matchdata'),
|
||||
__('live', 'swi_foot_matchdata'),
|
||||
__('timeline', 'swi_foot_matchdata'),
|
||||
__('match', 'swi_foot_matchdata'),
|
||||
],
|
||||
|
||||
attributes: {
|
||||
selectedTeam: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
selectedMatch: {
|
||||
type: 'string',
|
||||
default: ''
|
||||
},
|
||||
refreshInterval: {
|
||||
type: 'number',
|
||||
default: 30
|
||||
}
|
||||
},
|
||||
|
||||
edit: function (props) {
|
||||
const { attributes, setAttributes } = props;
|
||||
const { selectedTeam, selectedMatch, refreshInterval } = attributes;
|
||||
|
||||
const [teams, setTeams] = useState([]);
|
||||
const [matches, setMatches] = useState([]);
|
||||
const [loadingTeams, setLoadingTeams] = useState(true);
|
||||
const [loadingMatches, setLoadingMatches] = useState(false);
|
||||
|
||||
// Load teams
|
||||
useEffect(() => {
|
||||
makeAjaxCall('swi_foot_get_teams_for_editor')
|
||||
.then(response => {
|
||||
if (response.success && response.data) {
|
||||
setTeams(response.data);
|
||||
}
|
||||
setLoadingTeams(false);
|
||||
})
|
||||
.catch(() => setLoadingTeams(false));
|
||||
}, []);
|
||||
|
||||
// Load matches when team changes
|
||||
useEffect(() => {
|
||||
if (selectedTeam) {
|
||||
setLoadingMatches(true);
|
||||
makeAjaxCall('swi_foot_get_matches_for_team', { team_id: selectedTeam })
|
||||
.then(response => {
|
||||
if (response.success && response.data) {
|
||||
setMatches(response.data);
|
||||
}
|
||||
setLoadingMatches(false);
|
||||
})
|
||||
.catch(() => setLoadingMatches(false));
|
||||
}
|
||||
}, [selectedTeam]);
|
||||
|
||||
return el('div', { className: 'swi-foot-events-block' },
|
||||
el(InspectorControls, {},
|
||||
el(PanelBody, { title: __('Match Selection', 'swi_foot_matchdata') },
|
||||
loadingTeams ? el(Spinner) : el(SelectControl, {
|
||||
label: __('Select Team', 'swi_foot_matchdata'),
|
||||
value: selectedTeam,
|
||||
options: [{ value: '', label: __('Choose a team...', 'swi_foot_matchdata') }].concat(
|
||||
teams.map(team => ({ value: team.value, label: team.label }))
|
||||
),
|
||||
onChange: function (value) {
|
||||
setAttributes({ selectedTeam: value, selectedMatch: '' });
|
||||
}
|
||||
}),
|
||||
|
||||
selectedTeam && (loadingMatches ? el(Spinner) : el(SelectControl, {
|
||||
label: __('Select Match', 'swi_foot_matchdata'),
|
||||
value: selectedMatch,
|
||||
options: [{ value: '', label: __('Choose a match...', 'swi_foot_matchdata') }].concat(
|
||||
[{ value: 'current', label: __('Current Match', 'swi_foot_matchdata') }],
|
||||
matches.map(match => ({ value: match.value, label: match.label }))
|
||||
),
|
||||
onChange: function (value) { setAttributes({ selectedMatch: value }); }
|
||||
}))
|
||||
),
|
||||
|
||||
el(PanelBody, { title: __('Auto-Refresh Settings', 'swi_foot_matchdata') },
|
||||
el(RangeControl, {
|
||||
label: __('Refresh Interval (seconds)', 'swi_foot_matchdata'),
|
||||
value: refreshInterval,
|
||||
onChange: function (value) { setAttributes({ refreshInterval: value }); },
|
||||
min: 10,
|
||||
max: 300,
|
||||
step: 10
|
||||
})
|
||||
)
|
||||
),
|
||||
|
||||
el('div', {
|
||||
style: {
|
||||
padding: '30px',
|
||||
textAlign: 'center',
|
||||
background: '#f8f9fa',
|
||||
border: '2px dashed #ddd',
|
||||
borderRadius: '8px'
|
||||
}
|
||||
},
|
||||
el('h4', { style: { marginTop: 0 } }, __('Live Match Events', 'swi_foot_matchdata')),
|
||||
(!selectedTeam || !selectedMatch) ?
|
||||
el('p', { style: { color: '#666' } }, __('Please select team and match', 'swi_foot_matchdata')) :
|
||||
el('div', {},
|
||||
el('p', { style: { color: '#28a745' } }, '✓ ' + __('Will display live match events', 'swi_foot_matchdata')),
|
||||
el('p', { style: { fontSize: '12px', color: '#666' } },
|
||||
__('Auto-refresh every', 'swi_foot_matchdata') + ' ' + refreshInterval + ' ' + __('seconds', 'swi_foot_matchdata'))
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
save: function (props) {
|
||||
const { attributes } = props;
|
||||
const { selectedTeam, selectedMatch, refreshInterval } = attributes;
|
||||
|
||||
if (!selectedTeam || !selectedMatch) return '';
|
||||
|
||||
let shortcode = '[swi_foot_events ';
|
||||
if (selectedMatch === 'current') {
|
||||
shortcode += 'team_id="' + selectedTeam + '" show_current="true"';
|
||||
} else {
|
||||
shortcode += 'match_id="' + selectedMatch + '"';
|
||||
}
|
||||
shortcode += ' refresh_interval="' + refreshInterval + '"]';
|
||||
|
||||
return shortcode;
|
||||
}
|
||||
});
|
||||
|
||||
})(
|
||||
window.wp.blocks,
|
||||
window.wp.element,
|
||||
window.wp.blockEditor,
|
||||
window.wp.components,
|
||||
window.wp.i18n,
|
||||
window.wp.data
|
||||
);
|
||||
Reference in New Issue
Block a user