HEX
Server: LiteSpeed
System: Linux prometheus.hongkongserver.net 4.18.0-553.134.1.el8_10.x86_64 #1 SMP Tue Jun 16 16:05:57 EDT 2026 x86_64
User: ayxmplky (1112)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: /home/ayxmplky/public_html/wp-content/themes/tactic/inc/admin-translations.php
<?php
/**
 * WP Admin: страница управления переводами
 * WP Admin → TACTIC → Переводы
 *
 * @package tactic
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/* ── Меню в сайдбаре WP Admin ───────────────────────────────── */
function tactic_add_translations_menu(): void {
	add_menu_page(
		'TACTIC — Управление переводами',
		'TACTIC',
		'manage_options',
		'tactic-translations',
		'tactic_translations_page',
		'dashicons-translation',
		60
	);

	add_submenu_page(
		'tactic-translations',
		'TACTIC — Переводы (локали)',
		'Переводы (локали)',
		'manage_options',
		'tactic-translations',
		'tactic_translations_page'
	);

	add_submenu_page(
		'tactic-translations',
		'TACTIC — Слайдер Hero',
		'Слайдер Hero',
		'manage_options',
		'tactic-hero-slider',
		'tactic_hero_slider_page'
	);

	add_submenu_page(
		'tactic-translations',
		'TACTIC — About (главная)',
		'About (главная)',
		'manage_options',
		'tactic-about-home',
		'tactic_about_home_page'
	);

	add_submenu_page(
		'tactic-translations',
		'TACTIC — Плашки карты',
		'Плашки карты',
		'manage_options',
		'tactic-map-stats',
		'tactic_map_stats_page'
	);
}
add_action( 'admin_menu', 'tactic_add_translations_menu' );

/* ── Подключение media uploader для Hero Slider ─────────────── */
function tactic_admin_hero_slider_assets( string $hook ): void {
	if ( ! in_array( $hook, [ 'tactic_page_tactic-hero-slider', 'tactic_page_tactic-about-home', 'tactic_page_tactic-map-stats' ], true ) ) {
		return;
	}

	wp_enqueue_media();
}
add_action( 'admin_enqueue_scripts', 'tactic_admin_hero_slider_assets' );

/* ── Стили страницы ─────────────────────────────────────────── */
function tactic_translations_admin_styles( string $hook ): void {
	if ( $hook !== 'toplevel_page_tactic-translations' ) {
		return;
	}
	?>
	<style>
		.tactic-wrap { max-width: 1200px; }
		.tactic-wrap h1 { display: flex; align-items: center; gap: 10px; }
		.tactic-group { margin: 28px 0 0; }
		.tactic-group h2 { font-size: 15px; color: #1d2327; margin-bottom: 6px;
			padding: 8px 12px; background: #f0f0f1; border-left: 4px solid #2271b1; }
		.tactic-table { width: 100%; border-collapse: collapse; background: #fff;
			box-shadow: 0 1px 3px rgba(0,0,0,.08); margin-bottom: 4px; }
		.tactic-table th, .tactic-table td { padding: 10px 14px; border: 1px solid #e0e0e0; vertical-align: top; }
		.tactic-table th { background: #f6f7f7; font-weight: 600; font-size: 13px; white-space: nowrap; }
		.tactic-table td:first-child { width: 160px; color: #646970; font-family: monospace;
			font-size: 12px; background: #fafafa; }
		.tactic-table textarea { width: 100%; min-height: 52px; resize: vertical;
			font-size: 14px; line-height: 1.5; border: 1px solid #c3c4c7; border-radius: 3px;
			padding: 6px 8px; box-sizing: border-box; }
		.tactic-table textarea:focus { border-color: #2271b1; box-shadow: 0 0 0 1px #2271b1; outline: none; }
		.tactic-th-zh { background: #fff8e1 !important; }
		.tactic-th-en { background: #e8f4fd !important; }
		.tactic-td-zh textarea { background: #fffdf0; }
		.tactic-td-en textarea { background: #f0f8ff; }
		.tactic-save-bar { position: sticky; bottom: 0; background: #fff;
			padding: 14px 0; border-top: 1px solid #ddd; margin-top: 20px; z-index: 10; }
		.tactic-notice { background: #d1e7dd; border: 1px solid #a3cfbb;
			color: #0a3622; padding: 10px 16px; border-radius: 4px; margin: 12px 0; font-weight: 500; }
		.tactic-reset-link { float: right; font-size: 12px; color: #b32d2e; text-decoration: none; margin-top: 4px; }
		.tactic-reset-link:hover { text-decoration: underline; }
	</style>
	<?php
}
add_action( 'admin_head', 'tactic_translations_admin_styles' );

/* ── Основная страница ──────────────────────────────────────── */
function tactic_translations_page(): void {
	if ( ! current_user_can( 'manage_options' ) ) {
		return;
	}

	$saved_notice = '';

	/* Сохранение */
	if ( isset( $_POST['tactic_translations_nonce'] )
		&& wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['tactic_translations_nonce'] ) ), 'tactic_save_translations' )
	) {
		$defaults = require TACTIC_DIR . '/inc/strings.php';
		$input    = isset( $_POST['tactic_s'] ) && is_array( $_POST['tactic_s'] ) ? $_POST['tactic_s'] : []; // phpcs:ignore WordPress.Security.NonceVerification
		$saved    = [];

		foreach ( $defaults as $key => $pair ) {
			$saved[ $key ] = [
				'zh' => sanitize_textarea_field( wp_unslash( $input[ $key ]['zh'] ?? $pair['zh'] ) ),
				'en' => sanitize_textarea_field( wp_unslash( $input[ $key ]['en'] ?? $pair['en'] ) ),
			];
		}

		update_option( 'tactic_translations', $saved );
		tactic_s( '__reset__' ); // сбрасываем статический кэш
		$saved_notice = '<div class="tactic-notice">✓ Переводы сохранены.</div>';
	}

	/* Сброс к умолчаниям */
	if ( isset( $_GET['tactic_reset'] ) && check_admin_referer( 'tactic_reset_translations' ) ) {
		delete_option( 'tactic_translations' );
		tactic_s( '__reset__' );
		$saved_notice = '<div class="tactic-notice">✓ Переводы сброшены к значениям по умолчанию.</div>';
	}

	$defaults  = require TACTIC_DIR . '/inc/strings.php';
	$overrides = (array) get_option( 'tactic_translations', [] );
	$strings   = array_replace_recursive( $defaults, $overrides );

	/* Группы */
	$groups = [
		'🎯 Главный баннер (Hero)'       => [ 'hero_title', 'hero_subtitle', 'hero_btn1', 'hero_btn2', 'hero_aria', 'hero_pagination_aria' ],
		'📄 Страница «О нас» — верхний блок' => [ 'abt_hero_title', 'abt_hero_subtitle', 'abt_lead', 'abt_p1', 'abt_p2', 'abt_p3', 'abt_p4' ],
		'🏭 О компании (About)'          => [ 'about_title', 'about_subtitle', 'about_p1', 'about_p2', 'about_p3', 'about_btn', 'about_tools_title', 'about_tools_subtitle', 'about_tools_list_aria', 'explore_tools_prev_aria', 'explore_tools_next_aria' ],
		'🌍 Глобальная карта (Map)'      => [ 'map_title', 'map_subtitle', 'map_cta', 'map_img_alt', 'map_stats_controls_aria', 'map_stats_prev_aria', 'map_stats_next_aria', 'stat1', 'stat2', 'stat3', 'stat4', 'stat5' ],
		'📰 Новости (News)'              => [ 'news_title', 'news_subtitle', 'news_all_btn', 'news_hero_title', 'news_hero_subtitle', 'archive_news_showcase_aria', 'archive_articles_aria', 'news_archive_articles_title', 'news_archive_articles_subtitle', 'news_single_breadcrumb_aria', 'news_breadcrumb_archive', 'news_article_list_aria', 'news_articles_title', 'news_prev_article_aria', 'news_next_article_aria', 'home_news_empty_featured_title', 'home_news_empty_list_title', 'home_news_empty_list_desc', 'common_read_more' ],
		'🛍 Product Line'                => [ 'product_line_title', 'product_empty_title', 'product_empty_desc' ],
		'🎬 Отзывы И Видео'              => [ 'testimonials_title', 'testimonials_subtitle', 'testimonials_view_more', 'testimonials_list_aria', 'testimonials_fallback_full_name', 'testimonials_fallback_position', 'testimonials_fallback_company', 'testimonials_empty_title', 'testimonials_empty_desc', 'testimonials_slider_nav_aria', 'video_modal_aria', 'video_modal_close_aria', 'common_play_video_aria' ],
		'📬 Футер (Footer)'              => [ 'footer_contacts', 'footer_email_eu', 'footer_email_global', 'footer_qr_alt', 'footer_charger_aria', 'footer_charger_alt', 'footer_instagram_aria', 'footer_wechat_aria', 'footer_linkedin_aria', 'footer_dnb_aria', 'footer_dnb_alt' ],
		'✉️ Страница Contact Us'         => [
			'contact_title', 'contact_qr_label', 'contact_qr_desc',
			'contact_qr_alt',
			'contact_form_title',
			'contact_field_name', 'contact_field_company', 'contact_field_country',
			'contact_field_email', 'contact_field_phone', 'contact_field_mobile', 'contact_field_address', 'contact_field_industry', 'contact_field_message',
			'contact_field_gender_male', 'contact_field_gender_female',
			'contact_gender_aria',
			'contact_submit', 'contact_success', 'contact_error', 'contact_security_error',
			'contact_required_name', 'contact_required_company', 'contact_required_mobile', 'contact_required_email',
			'contact_required_phone', 'contact_required_address', 'contact_required_gender', 'contact_required_message', 'contact_required_terms',
			'contact_invalid_email', 'contact_invalid_phone',
			'contact_modal_title', 'contact_modal_text', 'contact_modal_stay', 'contact_modal_go',
			'contact_office_cn', 'contact_phone_label', 'contact_postal_label', 'contact_email_cn_label', 'contact_email_eu_label',
			'contact_terms', 'contact_placeholder_email',
		],
		'🗺 Навигация'                   => [ 'nav_home', 'nav_about', 'nav_blog', 'nav_awards', 'nav_contact', 'nav_aria', 'menu_btn_aria', 'common_breadcrumb_aria', 'common_page_navigation_aria' ],
		'🧩 Общие Страницы'              => [ 'common_no_content', 'common_demo_section_in_dev', 'archive_date_format', 'archive_default_title', 'single_post_nav_aria', 'error_404_title', 'error_404_text', 'error_404_home' ],
	];

	$reset_url = wp_nonce_url(
		add_query_arg( 'tactic_reset', '1', menu_page_url( 'tactic-translations', false ) ),
		'tactic_reset_translations'
	);
	?>
	<div class="wrap tactic-wrap">
		<h1>
			<span class="dashicons dashicons-translation" style="font-size:26px;height:26px;"></span>
			TACTIC — Управление переводами
		</h1>
		<p style="color:#646970;">Все UI-тексты сайта в одном месте. Изменения применяются мгновенно без правки кода.</p>

		<a href="<?php echo esc_url( $reset_url ); ?>"
			class="tactic-reset-link"
			onclick="return confirm('Сбросить все переводы к значениям по умолчанию?')">
			↺ Сбросить к умолчаниям
		</a>

		<?php echo wp_kses_post( $saved_notice ); ?>

		<form method="post">
			<?php wp_nonce_field( 'tactic_save_translations', 'tactic_translations_nonce' ); ?>

			<?php foreach ( $groups as $group_label => $keys ) : ?>
			<div class="tactic-group">
				<h2><?php echo esc_html( $group_label ); ?></h2>
				<table class="tactic-table">
					<thead>
						<tr>
							<th>Ключ</th>
							<th class="tactic-th-zh">🇨🇳 Китайский (zh)</th>
							<th class="tactic-th-en">🇬🇧 Английский (en)</th>
						</tr>
					</thead>
					<tbody>
					<?php foreach ( $keys as $key ) : ?>
						<?php if ( ! isset( $strings[ $key ] ) ) : continue; endif; ?>
						<tr>
							<td><?php echo esc_html( $key ); ?></td>
							<td class="tactic-td-zh">
								<textarea name="tactic_s[<?php echo esc_attr( $key ); ?>][zh]"><?php echo esc_textarea( $strings[ $key ]['zh'] ); ?></textarea>
							</td>
							<td class="tactic-td-en">
								<textarea name="tactic_s[<?php echo esc_attr( $key ); ?>][en]"><?php echo esc_textarea( $strings[ $key ]['en'] ); ?></textarea>
							</td>
						</tr>
					<?php endforeach; ?>
					</tbody>
				</table>
			</div>
			<?php endforeach; ?>

			<div class="tactic-save-bar">
				<?php submit_button( 'Сохранить переводы', 'primary large', 'submit', false ); ?>
				<span style="color:#646970; margin-left: 12px; font-size:13px;">
					Активный язык сайта: <strong><?php echo esc_html( strtoupper( tactic_current_lang() ) ); ?></strong>
					(переключается по ?lang=zh / ?lang=en)
				</span>
			</div>

		</form>
	</div>
	<?php
}

/* ── Отдельная страница: Hero Slider ───────────────────────── */
function tactic_hero_slider_page(): void {
	if ( ! current_user_can( 'manage_options' ) ) {
		return;
	}

	$notice = '';

	if (
		isset( $_POST['tactic_hero_slider_nonce'] )
		&& wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['tactic_hero_slider_nonce'] ) ), 'tactic_save_hero_slider' )
	) {
		$raw_ids = isset( $_POST['tactic_hero_slide_ids'] ) ? (string) wp_unslash( $_POST['tactic_hero_slide_ids'] ) : '';
		$ids     = array_filter( array_map( 'absint', explode( ',', $raw_ids ) ) );
		update_option( 'tactic_hero_slider_ids', implode( ',', $ids ) );
		$notice = '<div class="notice notice-success is-dismissible"><p>Слайдер Hero сохранён.</p></div>';
	}

	$current_ids = (string) get_option( 'tactic_hero_slider_ids', '' );
	$id_list     = array_filter( array_map( 'absint', explode( ',', $current_ids ) ) );
	?>
	<div class="wrap">
		<h1>TACTIC — Слайдер Hero</h1>
		<p style="max-width:900px;color:#646970;">Здесь настраиваются изображения слайдера главного баннера. Порядок выбранных изображений = порядок слайдов.</p>

		<?php echo wp_kses_post( $notice ); ?>

		<form method="post">
			<?php wp_nonce_field( 'tactic_save_hero_slider', 'tactic_hero_slider_nonce' ); ?>

			<table class="form-table" role="presentation">
				<tbody>
					<tr>
						<th scope="row"><label for="tactic_hero_slide_ids">Слайды Hero</label></th>
						<td>
							<input type="hidden" id="tactic_hero_slide_ids" name="tactic_hero_slide_ids" value="<?php echo esc_attr( implode( ',', $id_list ) ); ?>">
							<button type="button" class="button button-secondary" id="tactic-hero-pick">Выбрать изображения</button>
							<button type="button" class="button" id="tactic-hero-clear">Очистить</button>
							<p class="description">Можно выбрать несколько изображений. Используется на главной странице как слайдер с точками.</p>
							<div id="tactic-hero-preview" style="display:flex;gap:10px;flex-wrap:wrap;margin-top:10px;">
								<?php foreach ( $id_list as $img_id ) : ?>
									<?php echo wp_kses_post( wp_get_attachment_image( $img_id, 'thumbnail', false, [ 'style' => 'width:140px;height:90px;object-fit:cover;border:1px solid #dcdcde;' ] ) ); ?>
								<?php endforeach; ?>
							</div>
						</td>
					</tr>
				</tbody>
			</table>

			<?php submit_button( 'Сохранить слайды' ); ?>
		</form>
	</div>

	<script>
	(function($){
		'use strict';

		var frame = null;
		var $input = $('#tactic_hero_slide_ids');
		var $preview = $('#tactic-hero-preview');

		function renderPreview(items) {
			var html = '';
			items.forEach(function(item){
				var thumb = (item.sizes && item.sizes.thumbnail) ? item.sizes.thumbnail.url : item.url;
				html += '<img src="' + thumb + '" style="width:140px;height:90px;object-fit:cover;border:1px solid #dcdcde;" />';
			});
			$preview.html(html);
		}

		$('#tactic-hero-pick').on('click', function(e){
			e.preventDefault();

			if (!frame) {
				frame = wp.media({
					title: 'Выберите слайды Hero',
					button: { text: 'Использовать выбранные' },
					multiple: true,
					library: { type: 'image' }
				});

				frame.on('select', function(){
					var selection = frame.state().get('selection');
					var ids = [];
					var items = [];

					selection.each(function(model){
						var item = model.toJSON();
						ids.push(item.id);
						items.push(item);
					});

					$input.val(ids.join(','));
					renderPreview(items);
				});
			}

			frame.open();
		});

		$('#tactic-hero-clear').on('click', function(e){
			e.preventDefault();
			$input.val('');
			$preview.html('');
		});
	})(jQuery);
	</script>
	<?php
}

/* ── Отдельная страница: About на главной ───────────────────── */
function tactic_about_home_page(): void {
	if ( ! current_user_can( 'manage_options' ) ) {
		return;
	}

	$notice = '';

	if (
		isset( $_POST['tactic_about_home_nonce'] )
		&& wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['tactic_about_home_nonce'] ) ), 'tactic_save_about_home' )
	) {
		$image_id = isset( $_POST['tactic_about_home_building_id'] ) ? absint( $_POST['tactic_about_home_building_id'] ) : 0;
		update_option( 'tactic_about_home_building_id', $image_id );
		$notice = '<div class="notice notice-success is-dismissible"><p>Картинка блока About сохранена.</p></div>';
	}

	$current_id = (int) get_option( 'tactic_about_home_building_id', 0 );
	?>
	<div class="wrap">
		<h1>TACTIC — About (главная)</h1>
		<p style="max-width:900px;color:#646970;">Выберите изображение здания для правой части блока About на главной странице.</p>

		<?php echo wp_kses_post( $notice ); ?>

		<form method="post">
			<?php wp_nonce_field( 'tactic_save_about_home', 'tactic_about_home_nonce' ); ?>

			<table class="form-table" role="presentation">
				<tbody>
					<tr>
						<th scope="row"><label for="tactic_about_home_building_id">Изображение здания</label></th>
						<td>
							<input type="hidden" id="tactic_about_home_building_id" name="tactic_about_home_building_id" value="<?php echo esc_attr( (string) $current_id ); ?>">
							<button type="button" class="button button-secondary" id="tactic-about-pick">Выбрать изображение</button>
							<button type="button" class="button" id="tactic-about-clear">Очистить</button>
							<p class="description">Рекомендуется PNG на прозрачном фоне.</p>
							<div id="tactic-about-preview" style="margin-top:10px;max-width:420px;">
								<?php if ( $current_id ) : ?>
									<?php echo wp_kses_post( wp_get_attachment_image( $current_id, 'medium', false, [ 'style' => 'width:100%;height:auto;border:1px solid #dcdcde;' ] ) ); ?>
								<?php endif; ?>
							</div>
						</td>
					</tr>
				</tbody>
			</table>

			<?php submit_button( 'Сохранить изображение' ); ?>
		</form>
	</div>

	<script>
	(function($){
		'use strict';

		var frame = null;
		var $input = $('#tactic_about_home_building_id');
		var $preview = $('#tactic-about-preview');

		$('#tactic-about-pick').on('click', function(e){
			e.preventDefault();

			if (!frame) {
				frame = wp.media({
					title: 'Выберите изображение здания',
					button: { text: 'Использовать изображение' },
					multiple: false,
					library: { type: 'image' }
				});

				frame.on('select', function(){
					var item = frame.state().get('selection').first().toJSON();
					var thumb = (item.sizes && item.sizes.medium) ? item.sizes.medium.url : item.url;
					$input.val(item.id);
					$preview.html('<img src="' + thumb + '" style="width:100%;height:auto;border:1px solid #dcdcde;" />');
				});
			}

			frame.open();
		});

		$('#tactic-about-clear').on('click', function(e){
			e.preventDefault();
			$input.val('0');
			$preview.html('');
		});
	})(jQuery);
	</script>
	<?php
}

/**
 * Дефолтные плашки карты (zh/en) из strings.php с учётом overrides.
 *
 * @return array<int, array{zh:string,en:string}>
 */
function tactic_get_default_map_stats_rows(): array {
	$defaults  = require TACTIC_DIR . '/inc/strings.php';
	$overrides = (array) get_option( 'tactic_translations', [] );
	$strings   = array_replace_recursive( $defaults, $overrides );
	$keys      = [ 'stat1', 'stat2', 'stat3', 'stat4', 'stat5' ];
	$rows      = [];

	foreach ( $keys as $key ) {
		$rows[] = [
			'zh' => (string) ( $strings[ $key ]['zh'] ?? '' ),
			'en' => (string) ( $strings[ $key ]['en'] ?? '' ),
		];
	}

	return $rows;
}

/**
 * Отдельная страница: Плашки карты (вертикальный слайдер на главной).
 */
function tactic_map_stats_page(): void {
	if ( ! current_user_can( 'manage_options' ) ) {
		return;
	}

	$notice = '';

	if (
		isset( $_POST['tactic_map_stats_nonce'] )
		&& wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['tactic_map_stats_nonce'] ) ), 'tactic_save_map_stats' )
	) {
		$map_image_id = isset( $_POST['tactic_map_image_id'] ) ? absint( $_POST['tactic_map_image_id'] ) : 0;
		update_option( 'tactic_map_image_id', $map_image_id );

		$raw_rows = isset( $_POST['tactic_map_stats'] ) && is_array( $_POST['tactic_map_stats'] )
			? wp_unslash( $_POST['tactic_map_stats'] )
			: [];
		$rows = [];

		foreach ( $raw_rows as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}

			$zh = sanitize_text_field( (string) ( $row['zh'] ?? '' ) );
			$en = sanitize_text_field( (string) ( $row['en'] ?? '' ) );

			if ( '' === $zh && '' === $en ) {
				continue;
			}

			$rows[] = [
				'zh' => $zh,
				'en' => $en,
			];
		}

		update_option( 'tactic_map_stats', $rows );
		$notice = '<div class="notice notice-success is-dismissible"><p>Плашки карты сохранены.</p></div>';
	}

	$current_rows = get_option( 'tactic_map_stats', [] );
	if ( ! is_array( $current_rows ) || empty( $current_rows ) ) {
		$current_rows = tactic_get_default_map_stats_rows();
	}

	$current_map_image_id = (int) get_option( 'tactic_map_image_id', 0 );
	?>
	<div class="wrap">
		<h1>TACTIC — Плашки карты</h1>
		<p style="max-width:900px;color:#646970;">Управление картой и плашками вертикального слайдера в блоке Global Industrial Cooperation на главной.</p>

		<?php echo wp_kses_post( $notice ); ?>

		<form method="post">
			<?php wp_nonce_field( 'tactic_save_map_stats', 'tactic_map_stats_nonce' ); ?>

			<table class="form-table" role="presentation" style="max-width:1000px;margin-bottom:18px;">
				<tbody>
					<tr>
						<th scope="row"><label for="tactic_map_image_id">Изображение карты</label></th>
						<td>
							<input type="hidden" id="tactic_map_image_id" name="tactic_map_image_id" value="<?php echo esc_attr( (string) $current_map_image_id ); ?>">
							<button type="button" class="button button-secondary" id="tactic-map-pick">Выбрать изображение</button>
							<button type="button" class="button" id="tactic-map-clear">Очистить</button>
							<p class="description">Выбор картинки карты через вкладку TACTIC. Используется на главной странице.</p>
							<div id="tactic-map-preview" style="margin-top:10px;max-width:420px;">
								<?php if ( $current_map_image_id ) : ?>
									<?php echo wp_kses_post( wp_get_attachment_image( $current_map_image_id, 'medium', false, [ 'style' => 'width:100%;height:auto;border:1px solid #dcdcde;' ] ) ); ?>
								<?php endif; ?>
							</div>
						</td>
					</tr>
				</tbody>
			</table>

			<table class="widefat fixed" id="tactic-map-stats-table" style="max-width:1000px;">
				<thead>
					<tr>
						<th style="width:45%;">Текст (zh)</th>
						<th style="width:45%;">Text (en)</th>
						<th style="width:10%;">Действие</th>
					</tr>
				</thead>
				<tbody>
				<?php foreach ( $current_rows as $index => $row ) : ?>
					<tr>
						<td>
							<input type="text" class="regular-text" style="width:100%;" name="tactic_map_stats[<?php echo esc_attr( (string) $index ); ?>][zh]" value="<?php echo esc_attr( (string) ( $row['zh'] ?? '' ) ); ?>">
						</td>
						<td>
							<input type="text" class="regular-text" style="width:100%;" name="tactic_map_stats[<?php echo esc_attr( (string) $index ); ?>][en]" value="<?php echo esc_attr( (string) ( $row['en'] ?? '' ) ); ?>">
						</td>
						<td>
							<button type="button" class="button tactic-remove-stat-row">Удалить</button>
						</td>
					</tr>
				<?php endforeach; ?>
				</tbody>
			</table>

			<p style="margin-top:12px;display:flex;gap:8px;align-items:center;">
				<button type="button" class="button button-secondary" id="tactic-add-stat-row">+ Добавить плашку</button>
				<span style="color:#646970;">Пустые строки при сохранении автоматически удаляются.</span>
			</p>

			<?php submit_button( 'Сохранить плашки' ); ?>
		</form>
	</div>

	<script>
	(function($){
		'use strict';

		const table = document.getElementById('tactic-map-stats-table');
		const tbody = table ? table.querySelector('tbody') : null;
		const addBtn = document.getElementById('tactic-add-stat-row');
		const mapInput = document.getElementById('tactic_map_image_id');
		const mapPreview = document.getElementById('tactic-map-preview');
		const mapPickBtn = document.getElementById('tactic-map-pick');
		const mapClearBtn = document.getElementById('tactic-map-clear');
		let mapFrame = null;

		if (!tbody || !addBtn) {
			return;
		}

		const buildRow = (index) => {
			const tr = document.createElement('tr');
			tr.innerHTML =
				'<td><input type="text" class="regular-text" style="width:100%;" name="tactic_map_stats[' + index + '][zh]" value=""></td>' +
				'<td><input type="text" class="regular-text" style="width:100%;" name="tactic_map_stats[' + index + '][en]" value=""></td>' +
				'<td><button type="button" class="button tactic-remove-stat-row">Удалить</button></td>';
			return tr;
		};

		const reindexRows = () => {
			const rows = tbody.querySelectorAll('tr');
			rows.forEach((row, idx) => {
				const zhInput = row.querySelector('input[name*="[zh]"]');
				const enInput = row.querySelector('input[name*="[en]"]');
				if (zhInput) {
					zhInput.name = 'tactic_map_stats[' + idx + '][zh]';
				}
				if (enInput) {
					enInput.name = 'tactic_map_stats[' + idx + '][en]';
				}
			});
		};

		addBtn.addEventListener('click', () => {
			const nextIndex = tbody.querySelectorAll('tr').length;
			tbody.appendChild(buildRow(nextIndex));
		});

		if (mapPickBtn && mapInput && mapPreview) {
			mapPickBtn.addEventListener('click', (event) => {
				event.preventDefault();

				if (!mapFrame) {
					mapFrame = wp.media({
						title: 'Выберите изображение карты',
						button: { text: 'Использовать изображение' },
						multiple: false,
						library: { type: 'image' }
					});

					mapFrame.on('select', () => {
						const item = mapFrame.state().get('selection').first().toJSON();
						const preview = (item.sizes && item.sizes.medium) ? item.sizes.medium.url : item.url;
						mapInput.value = String(item.id || 0);
						mapPreview.innerHTML = '<img src="' + preview + '" style="width:100%;height:auto;border:1px solid #dcdcde;" />';
					});
				}

				mapFrame.open();
			});
		}

		if (mapClearBtn && mapInput && mapPreview) {
			mapClearBtn.addEventListener('click', (event) => {
				event.preventDefault();
				mapInput.value = '0';
				mapPreview.innerHTML = '';
			});
		}

		tbody.addEventListener('click', (event) => {
			const target = event.target;
			if (!(target instanceof HTMLElement)) {
				return;
			}
			if (!target.classList.contains('tactic-remove-stat-row')) {
				return;
			}

			const row = target.closest('tr');
			if (row) {
				row.remove();
				reindexRows();
			}
		});
	})(jQuery);
	</script>
	<?php
}