привет, у меня есть битрикс24 в коробке в нем есть...

Создано: 7 апреля 2025 г.

Отвечено с использованием O1 от Chat01

Вопрос

привет, у меня есть битрикс24 в коробке в нем есть отчеты пользователей, хоется для начала кастометезировать карточку отчета со стороны руководителя нам надо туда добавить еще одно свойство
оценка загруженности (от 1 до 5)

вот примерные данные отчеты все на компоненте:bitrix:timeman.report.weekly: 0.019 с
но нас интерисует именно детальная карточка отчета (пока не нашел где ее править)
Вот нашел где идет ответ по ее постронению:bitrix/modules/timeman/tools но возможно не туда копаю.
case 'admin_report_full':
$bReturnRes = true;

text
$ID = intval($_POST["report_id"] ?? 0); $USER_ID = intval($_POST["user_id"] ?? 0); $OBJID = intval($_POST["id"] ?? 0); $arAccessUsers = CTimeMan::GetAccess(); $bCanEditAll = in_array('*', $arAccessUsers['WRITE']); $bCanReadAll = in_array('*', $arAccessUsers['READ']); $bCanReadUser = (in_array($USER_ID, $arAccessUsers["READ"]) || $bCanReadAll); $bCanWriteUser = (in_array($USER_ID, $arAccessUsers["WRITE"]) || $bCanEditAll); if ($USER_ID > 0 && $bCanWriteUser && ($_POST["approve"] ?? null)) { if ($_POST["approve"] == "Y") { $dbrep = CTimeManReportFull::GetByID($ID); $rep = $dbrep->Fetch(); if (is_array($rep) && $USER_ID == $rep['USER_ID']) { $arFields = [ "MARK" => (in_array($_POST["mark"], ["G", "B", "N"]) !== false) ? $_POST["mark"] : "X", ]; if ($arFields["MARK"] != "X") { $arFields["APPROVER"] = $USER->GetID(); $arFields["APPROVE"] = "Y"; $arFields["APPROVE_DATE"] = ConvertTimeStamp(time(), "FULL"); } else { $arFields["APPROVE"] = "N"; $arFields["APPROVER"] = 0; $arFields["APPROVE_DATE"] = ""; } CTimeManReportFull::Update($ID, $arFields); $CACHE_MANAGER->Clean(CUserReportFull::getInfoCacheId($USER_ID), 'timeman_report_info'); CReportNotifications::MessageUpdate($ID, $rep, $arFields); } } } elseif (($_POST["mode"] ?? null) && $OBJID > 0) { $arFields = [ "UF_REPORT_PERIOD" => $_POST["mode"], "UF_TM_TIME" => (IsAmPmMode() ? convertTimeToMilitary($_POST["time"], 'H:MI T', 'HH:MI') : $_POST["time"]), "UF_TM_REPORT_DATE" => $_POST["date"], "UF_TM_DAY" => $_POST["day"], "ID" => $OBJID, ]; if ($_POST["object"] == "user") { $bCanEdit = ((in_array($OBJID, $arAccessUsers["WRITE"]) && $OBJID != $USER->GetID()) || $bCanEditAll || CTimeMan::IsAdmin() ); if ($bCanEdit) { $arReportUser = new CUserReportFull($OBJID); $res = $arReportUser->SetPeriod($arFields); $CACHE_MANAGER->Clean(CReportSettings::getSettingsCacheId($OBJID), 'timeman_report_settings'); $CACHE_MANAGER->Clean(CUserReportFull::getInfoCacheId($OBJID), 'timeman_report_info'); } } elseif ($_POST["object"] == "dep") { $arSubordination = CIntranetUtils::GetSubordinateDepartments($USER->GetID(), true); $bCanEdit = (in_array($OBJID, $arSubordination) || CTimeMan::IsAdmin()); if ($bCanEdit) { $res = CTimeManReportFull::SetPeriodSection($arFields); $CACHE_MANAGER->CleanDir('timeman_report_settings'); $CACHE_MANAGER->CleanDir('timeman_report_info'); } } } if ($bCanReadUser && !($_POST["mode"] ?? null)) { $dbRes = CUser::GetList( 'ID', 'ASC', ['ID' => $USER_ID], ['SELECT' => ['UF_*']] ); $arUser = $dbRes->GetNext(); $arUser['PHOTO'] = $arUser['PERSONAL_PHOTO'] > 0 ? CIntranetUtils::InitImage($arUser['PERSONAL_PHOTO'], 100, 100, BX_RESIZE_IMAGE_EXACT) : []; $arManagers = CTimeMan::GetUserManagers($USER_ID); $arManagers[] = $USER_ID; if (!is_array($arManagers) || count($arManagers) <= 0) { $arManagers = [$USER_ID]; } $user_url = COption::GetOptionString( 'intranet', 'path_user', '/company/personal/user/#USER_ID#/', ($_REQUEST['site_id'] ?? '') ); $dbManagers = CUser::GetList('ID', 'ASC', ['ID' => implode('|', $arManagers)]); $res["TO"] = []; $res["FROM"] = []; while ($manager = $dbManagers->Fetch()) { $manager['PHOTO'] = $manager['PERSONAL_PHOTO'] > 0 ? CIntranetUtils::InitImage($manager['PERSONAL_PHOTO'], 100, 100, BX_RESIZE_IMAGE_EXACT) : []; $arUserData = [ 'ID' => $manager['ID'], 'LOGIN' => $manager['LOGIN'], 'NAME' => CUser::FormatName(CSite::GetNameFormat(false), $manager, true, true), 'URL' => str_replace(['#ID#', '#USER_ID#'], $manager['ID'], $user_url), 'WORK_POSITION' => $manager['WORK_POSITION'], 'PHOTO' => $manager['PHOTO']['CACHE']['src'] ?? '', ]; if ($USER_ID == $arUserData["ID"]) { $res["FROM"] = $arUserData; } else { $res["TO"][] = $arUserData; } } if (count($res["TO"]) == 0) { $res["TO"][] = $res["FROM"]; } $arFilter = ["ID" => $ID, "USER_ID" => $USER_ID]; $arReportID = []; if ($_POST["empty_slider"] ?? null) { $arFilter = ["ACTIVE" => "Y", "USER_ID" => $USER_ID]; } $dbres = CTimeManReportFull::GetList(["USER_ID" => "asc", "ID" => "asc"], $arFilter); $report = null; while ($rep = $dbres->Fetch()) { $arReportID[] = $rep["ID"]; if ($rep["ID"] == $ID) { $report = $rep; } } $res["REPORT_LIST"] = $arReportID; if (is_array($report)) { $res["INFO"] = $report; if ($res["INFO"]['TASKS_ENABLED'] = (CBXFeatures::IsFeatureEnabled('Tasks') && CModule::IncludeModule('tasks'))) { $res["INFO"]['TASKS'] = unserialize($res["INFO"]['TASKS'] ?? '', ['allowed_classes' => false]); } else { unset($res["INFO"]['TASKS']); } if ($res["INFO"]['CALENDAR_ENABLED'] = CBXFeatures::IsFeatureEnabled('Calendar')) { $res["INFO"]['EVENTS'] = unserialize($res["INFO"]['EVENTS'] ?? '', ['allowed_classes' => false]); } else { unset($res["INFO"]['EVENTS']); } if ($res["INFO"]['FILES']) { $res["INFO"]['FILES'] = unserialize($res["INFO"]['FILES'] ?? '', ['allowed_classes' => false]); } $res["INFO"]['CAN_EDIT'] = ($arUser['ID'] != $USER->GetID()) && ($bCanEditAll || in_array($arUser['ID'], $arAccessUsers['WRITE'])); $res["INFO"]['CAN_EDIT_TEXT'] = ($report["APPROVE"] == "Y") ? "N" : "Y"; $culture = \Bitrix\Main\Application::getInstance()->getContext()->getCulture(); $dayMonthFormat = $culture->getDayMonthFormat(); if ($report["DATE_FROM"] != $report["DATE_TO"]) { $res["INFO"]["TEXT_TITLE"] = FormatDate($dayMonthFormat, MakeTimeStamp($report["DATE_FROM"])) . " - " . FormatDate($dayMonthFormat, MakeTimeStamp($report["DATE_TO"])); } else { $res["INFO"]["TEXT_TITLE"] = FormatDate($dayMonthFormat, MakeTimeStamp($report["DATE_TO"])); } $res["INFO"]["REPORT_STRIP_TAGS"] = strip_tags(nl2br($res["INFO"]["REPORT"])); $res["INFO"]["PLAN_STRIP_TAGS"] = strip_tags(nl2br($res["INFO"]["PLANS"])); $res["INFO"]["APPROVER_INFO"] = []; if (intval($res["INFO"]["APPROVER"]) > 0) { $res["INFO"]["APPROVE_DATE"] = FormatDate($DB->DateFormatToPHP(FORMAT_DATETIME), MakeTimeStamp($res["INFO"]["APPROVE_DATE"])); foreach ($res["TO"] as $manager) { if ($manager["ID"] == intval($res["INFO"]["APPROVER"])) { $res["INFO"]["APPROVER_INFO"] = $manager; break; } } if (!$res["INFO"]["APPROVER_INFO"]) { $dbaprrove = CUser::GetList('ID', 'ASC', ['ID' => intval($res["INFO"]["APPROVER"])]); if ($approver = $dbaprrove->Fetch()) { $approver['PHOTO'] = $approver['PERSONAL_PHOTO'] > 0 ? CIntranetUtils::InitImage($approver['PERSONAL_PHOTO'], 100, 100, BX_RESIZE_IMAGE_EXACT) : []; $res["INFO"]["APPROVER_INFO"] = [ 'ID' => $approver['ID'], 'LOGIN' => $approver['LOGIN'], 'NAME' => CUser::FormatName(CSite::GetNameFormat(false), $approver, true, true), 'URL' => str_replace(['#ID#', '#USER_ID#'], $approver['ID'], $user_url), 'WORK_POSITION' => $approver['WORK_POSITION'], 'PHOTO' => $approver['PHOTO']['CACHE']['src'], ]; } } } ob_start(); $APPLICATION->IncludeComponent( "bitrix:timeman.topic.reviews", "", [ "REPORT_ID" => $ID, ], false ); $res["COMMENTS"] = ob_get_contents(); ob_end_clean(); } } break; //carter

Мысль

Customization for workload assessment

I’m exploring a Bitrix24 scenario to tailor the manager's detailed report card. The objective is to add a workload assessment property (1 to 5) and ensure it is stored in the database, potentially in the "report full" table.

Updating the database

I’m thinking about creating a new database column for workload assessment or using an existing TIMEMAN field. Then, I'll adjust the admin_report_full code and ensure the new field is displayed on the front-end.

Integrating the assessment

I’m working through integrating the workload assessment into the admin_report_full section. This involves adding the field to the $res["INFO"] array, updating the database, and displaying it on the front-end template.

Adding user fields

I’m considering creating a new user field, "UF_LOADING_RATING," for the TimemanReportFull table. This involves updating the database schema, modifying the admin_report_full code, and incorporating it into the template for front-end display.

Reasoned about Bitrix24 report customization for 12 seconds

Ответ

Ниже общий план, как можно добавить в «детальную карточку отчёта» новое поле «оценка загруженности», чтобы руководитель мог его выставлять и смотреть:


1. Добавление нового поля (колонки) в таблицу или создание пользовательского поля (UF_*)

Вариант A. Добавить собственную колонку в таблицу b_timeman_report_full

  1. Создать поле в базе. Например, если вы хотите хранить оценку загруженности, то в таблице b_timeman_report_full можете добавить столбец вида:

    sql
    ALTER TABLE b_timeman_report_full ADD UF_WORKLOAD_MARK INT DEFAULT 0;

    Или если это текстом: VARCHAR(255), но для оценки от 1 до 5 подойдёт INT.

  2. Убедиться, что модуль умеет «видеть» это поле. В коде Bitrix24 иногда нужно вручную подхватить или указать в CReport::GetList и т.д. В случае с CTimeManReportFull::GetByID() – зачастую Bitrix подтягивает все поля таблицы (или «UF_*»), но это надо проверять.

    • Если Bitrix не тянет автоматически «пользовательские» поля, можно где-то дописать select UF_WORKLOAD_MARK as ... либо использовать средства для регистрирования «UF_» полей.

Вариант B. Создать пользовательское поле UF_* для timeman_report_full

Bitrix умеет добавлять полям сущностей свои «userfield». Но для этого нужно:

  1. Зарегистрировать пользовательское поле через Админку → Настройки → Пользовательские поля (или через скрипт на api).
  2. Указать, что это поле для типа «TIMEMAN_REPORT_FULL» (или похожей сущности, если такая зарегистрирована).
  3. Тогда это поле станет «UF_…» в таблице, и его будет удобно заполнять через стандартные функции вроде CTimeManReportFull::Update($id, [ "UF_…" => $value ]).

Если вы не хотите заморачиваться с пользовательскими полями Bitrix, то проще пойти по первому пути (добавить своё поле напрямую в таблицу).


2. Правка серверной логики (обработка POST, сохранение)

В вашем куске кода есть участок:

php
if ($USER_ID > 0 && $bCanWriteUser && ($_POST["approve"] ?? null)) { if ($_POST["approve"] == "Y") { $dbrep = CTimeManReportFull::GetByID($ID); $rep = $dbrep->Fetch(); if (is_array($rep) && $USER_ID == $rep['USER_ID']) { $arFields = [ "MARK" => (in_array($_POST["mark"], ["G", "B", "N"]) !== false) ? $_POST["mark"] : "X", ]; ... CTimeManReportFull::Update($ID, $arFields); } } }

Или другой блок, который что-то обновляет.
Вы можете в этот момент прочитать $_POST["workload"], проверить корректность значения (1…5) и сохранить вместе с MARK. Например:

php
if ($USER_ID > 0 && $bCanWriteUser && ($_POST["approve"] ?? null)) { if ($_POST["approve"] == "Y") { $dbrep = CTimeManReportFull::GetByID($ID); $rep = $dbrep->Fetch(); if (is_array($rep) && $USER_ID == $rep['USER_ID']) { $newMark = (in_array($_POST["mark"], ["G", "B", "N"]) !== false) ? $_POST["mark"] : "X"; // допустим, workload приходит в виде числа от 1 до 5: $workload = (int) $_POST["workload"]; if ($workload < 1 || $workload > 5) { $workload = 0; // или по умолчанию 0, если вышло за диапазон } $arFields = [ "MARK" => $newMark, "UF_WORKLOAD_MARK" => $workload, ]; if ($arFields["MARK"] != "X") { $arFields["APPROVER"] = $USER->GetID(); $arFields["APPROVE"] = "Y"; $arFields["APPROVE_DATE"] = ConvertTimeStamp(time(), "FULL"); } else { $arFields["APPROVE"] = "N"; $arFields["APPROVER"] = 0; $arFields["APPROVE_DATE"] = ""; } CTimeManReportFull::Update($ID, $arFields); $CACHE_MANAGER->Clean(CUserReportFull::getInfoCacheId($USER_ID), 'timeman_report_info'); CReportNotifications::MessageUpdate($ID, $rep, $arFields); } } }

Таким образом, при аппруве (и вообще по нажатию нужной кнопки) у вас запишется UF_WORKLOAD_MARK в таблице.

Важно: смотрите, где именно вам удобнее обрабатывать поле «оценка загруженности». Может быть, это не в блоке approve == "Y", а в другом блоке или вообще при формировании отчёта. Логика зависит от того, как вы хотите, чтобы поле заполнялось: только при утверждении отчёта или при редактировании и т.п.


3. Вывод значения в массив данных (чтобы показать в шаблоне)

Чтобы потом это поле отобразить в «детальной карточке отчёта», надо добавить его в $res["INFO"]. В коде можно посмотреть, как формируется $res["INFO"] = $report;, там скорее всего всё содержимое строки из CTimeManReportFull::GetByID($ID) кладётся. Если Bitrix уже подтягивает это поле, то оно появится в $report["UF_WORKLOAD_MARK"]. Убедитесь, что оно действительно там:

php
if (is_array($report)) { $res["INFO"] = $report; // ... // здесь где-то можно явно продублировать $res["INFO"]["WORKLOAD_MARK"] = $report["UF_WORKLOAD_MARK"]; // ... }

Если там нет UF_WORKLOAD_MARK, возможно, нужно либо расширять SELECT, либо зарегистрировать поле как пользовательское, либо просто делать отдельный запрос. Но обычно, если таблица b_timeman_report_full имеет колонку UF_WORKLOAD_MARK, и CTimeManReportFull::GetByID() не урезает SELECT, то Bitrix её вытащит.


4. Правка шаблона компонента (bitrix:timeman.report.weekly / detail)

Вы пишете, что пока не нашли, где правится детальная карточка отчёта. Обычно детальная карточка может выводиться через AJAX (судя по коду) или через включаемый компонент. Нужно в папке bitrix/components/bitrix/timeman.report.weekly/templates/.default/ (или в папке вашего шаблона, если переопределяли) искать файлы типа template.php, report.php, ajax.php и т.п.
Иногда деталка «вшита» в JS + AJAX. Для наглядности можно:

  1. Включить режим «Редактирование в публичной части» (если доступно).
  2. «Подсветку областей», чтобы увидеть, какой шаблон вызывается.
  3. Либо искать по проекту вызов компонента bitrix:timeman.topic.reviews или bitrix:timeman.report.weekly, как раз в файлах .default/template.php и смотреть, куда выводится $res["INFO"].

Там, где выводится основной отчёт (REPORT, PLANS, MARK), вы можете вписать строчку типа:

php
Оценка загруженности: <?=$arResult["INFO"]["UF_WORKLOAD_MARK"]?>

(или как вы там выводите — <span>, <div> и т.д.).

Возможно, у вас отдельный шаблон для «admin_report_full» (детального просмотра менеджером). Если его нет в timeman.report.weekly, то бывает, что это лежит в bitrix:timeman.report.edit или в каком-нибудь include-файле. Но поищите по коду фрагменты вроде case 'admin_report_full': ... $APPLICATION->IncludeComponent("bitrix:timeman.topic.reviews", ...), а затем поищите IncludeComponent("bitrix:timeman.topic.reviews" в папке /bitrix/components/bitrix/.


5. Итог: что нужно сделать «по-минимуму»

  1. Добавить поле в таблицу (через SQL или пользовательское поле).
  2. Поправить участок, где идёт CTimeManReportFull::Update(...), дописать UF_WORKLOAD_MARK (считать из $_POST["workload"] и записать).
  3. Убедиться, что в выборке детального отчёта это поле подтягивается (в $report). Если нет – настроить/дописать SELECT.
  4. В шаблоне детального просмотра вывести это значение.

Обычно этих шагов достаточно, чтобы сохранить и показывать новую «оценку загруженности».


Небольшая рекомендация

Чтобы не ломать «коробку», старайтесь переопределять шаблоны компонентов в вашем собственном пространстве (через копирование в /local/components/… или /local/templates/…). А в файлах ядра (bitrix/modules/timeman/tools/index.php) в идеале правки тоже надо делать аккуратно — возможно, через события (OnBeforeReportUpdate / OnAfterReportUpdate), если такие есть. Но если события не предусмотрены, придётся править ядро или заводить свой мини-компонент и т.д.
Просто имейте в виду, что при обновлении коробки могут перезаписаться файлы ядра. Поэтому все «хардкорные» правки надо документировать и проверять при апдейтах.


Надеюсь, этот план поможет вам добавить своё поле «Оценка загруженности (1-5)» и отобразить его в детальной карточке отчёта. Если возникнут вопросы с поиском конкретного шаблона, попробуйте по проекту поискать (grep/phpstorm/vs code) упоминания bitrix:timeman.report.weekly и bitrix:timeman.topic.reviews, чтобы понять, где именно выводится детальная часть.

Вопрос

начнем с хотябы шаблока просто его отрисовать свойство. Т.е. без сохронения и пользовательской части..
bitrix:timeman.report.weekly: 0.019 :

<? if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED!==true)die(); /** * Bitrix vars * * @var array $arParams * @var array $arResult * @var CBitrixComponent $component * @var CBitrixComponentTemplate $this * @global CMain $APPLICATION * @global CUser $USER */ \Bitrix\Main\UI\Extension::load(['ui.design-tokens']); $this->setFrameMode(true); if ($arResult['TASKS_ENABLED']): $APPLICATION->IncludeComponent( "bitrix:tasks.iframe.popup", ".default", array( "ON_TASK_ADDED" => "BX.DoNothing", "ON_TASK_CHANGED" => "BX.DoNothing", "ON_TASK_DELETED" => "BX.DoNothing", ), null, array("HIDE_ICONS" => "Y") ); endif; $arMess = array( 'EMPLOYEE' => GetMessage('TMR_EMPLOYEE'), 'OVERALL' => GetMessage('TMR_OVERALL'), 'OVERALL_DAYS' => GetMessage('TMR_OVERALL_DAYS'), 'OVERALL_VIOL' => GetMessage('TMR_OVERALL_VIOL'), 'TMR_OVERALL_VIOL_GOOD' => GetMessage('TMR_OVERALL_VIOL_GOOD'), 'ARRIVAL' => GetMessage('TMR_ARRIVAL'), 'DEPARTURE' => GetMessage('TMR_DEPARTURE'), 'EXP' => GetMessage('TMR_EXP'), 'CAPTION_ARRIVAL' => GetMessage('TMR_CAPTION_ARRIVAL'), 'CAPTION_DEPARTURE' => GetMessage('TMR_CAPTION_DEPARTURE'), 'CAPTION_DURATION' => GetMessage('TMR_CAPTION_DURATION'), 'FIXED' => GetMessage('TMR_FIXED'), 'FIXED_AT' => GetMessage('TMR_FIXED_AT'), 'FIXED_CAPTION' => GetMessage('TMR_FIXED_CAPTION'), 'FIXED_APPROVER' => GetMessage('TMR_FIXED_APPROVER'), 'APPROVE' => GetMessage('TMR_APPROVE'), 'APPROVE' => GetMessage('TMR_APPROVE'), 'SAVE' => GetMessage('MAIN_SAVE'), 'CLOSE' => GetMessage('TMR_CLOSE'), 'LEAKS_CAPTION' => GetMessage('TMR_LEAKS_CAPTION'), 'FILTER_ARRIVAL' => GetMessage('TMR_FILTER_ARRIVAL'), 'FILTER_DEPARTURE' => GetMessage('TMR_FILTER_DEPARTURE'), 'FILTER_LABEL_ADD' => GetMessage('TMR_FILTER_LABEL_ADD'), 'FILTER_LABEL_DPT' => GetMessage('TMR_FILTER_LABEL_DPT'), 'HINT_TITLE' => GetMessage('TMR_HINT_TITLE'), 'HINT_MAX_START' => GetMessage('TMR_HINT_MAX_START'), 'HINT_MIN_FINISH' => GetMessage('TMR_HINT_MIN_FINISH'), 'HINT_MIN_DURATION' => GetMessage('TMR_HINT_MIN_DURATION'), 'DAY_NOT_FINISHED' => GetMessage('TMR_DAY_NOT_FINISHED'), 'HEAD' => GetMessage('TMR_HEAD'), 'HINT_DISABLED' => GetMessage('TMR_HINT_DISABLED'), 'HINT_FREE' => GetMessage('TMR_HINT_FREE'), 'MARK_N'=>GetMessage("TMR_NO_MARK"), 'MARK_G'=>GetMessage("TMR_GOOD_MARK"), 'MARK_B'=>GetMessage("TMR_BAD_MARK"), 'NONE'=>GetMessage("TMR_NONE"), 'DAY'=>GetMessage("TMR_DAY"), 'WEEK'=>GetMessage("TMR_WEEK"), 'MONTH'=>GetMessage("TMR_MONTH"), 'TIME'=>GetMessage("TMR_TIME"), 'PARENT'=>GetMessage("TMR_PARENT"), 'DATE'=>GetMessage("TMR_DATE"), 'PERIOD'=>GetMessage("TMR_PERIOD"), 'TMR_DESC'=>GetMessage("TMR_DESC"), 'TMR_EVERY_DAY'=>GetMessage("TMR_EVERY_DAY"), 'TMR_EVERY_WEEK'=>GetMessage("TMR_EVERY_WEEK"), 'TMR_EVERY_MONTH'=>GetMessage("TMR_EVERY_MONTH"), 'TMR_INDAY_FULL_1'=>GetMessage("TMR_INDAY_FULL_1"), 'TMR_INDAY_FULL_2'=>GetMessage("TMR_INDAY_FULL_2"), 'TMR_INDAY_FULL_3'=>GetMessage("TMR_INDAY_FULL_3"), 'TMR_INDAY_FULL_4'=>GetMessage("TMR_INDAY_FULL_4"), 'TMR_INDAY_FULL_5'=>GetMessage("TMR_INDAY_FULL_5"), 'TMR_INDAY_FULL_6'=>GetMessage("TMR_INDAY_FULL_6"), 'TMR_INDAY_FULL_7'=>GetMessage("TMR_INDAY_FULL_7"), 'TMR_DAY_FULL_1'=>GetMessage("TMR_DAY_FULL_2"), 'TMR_DAY_FULL_2'=>GetMessage("TMR_DAY_FULL_3"), 'TMR_DAY_FULL_3'=>GetMessage("TMR_DAY_FULL_4"), 'TMR_DAY_FULL_4'=>GetMessage("TMR_DAY_FULL_5"), 'TMR_DAY_FULL_5'=>GetMessage("TMR_DAY_FULL_6"), 'TMR_DAY_FULL_6'=>GetMessage("TMR_DAY_FULL_7"), 'TMR_DAY_FULL_7'=>GetMessage("TMR_DAY_FULL_1"), 'TMR_INDATE'=>GetMessage("TMR_INDATE"), 'TMR_BEGIN_DATE'=>GetMessage("TMR_BEGIN_DATE"), 'TMR_DAY_WEEK'=>GetMessage("TMR_DAY_WEEK"), 'TMR_DATE_MONTH'=>GetMessage("TMR_DATE_MONTH"), 'TMR_MARK'=>GetMessage("TMR_MARK"), 'TMR_REPORT_COUNT'=>GetMessage("TMR_REPORT_COUNT"), 'TMR_REPORT_COUNT_MARK'=>GetMessage("TMR_REPORT_COUNT_MARK"), 'TMR_REPORT'=>GetMessage("TMR_REPORT"), 'TMR_NOT_ACCEPT'=>GetMessage("TMR_NOT_ACCEPT"), 'TMR_REPORT_APPROVER'=>GetMessage("TMR_REPORT_APPROVER"), 'TMR_ACCEPT_DATE'=>GetMessage("TMR_ACCEPT_DATE"), 'TMR_PARENT'=>GetMessage("TMR_PARENT"), 'TMR_SUCCESS'=>GetMessage("TMR_SUCCESS"), 'TM_SETTINGS_REPORT'=>GetMessage("TM_SETTINGS_REPORT") ); $cur_date_m = date('n'); $cur_date_y = date('Y'); ?> <div class="tm-report-layout" id="bx_tm_report"> <div class="webform tm-filter-webform"> <form name="REPORT_FILTER"> <div class="webform-round-corners webform-additional-fields tm-report"> <div class="webform-corners-top"> <div class="webform-left-corner"></div> <div class="webform-right-corner"></div> </div>
text
<div class="webform-content tm-filter"> <span class="tm-filter-item filter-date tm-filter-item-first" id="tm-filter-date"> <a href="javascript:void(0)" onclick="this.blur();window.BXTMREPORT.changeMonth(-1)" class="filter-date-link filter-date-link-left"></a> <span class="fiter-date-text" id="tm_datefilter_title" onclick="BX.calendar({node: this, field: 'bx_goto_date', bTime: false, callback: jsCalendarInsertDate});"><?= \Bitrix\Main\Localization\Loc::getMessage( 'TMR_REPORT_DATE_FILTER_TITLE', [ '#MONTH#' => GetMessage('TMR_MONTH_'.$cur_date_m), '#YEAR#' => $cur_date_y ] ) ?></span> <input type="hidden" name="bx_goto_date" id="bx_goto_date" value="<?=ConvertTimeStamp()?>" />
<? $APPLICATION->IncludeComponent('bitrix:main.calendar', '', array('SHOW_INPUT' => 'Y', 'SHOW_TIME' => 'N', 'HIDE_TIMEBAR' => 'Y', 'INPUT_NAME' => 'bx_goto_date', 'SILENT'=>'Y'), null, array('HIDE_ICONS' => 'Y')); ?> <script> function jsCalendarInsertDate(value) { if (BX.type.isDate(value)) value = value.valueOf(); else value = parseInt(value); var date = new Date(value); if (date.getMonth() == BXTMREPORT.SETTINGS.DATE_START.getMonth() && date.getYear() == BXTMREPORT.SETTINGS.DATE_START.getYear()) { var cell = BXTMREPORT.PARTS.LAYOUT_COLS.DATA.firstChild.tHead.rows[0].cells[date.getDate()-1]; var q = cell.offsetLeft - BXTMREPORT.PARTS.LAYOUT_COLS.DATA.offsetWidth + Math.ceil(cell.offsetWidth * 1.6); BXTMREPORT.PARTS.LAYOUT_COLS.DATA.scrollLeft = q > 0 ? q : 0; } else { var dm = (date.getFullYear()-BXTMREPORT.SETTINGS.DATE_START.getFullYear()) * 12; dm += date.getMonth() - BXTMREPORT.SETTINGS.DATE_START.getMonth(); BXTMREPORT.changeMonth(dm) } } </script>
text
<a href="javascript:void(0)" onclick="this.blur();window.BXTMREPORT.changeMonth(1)" class="filter-date-link filter-date-link-right"></a> <a href="javascript:void(0)" class="filter-date-link filter-date-link-calendar" onclick="this.blur();window.BXTMREPORT.setToday(); return false;" title="<?=htmlspecialcharsbx(GetMessage('TMR_SET_TODAY'))?>"></a> </span> <input type="hidden" name="month" value="<?=$cur_date_m;?>" /> <input type="hidden" name="year" value="<?=$cur_date_y;?>" /> <span class="tm-filter-item"> <input type="checkbox" name="stats" id="stats" onclick="window.BXTMREPORT.toggleStats(this.checked);" checked="checked" /><label for="stats"><?=GetMessage('TMR_STATS');?></label> </span>
<? if (count($arResult['arAccessUsers']['READ']) > 1 || $arResult['arAccessUsers']['READ'][0] == '*'): ?>
text
<?if (count($arResult['arDirectUsers'])>1) $display = "inline-block"; else $display = "none";?> <span class="tm-filter-item inactive" style = "display:<?=$display;?>"> <select name="show_all" class="inactive" onmousedown="BX.removeClass(this.parentNode, 'inactive')" onchange="window.BXTMREPORT.Filter();"> <option value="Y"><?=GetMessage('TMR_FILTER_SHOW_ALL_Y')?></option> <option <?=(($arResult['SHOW_ALL'] == "N")?"selected":"")?> value="N"><?=GetMessage('TMR_FILTER_ONLY_DIRECT')?></option> </select> <a href="javascript:void(0)" class="filter-reset" onclick="this.blur(); document.forms.REPORT_FILTER.show_all.value = 'Y'; window.BXTMREPORT.Filter(); return false;"></a> </span> <span class="tm-filter-item inactive">
<? function __tmr_replace($str) { $str = preg_replace( '/<option([^>]*)>'.GetMessage('MAIN_NO').'<\/option>/iu', '<option\\1>'.GetMessage('TMR_FILTER_DEPT_0').'</option>', $str ); $str = preg_replace('/name="department"/i', 'name="department" onchange="window.BXTMREPORT.Filter();"', $str); return $str; } CIntranetUtils::ShowDepartmentFilter($arResult['UF_DEPARTMENT_field'], true, false, '__tmr_replace'); ?> <script> document.forms.REPORT_FILTER.department.value = "<?=$arResult["DEPARTMENT_ID"];?>"; </script>

<a href="javascript:void(0);" class="filter-reset" onclick="this.blur(); document.forms.REPORT_FILTER.department.value = ''; window.BXTMREPORT.Filter(); return false;"></a>
</span>
<span class="tm-settings-item" id="TMBUTTON" onclick="BXTMREPORT.InitSettingMode(this);">
<span class="tm-settings-l"></span><span class="tm-settings-c"><span class="tm-settings-icon"></span><?=GetMessage("TM_SETTINGS")?></span><span class="tm-settings-r"></span>
</span>

<? endif; ?>
text
</div> <div class="webform-corners-bottom"> <div class="webform-left-corner"></div> <div class="webform-right-corner"></div> </div> </div> </form> </div> <div id="bx-report-overdue" style="display:none" class="bx-tm-overdue"></div>
</div> <script> window.BXTMREPORT = new JCTimeManReport('bx_tm_report', { DEPARTMENTS: 'tm_report_conrol_departments', FILTER: 'REPORT_FILTER', START_SHOW_ALL:"<?=$arResult["SHOW_ALL"];?>", START_DEPARTMENT:"<?=$arResult["DEPARTMENT_ID"];?>", DATESELECTOR: 'bx_tm_report_dateselector', MONTHS: [<?for($i=1;$i<13;$i++){echo ($i==1 ? '' : ','),"'",CUtil::JSEscape(GetMessage('TMR_MONTH_'.$i)),"'";}?>], DAYS: [<?for($i=1;$i<8;$i++){echo ($i==1 ? '' : ','),"'",CUtil::JSEscape(GetMessage('TMR_DAY_'.$i)),"'";}?>], LANG: <?=CUtil::PhpToJsObject($arMess)?>, SITE_ID: '<?=SITE_ID?>' })</script> <div style="clear: both;"></div>

Мысль

Integrating a new property

I’m mapping out the insertion of a "Оценка загруженности" property, rating 1 to 5. The aim is to embed it seamlessly within the template code either after lines 214 or 215 or at the end.

Mapping the integration

Noticing the need to include "Оценка загруженности" in the template, I’m thinking of simple printing like "Оценка загруженности: ??", using "echo" or "<?=$arResult["INFO"]["someProperty"]?>" for display.

Enhancing template precision

I’m refining the template code to include "Оценка загруженности" using <?=$arResult["INFO"]["UF_WORKLOAD_MARK"]?>, ensuring it’s placed correctly and adding a margin for clarity.

Introducing static text

Pinpointing the user's desire for static text "Оценка загруженности: 4" in the template, as the detailed template might involve different specifications.

Reasoned for 12 seconds

Ответ

Ниже короткий пример того, как можно «в лоб» добавить на страницу шаблона вывод свойства «Оценка загруженности». Предположим, что в массиве $$arResult$ (или другом месте) у нас уже есть переменная с названием UF_WORKLOAD_MARK (или любая своя), которую мы хотим показать.

В вашем шаблоне bitrix:timeman.report.weekly, после (или до) нужного места верстки просто вставьте, к примеру:

php
<?php // Примерно в конце файла template.php (или в подходящем месте внутри верстки) ?> <div style="margin: 10px 0; font-weight: bold;"> Оценка загруженности: <?=htmlspecialcharsbx($arResult["INFO"]["UF_WORKLOAD_MARK"] ?? "—")?> </div>
  1. $arResult["INFO"]["UF_WORKLOAD_MARK"] — это лишь пример того, где может лежать ваша «оценка загруженности». Если у вас другая переменная или другой ключ массива — подставьте свой.

  2. Если данных пока нет, вы можете временно вывести просто статический текст:

    php
    <div>Оценка загруженности: 4 (пример)</div>

    А затем уже задействовать реальные данные, когда будете дорабатывать логику.

  3. Так как этот шаблон, судя по коду, в основном отвечает за общий список/сводку отчётов за неделю (а не за «детальную карточку»), то есть вероятность, что детальная часть (где показывается «Карточка отчёта») рендерится другим компонентом или через AJAX. Однако если вам нужно «просто попробовать» вывести свойство – это хороший старт.

  4. При желании можно оформить через языковые фразы (например, создать фразу в /lang/ru/component.php и вызвать её GetMessage('TMR_MY_WORKLOAD')), но для начала достаточно статичного текста «Оценка загруженности: ...».

Если вдруг в $arResult ещё нет поля UF_WORKLOAD_MARK (или другого для «оценки»), то при нынешней логике вы всё равно не увидите ничего. Но так вы хотя бы будете уверены, что шаблон «выводит» то, что ему пришло.

Вопрос

У меня вопрос именно как мне найти место ... т.е. где этот шаблон сам детального отчета сотрдника. может ты продумаеш путь как мы найдем место, а уже далее разберемся

Вопрос

/bitrix/tools/timeman.php?action=admin_report_full&site_id=s1

в ответе вижу:
'COMMENTS':'\n
<div class="timeman-comment">
\n
<span class="timeman-comment-avatar" style="background:url('/upload/resize_cache/main/9d8/b7so5x2j1ahx658q7usoymn534sqs71m/100_100_2/bird_black.png') no-repeat center center; background-size: cover;">
</span>
\n
<span class="timeman-comment-body">
\n
т.е. сразу постраение

Мысль

Locating the detailed report

Wondering how to pinpoint the "detailed employee report" template in Timeman. It seems Ajax, specifically "admin_report_full," might be responsible for rendering the detailed view on timeman.php.

Pulling in HTML content

OK, let's see. In Timeman, the detailed report's HTML comes from the "bitrix:timeman.topic.reviews" component. This suggests the user might want to tweak the comments or report's HTML layout directly.

Tracing HTML origin

Investigating how HTML for comments and details is generated reveals the "bitrix:timeman.topic.reviews" component as the source. This suggests the need to dig into how this component creates HTML outputs.

Tracing the template

OK, let me see. The detailed report in Timeman shows HTML from "bitrix:timeman.topic.reviews" via Bitrix's embedded component method. This suggests a deeper look into this process might be necessary.

Building the profile

Navigating the frontend, I found that the detailed report's main HTML is crafted using JavaScript from JSON objects, while 'COMMENTS' stem from the "bitrix:timeman.topic.reviews" component.

Crafting the layout

I'm figuring out how to tweak the detailed report layout. Overriding JavaScript that processes JSON input or tweaking the "bitrix:timeman.topic.reviews" component for comments might be key.

Inspecting main script

Mapping out the JavaScript snippet for 'admin_report_full' to reveal how the detailed report is constructed, particularly how 'REPORT', 'PLANS', 'FILES', 'TASKS', 'EVENTS', 'MARK' are handled.

Injecting data

I dug into retrieving JSON with 'INFO' and 'COMMENTS'. I traced the AJAX call in the JavaScript file, spotting how to add the extra data field dynamically.

Reasoned about locating detailed report template for 1m 13s

Ответ

В «коробочном» Битриксе для детализации отчёта (когда открывается карточка конкретного сотрудника и его отчёта) часто нет отдельного «.php-шаблона» компонента в классическом понимании, как в bitrix:timeman.report.weekly/templates/.default/template.php. Чаще эта детальная карточка:

  1. Формируется на сервере (через case 'admin_report_full' в /bitrix/tools/timeman.php), где собирается массив $res ( INFO, COMMENTS, и т.д. ), а затем отдается фронтенду в виде JSON или «HTML-кусков в JSON» (как вы видите по 'COMMENTS' => '<div class="...">' и проч.).

  2. На клиенте (в JavaScript) происходит AJAX-запрос к timeman.php?action=admin_report_full, и затем полученные данные вставляются в DOM (или всплывающее окно, или слайдер). То есть в большинстве случаев детальная карточка отчёта отрисовывается именно в JS. Поэтому классического template.php для детальной карточки вы можете и не найти – всё формируется «на лету» на клиентской стороне.

Как найти место, где формируется детальная карточка

  1. Посмотрите, кто отправляет AJAX-запрос на timeman.php?action=admin_report_full.

    • Откройте браузер DevTools → вкладка «Network».
    • Кликните по тому месту, где открывается детальная карточка.
    • Посмотрите, какой файл JS вызывает /bitrix/tools/timeman.php?action=admin_report_full.
      Обычно это что-то вроде timeman.report.js или script.js внутри bitrix:timeman.report.weekly.
      Либо ищите глобальным поиском по фразе admin_report_full.
  2. Посмотрите, как обрабатывается ответ. В коде JavaScript это будет примерно так:

    js
    BX.ajax({ url: '/bitrix/tools/timeman.php?action=admin_report_full', data: { /* ... */ }, method: 'POST', onsuccess: function(resJson) { var res = BX.parseJSON(resJson); // тут часто бывает log(res) или сразу вставка: document.getElementById('...').innerHTML = res.COMMENTS; // либо формируется верстка из res.INFO, res.TO, res.FROM и т.д. } });

    Именно в этом месте идёт сборка верстки детальной карточки: заголовок (REPORT_STRIP_TAGS, PLAN_STRIP_TAGS), поля «Оценка» (MARK), «Одобрил» и т.п.

  3. Если нужен именно блок комментариев (COMMENTS), то он вызывается через:

    php
    ob_start(); $APPLICATION->IncludeComponent("bitrix:timeman.topic.reviews", "", [ "REPORT_ID" => $ID ], false); $res["COMMENTS"] = ob_get_clean();

    Это стандартный компонент:
    /bitrix/components/bitrix/timeman.topic.reviews/
    И там же, в его templates/.default/template.php, формируется HTML комментариев.

  4. Для остальной части отчёта (сам текст, планы, оценки и т.д.) в текущем коде timeman.php мы не видим включения другого компонента. Скорее всего, верстка либо формируется «на лету» в JS, либо это какая-то старая логика CJSCore::Init(...). Поэтому искать стоит по ключам наподобие "res.INFO", "res.COMMENTS", "admin_report_full", "REPORT_STRIP_TAGS", "APPROVE_DATE" и т.п.`

  5. Если всё-таки у Bitrix есть заготовленный PHP-шаблон (переопределённый, например, в /local/components/...), вы найдёте его по тому, что в коде будет $APPLICATION->IncludeComponent("bitrix:timeman.report.edit", ...) или что-то похожее для «admin_report_full». Но из вашего фрагмента видно, что единственный $APPLICATION->IncludeComponent() ― это bitrix:timeman.topic.reviews (комментарии). Значит, остальная часть формируется вручную (через JSON + JS).


Подсказка для быстрого поиска

  • Шаг 1: Сделайте глобальный поиск по коду (PhpStorm / VSCode) на фразу admin_report_full.
  • Шаг 2: Откройте все найденные результаты: наверняка найдёте JavaScript, где указана строка action: 'admin_report_full'. Там будет коллбэк onsuccess: function(res){...}, внутри которого и есть отрисовка.
  • Шаг 3: Расширьте этот коллбэк, чтобы дополнительно вывести/вставить ваше поле «Оценка загруженности» (что придёт в res.INFO["UF_WORKLOAD_MARK"] или как вы его назовёте).

Таким образом, «детальный шаблон» сводится к тому JavaScript-куску, где вы распаковываете объект ответа и динамически вставляете его в HTML. Именно там отображаются все поля, которые вернулись с сервера.

Вопрос

Вот тут формируется ответ:
case 'admin_report_full':
$bReturnRes = true;

text
$ID = intval($_POST["report_id"] ?? 0); $USER_ID = intval($_POST["user_id"] ?? 0); $OBJID = intval($_POST["id"] ?? 0); $arAccessUsers = CTimeMan::GetAccess(); $bCanEditAll = in_array('*', $arAccessUsers['WRITE']); $bCanReadAll = in_array('*', $arAccessUsers['READ']); $bCanReadUser = (in_array($USER_ID, $arAccessUsers["READ"]) || $bCanReadAll); $bCanWriteUser = (in_array($USER_ID, $arAccessUsers["WRITE"]) || $bCanEditAll); if ($USER_ID > 0 && $bCanWriteUser && ($_POST["approve"] ?? null)) { if ($_POST["approve"] == "Y") { $dbrep = CTimeManReportFull::GetByID($ID); $rep = $dbrep->Fetch(); if (is_array($rep) && $USER_ID == $rep['USER_ID']) { $arFields = [ "MARK" => (in_array($_POST["mark"], ["G", "B", "N"]) !== false) ? $_POST["mark"] : "X", ]; if ($arFields["MARK"] != "X") { $arFields["APPROVER"] = $USER->GetID(); $arFields["APPROVE"] = "Y"; $arFields["APPROVE_DATE"] = ConvertTimeStamp(time(), "FULL"); } else { $arFields["APPROVE"] = "N"; $arFields["APPROVER"] = 0; $arFields["APPROVE_DATE"] = ""; } CTimeManReportFull::Update($ID, $arFields); $CACHE_MANAGER->Clean(CUserReportFull::getInfoCacheId($USER_ID), 'timeman_report_info'); CReportNotifications::MessageUpdate($ID, $rep, $arFields); } } } elseif (($_POST["mode"] ?? null) && $OBJID > 0) { $arFields = [ "UF_REPORT_PERIOD" => $_POST["mode"], "UF_TM_TIME" => (IsAmPmMode() ? convertTimeToMilitary($_POST["time"], 'H:MI T', 'HH:MI') : $_POST["time"]), "UF_TM_REPORT_DATE" => $_POST["date"], "UF_TM_DAY" => $_POST["day"], "ID" => $OBJID, ]; if ($_POST["object"] == "user") { $bCanEdit = ((in_array($OBJID, $arAccessUsers["WRITE"]) && $OBJID != $USER->GetID()) || $bCanEditAll || CTimeMan::IsAdmin() ); if ($bCanEdit) { $arReportUser = new CUserReportFull($OBJID); $res = $arReportUser->SetPeriod($arFields); $CACHE_MANAGER->Clean(CReportSettings::getSettingsCacheId($OBJID), 'timeman_report_settings'); $CACHE_MANAGER->Clean(CUserReportFull::getInfoCacheId($OBJID), 'timeman_report_info'); } } elseif ($_POST["object"] == "dep") { $arSubordination = CIntranetUtils::GetSubordinateDepartments($USER->GetID(), true); $bCanEdit = (in_array($OBJID, $arSubordination) || CTimeMan::IsAdmin()); if ($bCanEdit) { $res = CTimeManReportFull::SetPeriodSection($arFields); $CACHE_MANAGER->CleanDir('timeman_report_settings'); $CACHE_MANAGER->CleanDir('timeman_report_info'); } } } if ($bCanReadUser && !($_POST["mode"] ?? null)) { $dbRes = CUser::GetList( 'ID', 'ASC', ['ID' => $USER_ID], ['SELECT' => ['UF_*']] ); $arUser = $dbRes->GetNext(); $arUser['PHOTO'] = $arUser['PERSONAL_PHOTO'] > 0 ? CIntranetUtils::InitImage($arUser['PERSONAL_PHOTO'], 100, 100, BX_RESIZE_IMAGE_EXACT) : []; $arManagers = CTimeMan::GetUserManagers($USER_ID); $arManagers[] = $USER_ID; if (!is_array($arManagers) || count($arManagers) <= 0) { $arManagers = [$USER_ID]; } $user_url = COption::GetOptionString( 'intranet', 'path_user', '/company/personal/user/#USER_ID#/', ($_REQUEST['site_id'] ?? '') ); $dbManagers = CUser::GetList('ID', 'ASC', ['ID' => implode('|', $arManagers)]); $res["TO"] = []; $res["FROM"] = []; while ($manager = $dbManagers->Fetch()) { $manager['PHOTO'] = $manager['PERSONAL_PHOTO'] > 0 ? CIntranetUtils::InitImage($manager['PERSONAL_PHOTO'], 100, 100, BX_RESIZE_IMAGE_EXACT) : []; $arUserData = [ 'ID' => $manager['ID'], 'LOGIN' => $manager['LOGIN'], 'NAME' => CUser::FormatName(CSite::GetNameFormat(false), $manager, true, true), 'URL' => str_replace(['#ID#', '#USER_ID#'], $manager['ID'], $user_url), 'WORK_POSITION' => $manager['WORK_POSITION'], 'PHOTO' => $manager['PHOTO']['CACHE']['src'] ?? '', ]; if ($USER_ID == $arUserData["ID"]) { $res["FROM"] = $arUserData; } else { $res["TO"][] = $arUserData; } } if (count($res["TO"]) == 0) { $res["TO"][] = $res["FROM"]; } $arFilter = ["ID" => $ID, "USER_ID" => $USER_ID]; $arReportID = []; if ($_POST["empty_slider"] ?? null) { $arFilter = ["ACTIVE" => "Y", "USER_ID" => $USER_ID]; } $dbres = CTimeManReportFull::GetList(["USER_ID" => "asc", "ID" => "asc"], $arFilter); $report = null; while ($rep = $dbres->Fetch()) { $arReportID[] = $rep["ID"]; if ($rep["ID"] == $ID) { $report = $rep; } } $res["REPORT_LIST"] = $arReportID; if (is_array($report)) { $res["INFO"] = $report; if ($res["INFO"]['TASKS_ENABLED'] = (CBXFeatures::IsFeatureEnabled('Tasks') && CModule::IncludeModule('tasks'))) { $res["INFO"]['TASKS'] = unserialize($res["INFO"]['TASKS'] ?? '', ['allowed_classes' => false]); } else { unset($res["INFO"]['TASKS']); } if ($res["INFO"]['CALENDAR_ENABLED'] = CBXFeatures::IsFeatureEnabled('Calendar')) { $res["INFO"]['EVENTS'] = unserialize($res["INFO"]['EVENTS'] ?? '', ['allowed_classes' => false]); } else { unset($res["INFO"]['EVENTS']); } if ($res["INFO"]['FILES']) { $res["INFO"]['FILES'] = unserialize($res["INFO"]['FILES'] ?? '', ['allowed_classes' => false]); } $res["INFO"]['CAN_EDIT'] = ($arUser['ID'] != $USER->GetID()) && ($bCanEditAll || in_array($arUser['ID'], $arAccessUsers['WRITE'])); $res["INFO"]['CAN_EDIT_TEXT'] = ($report["APPROVE"] == "Y") ? "N" : "Y"; $culture = \Bitrix\Main\Application::getInstance()->getContext()->getCulture(); $dayMonthFormat = $culture->getDayMonthFormat(); if ($report["DATE_FROM"] != $report["DATE_TO"]) { $res["INFO"]["TEXT_TITLE"] = FormatDate($dayMonthFormat, MakeTimeStamp($report["DATE_FROM"])) . " - " . FormatDate($dayMonthFormat, MakeTimeStamp($report["DATE_TO"])); } else { $res["INFO"]["TEXT_TITLE"] = FormatDate($dayMonthFormat, MakeTimeStamp($report["DATE_TO"])); } $res["INFO"]["REPORT_STRIP_TAGS"] = strip_tags(nl2br($res["INFO"]["REPORT"])); $res["INFO"]["PLAN_STRIP_TAGS"] = strip_tags(nl2br($res["INFO"]["PLANS"])); $res["INFO"]["APPROVER_INFO"] = []; if (intval($res["INFO"]["APPROVER"]) > 0) { $res["INFO"]["APPROVE_DATE"] = FormatDate($DB->DateFormatToPHP(FORMAT_DATETIME), MakeTimeStamp($res["INFO"]["APPROVE_DATE"])); foreach ($res["TO"] as $manager) { if ($manager["ID"] == intval($res["INFO"]["APPROVER"])) { $res["INFO"]["APPROVER_INFO"] = $manager; break; } } if (!$res["INFO"]["APPROVER_INFO"]) { $dbaprrove = CUser::GetList('ID', 'ASC', ['ID' => intval($res["INFO"]["APPROVER"])]); if ($approver = $dbaprrove->Fetch()) { $approver['PHOTO'] = $approver['PERSONAL_PHOTO'] > 0 ? CIntranetUtils::InitImage($approver['PERSONAL_PHOTO'], 100, 100, BX_RESIZE_IMAGE_EXACT) : []; $res["INFO"]["APPROVER_INFO"] = [ 'ID' => $approver['ID'], 'LOGIN' => $approver['LOGIN'], 'NAME' => CUser::FormatName(CSite::GetNameFormat(false), $approver, true, true), 'URL' => str_replace(['#ID#', '#USER_ID#'], $approver['ID'], $user_url), 'WORK_POSITION' => $approver['WORK_POSITION'], 'PHOTO' => $approver['PHOTO']['CACHE']['src'], ]; } } } ob_start(); $APPLICATION->IncludeComponent( "bitrix:timeman.topic.reviews", "", [ "REPORT_ID" => $ID, ], false ); $res["COMMENTS"] = ob_get_contents(); ob_end_clean(); } } break; //carter

тут мы можем прост одобавить свойство? я так понимаю нет? если про визуальную часть

Мысль

Integrating new data

OK, let me see. The code constructs $res and envisions extending $res["INFO"] with "WORKLOAD" or "WORKLOAD_MARK" to include manager workload data in the JSON response.

Infusing visual data

OK, let me see: first, add the "WORKLOAD" property to $res["INFO"]. Then, front-end code should pull it from the AJAX response and securely insert it into the HTML.

Enhancing HTML integration

I’m considering adding the "WORKLOAD" property to $res["COMMENTS"] to ensure it shows up in the HTML. It might be more seamless to include it directly in the HTML-building logic.

Reasoned for 39 seconds

Ответ

Короткий ответ:
В этом месте (case 'admin_report_full') вы действительно можете добавить новое поле (например, $res["INFO"]["WORKLOAD_MARK"]) в итоговый массив, который уходит в ответ на AJAX-запрос. Но это ещё не означает, что поле автоматически отрисуется на экране. После этого PHP-кода включается JavaScript-логика, которая парсит полученный объект res (т.е. res["INFO"], res["COMMENTS"] и т.д.) и вставляет в DOM нужные куски HTML.

Чтобы «визуально» показать новое свойство, у вас есть два пути:


1. Добавить HTML прямо в res["COMMENTS"] (или ещё какой-нибудь ключ ответа)

Вы можете «прицепить» нужный фрагмент HTML к $res["COMMENTS"], так как он потом вставляется на страницу. Например, перед:

php
ob_start(); $APPLICATION->IncludeComponent( "bitrix:timeman.topic.reviews", "", [ "REPORT_ID" => $ID, ], false ); $res["COMMENTS"] = ob_get_contents(); ob_end_clean();

Можно сделать:

php
ob_start(); ?> <div style="font-weight: bold; margin-top: 10px;"> Оценка загруженности: <?=htmlspecialcharsbx($report["UF_WORKLOAD_MARK"] ?? "—")?> </div> <?php $htmlWorkload = ob_get_contents(); ob_end_clean(); // теперь прикрепим это к COMMENTS ob_start(); $APPLICATION->IncludeComponent(...); $res["COMMENTS"] = ob_get_contents(); ob_end_clean(); $res["COMMENTS"] .= $htmlWorkload;

Тогда в итоговом JSON-ответе ключ COMMENTS уже будет содержать и HTML с вашим новым свойством. На клиенте после AJAX-а этот HTML вставится на страницу (в том же месте, где вставляются комментарии).

Плюсы: Быстро и наглядно.
Минусы: Немного «хак» — приделываем верстку к блоку комментариев, что не всегда красиво.


2. Найти JS-код, который получает res["INFO"] и самому добавить вывод (лучший метод)

В большинстве случаев вся «детальная карточка отчёта» формируется так:

  1. PHP формирует массив (включая res["INFO"], res["COMMENTS"], res["TO"] и т. д.).
  2. Отправляет это в JSON-ответе (echo json_encode($res) или чем-то подобным).
  3. JavaScript ловит этот ответ, что-то вроде:
    js
    BX.ajax({ url: '/bitrix/tools/timeman.php?action=admin_report_full', method: 'POST', data: {...}, onsuccess: function(resText) { var res = BX.parseJSON(resText); // ... тут формируется интерфейс детального отчёта // res.INFO содержит все поля: REPORT, PLANS, MARK, APPROVE_DATE... } });
  4. В этом коллбэке на клиенте обычно пишется:
    js
    document.querySelector('#someBlock').innerHTML = res.INFO.REPORT_STRIP_TAGS; document.querySelector('#anotherBlock').innerHTML = res.COMMENTS; ...
    и так далее.

Чтобы «визуально» вывести UF_WORKLOAD_MARK, вы должны:

  • В PHP (в том фрагменте case 'admin_report_full') добавить в $res["INFO"]["WORKLOAD_MARK"] нужное значение (например, из $report["UF_WORKLOAD_MARK"]).
  • В JavaScript (после получения res) вставить это значение в нужное место DOM, например:
    js
    document.querySelector('#bx-tm-report-detail .workload-mark').textContent = res.INFO.WORKLOAD_MARK;
    или как в вашем коде устроено.

Так что надо (а) найти этот JS-файл (или inline-скрипт), где после AJAX вызывается admin_report_full. Чаще всего он где-то рядом с timeman.report.js, report.js, или в том же timeman.php (выше/ниже) есть <script>…</script>.

Совет: сделайте поиском по проекту admin_report_full и смотрите, где есть JavaScript, который делает BX.ajax на action=admin_report_full. Именно там происходит отрисовка полученных данных.


Итоговая схема

  1. Добавляем в PHP:

    php
    if (is_array($report)) { $res["INFO"] = $report; // ... // вот здесь: $res["INFO"]["WORKLOAD_MARK"] = $report["UF_WORKLOAD_MARK"]; // ... }
  2. На клиенте (JavaScript), там где выводятся поля отчёта, добавить логику, чтобы показать res.INFO.WORKLOAD_MARK. Например:

    js
    var container = document.getElementById('tm-report-full'); container.innerHTML += '<div>Оценка загруженности: ' + res.INFO.WORKLOAD_MARK + '</div>';

    Или другой метод, в зависимости от верстки.

  3. Если вы не находите код, который формирует HTML на клиенте (или это слишком сложно), можете «в лоб» сгенерировать HTML на PHP-стороне (как вариант 1) и впихнуть его в $res["COMMENTS"] или создать другой $res["DETAIL_HTML"], а на клиенте просто вставить res.DETAIL_HTML.

Таким образом, визуальную часть вы «рисуете» либо полностью в серверном рендере (через ob_start() и $res["COMMENTS"]), либо в клиентском JS, используя значение $res["INFO"]["WORKLOAD_MARK"].

Без изменения клиентского кода (JS) или без «прицепа» к $res["COMMENTS"] ваше поле просто останется в JSON-ответе, но не будет показано на странице.

Вопрос

как найти где у нас формируется запрос? BX.ajax

Мысль

Mapping out customization

I'm considering how to customize the user's Bitrix24 box, focusing on user reports. This involves understanding the current setup and exploring effective customization strategies.

Adjusting customizations

Let's start by tweaking the existing using user reports. This involves refining data presentation, refining formats, and figuring out how to make the reports more insightful for specific needs.

Mapping out customization

I’m thinking through the customization of user reports in Bitrix24, focusing on refining data presentation, analysis, and formatting for improved clarity and specific needs.

Enhancing customization

I’m refining the data presentation and analysis in user reports for a clearer view in Bitrix24. This leads me to think about improving attention to detail for specific needs.

Mapping out how to

I’m outlining the user's Bitrix24 customization request, focusing on refining data presentation, suggesting modifications, and identifying key adjustments for enhanced clarity and usability.

Tailoring user reports

I’m gathering insights to customize Bitrix24 user reports, focusing on refining data presentation, enhancing clarity, and ensuring specificity to meet the user's needs effectively.

Enhancing presentation

I’m mapping out how to improve the clarity of Bitrix24 user reports by refining data presentation techniques, introducing more detailed analysis, and implementing wider viewing options for better user insights.

Enhancing clarity

I’m improving the clarity of Bitrix24 user reports by refining data presentation, suggesting detailed analysis, and recommending wider viewing options to ensure usability and meet the user's needs effectively.

Reasoned for 9 seconds

Ответ

Чтобы найти, где формируется запрос BX.ajax в вашем проекте, нужно выполнить несколько шагов для поиска в коде и отладки с использованием браузерных инструментов. Вот подробный план:

Шаг 1: Найти вызов BX.ajax в JavaScript

  1. Поиск по коду проекта: В вашем проекте выполните поиск по ключевым словам BX.ajax или части запроса, например:

    js
    BX.ajax({ url: '/bitrix/tools/timeman.php?action=admin_report_full', });

    Это поможет вам найти место, где происходит вызов AJAX-запроса. Обычно запрос на timeman.php?action=admin_report_full будет в одном из JavaScript файлов, возможно, в компоненте или его шаблоне.

  2. Проверка с использованием инструментов разработчика: Если не удается найти запрос через поиск, можно использовать браузерные инструменты разработчика для отслеживания запросов:

    • Откройте страницу, на которой работает детальная карточка отчёта (где вы ожидаете увидеть AJAX-запрос).
    • Включите Network вкладку в Developer Tools (инструменты разработчика). В Google Chrome это делается через правый клик → «Inspect» → вкладка Network.
    • Обновите страницу или откройте нужный элемент (например, детальную карточку отчёта).
    • Когда запрос на timeman.php?action=admin_report_full отправится, он отобразится в списке запросов на вкладке Network.
    • Кликните на этот запрос в списке и посмотрите его Request URL и Response.

Шаг 2: Найти точку вызова на основе URL

Если вы уже видите запрос в инструментах разработчика, то посмотрите Request URL. Например, запрос будет выглядеть так:

/bitrix/tools/timeman.php?action=admin_report_full

Используйте этот URL в поиске по проекту. Например, можно выполнить поиск по файлу или по всему проекту:

php
action=admin_report_full

Это поможет вам найти, где именно в коде выполняется этот запрос. Находя это место, вы сможете понять, как передаются параметры в запросе (например, report_id, user_id, и другие).

Шаг 3: Исследовать JavaScript-код, который инициирует запрос

После того как вы нашли код, который отправляет запрос, вам нужно будет понять, как обрабатывается ответ и как происходит рендеринг данных на странице.

  1. Найдите код, который обрабатывает ответ от BX.ajax. Это будет выглядеть примерно так:

    js
    BX.ajax({ url: '/bitrix/tools/timeman.php?action=admin_report_full', data: { report_id: reportId, user_id: userId, approve: approveValue }, method: 'POST', onsuccess: function(response) { // Здесь будет обработка ответа от сервера var result = BX.parseJSON(response); // Например: document.getElementById('someElement').innerHTML = result.COMMENTS; } });
  2. Посмотрите, как данные вставляются в DOM: Ответ от сервера, скорее всего, будет содержать HTML (например, комментарии) или другие данные (INFO, REPORT, APPROVE, и т.д.). Обычно эти данные вставляются в нужный элемент на странице. Найдите этот участок кода и добавьте логику для вывода вашего поля «оценка загруженности».

Шаг 4: Использование точки прерывания в JavaScript (опционально)

  1. В браузере включите инструменты разработчика, перейдите на вкладку Sources.
  2. Воспользуйтесь точками прерывания (breakpoints) для того, чтобы поставить точку на метод BX.ajax, и когда он будет вызван, вы сможете увидеть стек вызовов и точку в коде, где это происходит.

Резюме:

  1. Используйте поиск по проекту с ключевыми словами BX.ajax, чтобы найти точку вызова в JavaScript.
  2. Воспользуйтесь инструментами разработчика в браузере, чтобы отслеживать сам запрос и его ответ.
  3. На основе найденного кода добавьте необходимую логику для отображения дополнительного свойства (например, «оценка загруженности») на странице.

Когда найдете место, где формируется и отправляется запрос BX.ajax, вы сможете точно понять, как обрабатываются данные и где внести изменения для отображения нового поля.

Вопрос

так а давай ты напишешь JS которыйй попробует перехватить или собрать нужную информацию

Мысль

Intercepting AJAX calls

I’m working through intercepting the admin_report_full request using BX.ajax. This involves wrapping BX.ajax to capture URLs containing "admin_report_full" and logging them for further analysis.

Taking a closer look

I’m working through hooking the onsuccess callback in BX.ajax to intercept responses for admin_report_full. This method allows for prior analysis and interaction with the response data before passing it to the original callback.

Reasoned for 12 seconds

Ответ

Ниже приведён пример «хака» на чистом JavaScript, который позволяет перехватить любой вызов BX.ajax и, в частности, подглядеть (и при желании модифицировать) запрос/ответ, связанный с admin_report_full. Это может пригодиться, когда вы не находите точное место в коде, но хотите поймать событие AJAX на лету.

Внимание: это именно «обёртка» (монки-патч) вокруг BX.ajax. Код не является «правильной» практикой для промышленного использования, потому что вмешивается во все AJAX-запросы BX.ajax. Но может помочь на этапе отладки и исследования.

Шаги использования

  1. Подключите этот скрипт где-то на странице (например, в самом низу перед </body>, либо через консоль браузера, либо через пользовательский скрипт).
  2. Откройте DevTools → вкладка «Console», чтобы видеть вывод console.log.
  3. Когда будет сделан вызов BX.ajax, наш код «подменит» функцию и сможет вывести в консоль данные запроса и ответа.

Пример кода

html
<script> (function() { // Сохраняем оригинальную функцию BX.ajax var originalBxAjax = BX.ajax; // Переопределяем BX.ajax своей функцией-обёрткой BX.ajax = function(config) { // Проверяем, подходит ли нам URL (например, action=admin_report_full) var isTimemanRequest = false; if (config.url && config.url.indexOf("admin_report_full") !== -1) { isTimemanRequest = true; console.log("[BX.ajax] Перехвачена отправка на admin_report_full:", config); } // Сохраняем оригинальный коллбэк 'onsuccess' (если он есть) var originalSuccess = config.onsuccess; // Создаём новую функцию, которая будет вызываться вместо оригинальной config.onsuccess = function(response) { if (isTimemanRequest) { console.log("[BX.ajax] Перехвачена ОТВЕТ для admin_report_full:", response); // Если ответ — это JSON-строка, можно сразу распарсить var data; try { data = BX.parseJSON(response); console.log("[BX.ajax] Распарсенные данные:", data); // Здесь вы можете, например, дополнительно вывести данные // или сохранить их, или модифицировать (если очень надо). // Пример: вывести в консоль оценку загруженности: if (data && data.INFO && data.INFO.WORKLOAD_MARK) { console.log("Оценка загруженности (WORKLOAD_MARK) =", data.INFO.WORKLOAD_MARK); } } catch (e) { console.warn("[BX.ajax] Ошибка парсинга JSON:", e); } } // Вызываем оригинальный onsuccess (чтобы логика Битрикс не сломалась) if (typeof originalSuccess === "function") { originalSuccess.call(this, response); } }; // Наконец, вызываем оригинальный BX.ajax с нашей «подправленной» конфигурацией return originalBxAjax.call(BX, config); }; })(); </script>

Как это работает

  1. Сохраняем ссылку на исходную функцию BX.ajax в переменную originalBxAjax.
  2. Заменяем BX.ajax на функцию, которая:
    • Смотрит, какой url используется.
    • При необходимости (например, если в url есть admin_report_full) выводит информацию в консоль.
    • Перезаписывает коллбэк onsuccess так, чтобы мы могли посмотреть/распарсить ответ.
    • Вызывает «старую» (originalBxAjax) функцию, чтобы не ломать внутренний функционал Bitrix.
  3. Теперь любые вызовы BX.ajax(...) (включая запросы к admin_report_full) будут проходить через нашу обёртку.

Что дальше?

  • Если вы просто хотите понять, какие данные уходят на сервер (POST, GET) и что приходит назад, такой скрипт даст вам возможность это увидеть в консоли, не копаясь глубоко в коде.
  • Как только вы найдёте, кто именно вызывает BX.ajax({ url: '…admin_report_full…' }), вы сможете перейти в тот участок кода (в JS-файле или в скрипте) и там точечно доработать логику вывода нового свойства.
  • Если нужно «правильно» добавить вывод «Оценки загруженности» в интерфейс, то лучше всё-таки открыть оригинальный код, где формируется детальная карточка, и дописать там свой innerHTML или appendChild, чем полагаться на глобальный перехват. Но для отладки / промежуточного этапа такой метод с «монки-патчем» довольно удобен.

Вопрос

Uncaught TypeError: t.ajax.prepareData is not a function
at t.timeman_query (core_timeman.js:43:20)
at t.JSTimeManReportFullForm.Click (core_timeman.js:6735:6)
at t.ReportSlider.ShowReport (core_timeman.js:7399:19)
at t.StartSlider (core_timeman.js:7276:24)
at HTMLTableCellElement.<anonymous> (script.js:880:7)

Мысль

Evaluating the issue

I’m mapping out the cause of the error, likely due to replacing "BX.ajax" or "t.ajax" with a different function, leading to a mismatch in methods.

Ensuring compatibility

I’m considering a safer approach to compatibility by wrapping or passing the original "BX.ajax" object, preventing issues like the missing "prepareData" function. This method should maintain functionality and stability.

Ensuring method retention

I'm digging into partial override or wrapping to keep methods intact. This approach maintains compatibility and prevents errors like the missing "prepareData" function, ensuring smooth operation.

Reasoned for 39 seconds

Ответ

Ошибка вида:

text
Uncaught TypeError: t.ajax.prepareData is not a function at t.timeman_query (core_timeman.js:43:20) ...

чаще всего возникает, когда вы переопределяете или «монки-патчите» (monkey-patch) BX.ajax (или другой объект), но при этом затираете некоторые методы/свойства, которые Bitrix ожидает от объекта BX.ajax.

В core_timeman.js (или другом JS-файле Bitrix) есть вызов типа:

js
t.ajax.prepareData(...)

или

js
BX.ajax.prepareData(...)

Если после вашей переопределённой функции больше нет метода prepareData, выбрасывается ошибка ... is not a function.


Почему это происходит?

  1. В стандартном Bitrix BX.ajax — это не просто функция, а объект со своими методами, например:

    • BX.ajax(...);
    • BX.ajax.prepareData(...);
    • BX.ajax.submitAjax(...);
    • и т.п.
  2. Если вы написали код вроде:

    js
    (function() { var originalBxAjax = BX.ajax; BX.ajax = function(config) { // ... // ваш код // ... return originalBxAjax.call(BX, config); }; })();

    Вы тем самым превратили BX.ajax в «чистую» функцию, у которой нет дополнительных методов (prepareData, submitAjax, и т.д.), которые были у старого объекта BX.ajax.

  3. Как результат, когда Bitrix (или ваш код) делает BX.ajax.prepareData(...), вы получаете TypeError.


Как правильно «обёрнуть» BX.ajax, не потеряв методы?

Ниже пример кода, который сохраняет все статические методы и свойства BX.ajax, но позволяет вам «подменить» основной вызов для целей логирования или отладки.

html
<script> (function() { // 1) Сохраняем старый объект (не просто функцию, а весь объект со свойствами) var oldBxAjax = BX.ajax; // 2) Создаём обёртку — функцию, которую мы будем вызывать, когда кто-то вызывает BX.ajax(...) function newAjaxWrapper(config) { // Здесь можно «посмотреть» url, data, method и т.д. if (config && config.url && config.url.indexOf('admin_report_full') !== -1) { console.log('[newAjaxWrapper] Перехваченный config:', config); } // Сохраняем исходный onsuccess var originalSuccess = config.onsuccess; config.onsuccess = function(response) { // Тоже можем логировать ответ console.log('[newAjaxWrapper] Ответ от сервера:', response); // Вызываем старый коллбэк, чтобы ничего не ломать if (typeof originalSuccess === 'function') { originalSuccess.call(this, response); } }; // Возвращаем результат старой функции BX.ajax (которая тоже может быть функцией) return oldBxAjax.call(BX, config); } // 3) Копируем **все** свойства со старого объекта BX.ajax на нашу новую функцию, // чтобы методы типа prepareData, submitAjax и прочие не пропали. for (var key in oldBxAjax) { if (Object.prototype.hasOwnProperty.call(oldBxAjax, key)) { newAjaxWrapper[key] = oldBxAjax[key]; } } // 4) Теперь подменяем BX.ajax на нашу обёртку BX.ajax = newAjaxWrapper; })(); </script>

Пояснение к этому коду

  1. Мы берём исходный объект BX.ajax (у которого есть методы).
  2. Создаём функцию newAjaxWrapper, которая перехватывает вызовы BX.ajax(config).
  3. Внутри этой функции делаем, что нужно (логируем, проверяем config.url, модифицируем onsuccess и т.п.).
  4. Вызываем oldBxAjax.call(...), чтобы не сломать исходную логику.
  5. Важно: копируем свойства (все for in) со старого BX.ajax на нашу новую функцию (или объект). Иначе BX.ajax.prepareData и другие методы будут утеряны.
  6. Присваиваем BX.ajax = newAjaxWrapper;. Теперь вызовы BX.ajax(...) идут через нашу обёртку, но BX.ajax.prepareData и т.д. по-прежнему существуют.

Таким образом, код типа core_timeman.js: t.ajax.prepareData(...) не сломается, потому что newAjaxWrapper.prepareData = oldBxAjax.prepareData.


Если у вас всё равно не BX.ajax, а t.ajax

Иногда в коде Bitrix (особенно в «коробочной» части для timeman) используется объект вида:

js
t = { ajax: BX.ajax, ... };

или даже собственный метод t.timeman_query = function(...) { ... }, который внутри всё равно вызывает BX.ajax(...). Если у вас конкретно в коде t.ajax.prepareData (а не BX.ajax.prepareData), убедитесь, что t.ajax = BX.ajax после вашей подстановки.

Если t.ajax сохранялся в переменную до вашего переопределения BX.ajax, то, возможно, t.ajax ссылается на «уже испорченную» функцию.
В таком случае придётся либо:

  • Переопределять t.ajax аналогичным способом.
  • Либо убедиться, что t.ajax всегда берёт актуальный BX.ajax.

Вывод

Чтобы не словить TypeError: ... is not a function, нужно не терять исходные методы BX.ajax. Правильный подход – это аккуратная «обёртка» (wrapper), где вы:

  1. Сохраняете ссылку на старый объект BX.ajax.
  2. Создаёте новый объект или функцию, возвращающую oldBxAjax.call(...).
  3. Копируете все свойства и методы (включая prepareData) на вашу новую обёртку.
  4. Присваиваете BX.ajax = newAjaxWrapper;.

Тогда ваш патч будет работать, не ломая стандартный функционал Bitrix.

Вопрос

[newAjaxWrapper] Перехваченный config: {method: 'POST', dataType: 'json', url: '/bitrix/tools/timeman.php?action=admin_report_full…ite_id=s1&sessid=609982bd84c499969d59fd8234ef1c7b', data: 'report_id=5&user_id=1&empty_slider=Y&device=browser', onsuccess: ƒ, …}
VM16044:18 [newAjaxWrapper] Ответ от сервера: {TO: Array(1), FROM: {…}, REPORT_LIST: Array(1), INFO: {…}, COMMENTS: '\n\t\t\t\t\t\t\t<div class="timeman-comment">\n\t\t\t\t<span cl…\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t\t\t'}

Мысль

Tracking configuration details

I’m looking at console logs to spot the config object. It shows url and data, leading to TO, FROM, REPORT_LIST, INFO, and COMMENTS.

Demonstrating integration

I'm providing a code snippet to log the response. This includes inserting the new field into COMMENTS or the DOM, guiding through integrating or patching changes.

Integrating custom fields

I’m exploring how to integrate a new custom field or property, like INFO.OURMARK = 5;, into the returned object. This involves either adding it to the final HTML or as a DOM element.

Reasoned for 12 seconds

Ответ

Из логов в консоли видно, что обёртка для BX.ajax успешно перехватывает запрос и выводит ответ. То есть теперь вы видите в консоли:

  1. Конфиг (параметры) запроса, который уходит на admin_report_full.
  2. Ответ (JSON, где есть поля TO, FROM, REPORT_LIST, INFO, COMMENTS и т.д.).

Пример лога у вас такой:

text
[newAjaxWrapper] Перехваченный config: {...} [newAjaxWrapper] Ответ от сервера: { TO: [...], FROM: {...}, REPORT_LIST: [...], INFO: {...}, COMMENTS: '...HTML-строка...' }

Это означает, что вы уже можете исследовать, какие конкретно поля приходят в res.INFO (или res.COMMENTS) для нужного отчёта.


Что дальше делать с этими данными?

1. Посмотреть, есть ли там ваше новое поле

Если вы на серверной стороне (в timeman.php, блок admin_report_full) добавили, например:

php
$res["INFO"]["WORKLOAD_MARK"] = $report["UF_WORKLOAD_MARK"];

или какое-то другое пользовательское поле, теперь вы можете убедиться, что оно реально приходит в ответе.
В консоли вы увидите res.INFO.WORKLOAD_MARK: 3 (или то значение, что есть в БД).

Если поля нет — значит, нужно доработать серверную часть (т.е. создать/заполнить $res["INFO"]["WORKLOAD_MARK"]).

2. Отобразить поле на странице

Сейчас в консоли вы видите ответ, но на страницу оно не выводится, так как клиентский код Bitrix (или ваш) не знает, что нужно где-то «нарисовать» это поле.

Есть два варианта, где «подключиться» к выводу:

  1. В вашей обёртке ( newAjaxWrapper ) – вы можете «дополнить» onsuccess, после того как получили res. Например:

    js
    // Ваш обёрнутый onsuccess config.onsuccess = function(response) { var data = BX.parseJSON(response); // Например, выводим в консоль console.log('[newAjaxWrapper] data:', data); if (data && data.INFO && data.INFO.WORKLOAD_MARK) { // Ищем какой-то элемент на странице, куда вставить var container = document.getElementById('myCustomContainer'); if (container) { container.innerHTML = "Оценка загруженности: " + data.INFO.WORKLOAD_MARK; } } // Не забываем вызвать исходный коллбэк, чтобы Битрикс не сломался if (typeof originalSuccess === 'function') { originalSuccess.call(this, response); } };
    • Так вы «подмешиваетесь» к существующей логике, оставляя остальной интерфейс нетронутым.
    • Плюс: не нужно менять оригинальные JS-файлы Bitrix.
    • Минус: это неофициальный метод, который может потребовать поддержки при обновлениях.
  2. В «родном» JS-коде, который обрабатывает ответ от admin_report_full.

    • То есть найти там, где у Bitrix (или у вашего кастомного скрипта) используется BX.ajax({...onsuccess: function(resText) {...}}) без вашей обёртки.
    • Добавить туда строку вида:
      js
      document.querySelector("#somePlace").innerHTML = "Моя оценка: " + res.INFO.WORKLOAD_MARK;
    • Это более «чистый» путь, если вы точно знаете место, где формируется детальная карточка. Но нужно найти и править исходный JS-файл (что при обновлениях коробки может конфликтовать).

Что, если я хочу вставить поле внутрь уже имеющегося HTML (например, рядом с комментариями)?

Если посмотрите, как Bitrix вставляет res.COMMENTS, то увидите что-то вроде:

js
document.getElementById('timeman-comments-container').innerHTML = res.COMMENTS;

или аналог. В таком случае, чтобы прямо вместе с комментариями появился ваш блок, можно:

  • Сформировать HTML на стороне PHP и «приделать» его к $res["COMMENTS"].
    Например:

    php
    ob_start(); ?> <div class="my-extra-field"> Оценка загруженности: <?=htmlspecialcharsbx($report["UF_WORKLOAD_MARK"])?> </div> <?php $htmlExtra = ob_get_clean(); $res["COMMENTS"] = $res["COMMENTS"] . $htmlExtra;

    Тогда в ответе COMMENTS уже будет содержать и блок «Оценка загруженности», и на клиенте оно автоматически впечатается в страницу вместе с комментариями.

  • Или перехватить res.COMMENTS в onsuccess и самому конкатенировать:

    js
    data.COMMENTS += '<div class="my-extra-field">Оценка: ' + data.INFO.WORKLOAD_MARK + '</div>'; document.getElementById('timeman-comments-container').innerHTML = data.COMMENTS;

В итоге вы получаете нужное поле прямо в том месте, где рендерятся комментарии.


Итого

  • Вы уже видите, что запрос перехватился, и ответ приходит в консоль.
  • Теперь дело за малым: решить, где (на сервере или в JS) «дописать» HTML/логика вывода нового поля в интерфейс.
  • Самая простая доработка, не требующая поиска JS-логики, – это приделать HTML к $res["COMMENTS"] на сервере и тем самым встроить ваш новый блок туда, где Bitrix уже вставляет COMMENTS.

Либо искать «родной» код JavaScript, где обрабатывается res.INFO и дописывать туда innerHTML.

Главное: теперь, благодаря «newAjaxWrapper», вы точно знаете, что запрос улетает, что приходит, и можете отследить все поля для детальной карточки отчёта.

Вопрос

окей APPLICATION>IncludeComponent("bitrix:timeman.topic.reviews","",["REPORTID"=>APPLICATION->IncludeComponent( "bitrix:timeman.topic.reviews", "", [ "REPORT_ID" => ID,
],
false
);
res["COMMENTS"]=obgetcontents();?><divclass="myextrafield">Оценказагруженности:<?=htmlspecialcharsbx(res["COMMENTS"] = ob_get_contents(); ?> <div class="my-extra-field"> Оценка загруженности: <?=htmlspecialcharsbx(report["UF_WORKLOAD_MARK"])?>
</div>
<?php
htmlExtra=obgetclean();htmlExtra = ob_get_clean(); res["COMMENTS"] = res["COMMENTS"].res["COMMENTS"] . htmlExtra;
ob_end_clean();
}
}

вариант стработал. давай только его разовем сделаем тут выподающий список использую UI битрикса с оценкой от 1 до 5

Мысль

Understanding the request

I’m thinking through the user's request about customizing reports in Bitrix24 to better meet their needs. It’s interesting to see how this aligns with enhancing their system's functionality.

Clarifying customization needs

Addressing the user's request to start customizing reports in Bitrix24, I’m thinking about the best approach to guide them effectively.

Addressing customization

I'm responding to a request to start customizing reports in Bitrix24, focusing on the best approach to guide the user effectively in enhancing their system's functionality.

Formulating the response

I’m crafting a response to the user's request for customizing reports in Bitrix24, ensuring a clear and effective guide to enhance their system's functionality.

Implementing customization

I’m outlining steps to customize Bitrix24 reports, ensuring a clear and effective guide. This involves mapping out key points and providing detailed instructions.

Crafting the response

I’m crafting a thoughtful response to the user's request for customizing reports in Bitrix24, ensuring clarity and effectiveness in guiding them through the customization process.

Crafting the approach

I’m preparing a response to the user’s request for customizing reports in Bitrix24, focusing on clarity and providing a step-by-step guide to enhance their system.

Mapping the response

I’m outlining a response for customizing reports in Bitrix24, aiming to ensure clarity and provide a step-by-step guide for the user.

Reasoned for 7 seconds

Ответ

Чтобы добавить выпадающий список с оценкой от 1 до 5, используя UI компоненты Битрикса, нам нужно будет использовать компонент bitrix:ui.select или похожий компонент, который предоставляет интерфейс для выбора значений в виде списка.

Вот шаги по расширению текущего кода с выпадающим списком для оценки загруженности.

Шаг 1: Вставка выпадающего списка

  1. Добавим в блок с оценкой загруженности выпадающий список (select).
  2. Обработаем выбор из списка на стороне сервера (через форму).
  3. Вставим это в COMMENTS, чтобы отобразить в интерфейсе.

Вот пример, как можно это реализовать:

Развёрнутый пример:

php
$APPLICATION->IncludeComponent( "bitrix:timeman.topic.reviews", "", [ "REPORT_ID" => $ID, ], false ); // Получаем текущую оценку, если она уже есть $currentWorkloadMark = $report["UF_WORKLOAD_MARK"] ?? 0; // Формируем HTML для выпадающего списка с оценкой ?> <div class="my-extra-field"> <label for="workload_mark"><?= GetMessage("TMR_WORKLOAD_MARK_LABEL") ?>:</label> <select id="workload_mark" name="UF_WORKLOAD_MARK"> <option value="1" <?= ($currentWorkloadMark == 1) ? 'selected' : '' ?>>1</option> <option value="2" <?= ($currentWorkloadMark == 2) ? 'selected' : '' ?>>2</option> <option value="3" <?= ($currentWorkloadMark == 3) ? 'selected' : '' ?>>3</option> <option value="4" <?= ($currentWorkloadMark == 4) ? 'selected' : '' ?>>4</option> <option value="5" <?= ($currentWorkloadMark == 5) ? 'selected' : '' ?>>5</option> </select> </div> <?php $htmlExtra = ob_get_clean(); // Добавляем новый блок с оценкой к комментариям $res["COMMENTS"] = $res["COMMENTS"] . $htmlExtra; ob_end_clean();

Пояснения:

  1. <select> с оценками от 1 до 5: Мы создаём выпадающий список с пятью значениями. Если в $report["UF_WORKLOAD_MARK"] уже есть значение (например, 3), оно будет установлено как выбранное. Для этого используется конструкция <?= ($currentWorkloadMark == 3) ? 'selected' : '' ?>.
  2. GetMessage("TMR_WORKLOAD_MARK_LABEL"): Это пример локализованной строки для метки. Замените на вашу локализацию, например: Оценка загруженности.
  3. Мы добавляем этот HTML код в $res["COMMENTS"], чтобы он был отображен вместе с комментариями отчёта.

Шаг 2: Обработка выбора на сервере

Чтобы сохранить выбор, необходимо добавить обработку, когда пользователь выбрал оценку и отправил форму. В вашем коде, после того как форма будет отправлена, можно будет обработать поле UF_WORKLOAD_MARK и сохранить его в БД.

Пример:

php
if ($_POST["UF_WORKLOAD_MARK"]) { $workloadMark = intval($_POST["UF_WORKLOAD_MARK"]); // Проверяем, что это значение от 1 до 5 if ($workloadMark >= 1 && $workloadMark <= 5) { // Сохраняем в БД $arFields = [ "UF_WORKLOAD_MARK" => $workloadMark, ]; CTimeManReportFull::Update($ID, $arFields); } }
  • $_POST["UF_WORKLOAD_MARK"] — это значение, выбранное пользователем в выпадающем списке.
  • Мы проверяем, что оно в пределах от 1 до 5.
  • После этого обновляем отчет с новым значением оценки.

Шаг 3: Добавление UI компонентов (по желанию)

Для улучшенного интерфейса можно использовать UI компоненты Bitrix, например, компонент bitrix:ui.select, который позволяет стилизовать выпадающий список и даёт дополнительные возможности для работы с компонентами.

Вот как это может выглядеть:

php
// Используем UI компонент Bitrix для выбора оценки use Bitrix\Main\UI\Extension; Extension::load(["ui.select"]); ?> <div class="my-extra-field"> <label for="workload_mark"><?= GetMessage("TMR_WORKLOAD_MARK_LABEL") ?>:</label> <select id="workload_mark" name="UF_WORKLOAD_MARK" class="ui-select"> <option value="1" <?= ($currentWorkloadMark == 1) ? 'selected' : '' ?>>1</option> <option value="2" <?= ($currentWorkloadMark == 2) ? 'selected' : '' ?>>2</option> <option value="3" <?= ($currentWorkloadMark == 3) ? 'selected' : '' ?>>3</option> <option value="4" <?= ($currentWorkloadMark == 4) ? 'selected' : '' ?>>4</option> <option value="5" <?= ($currentWorkloadMark == 5) ? 'selected' : '' ?>>5</option> </select> </div> <?php $htmlExtra = ob_get_clean(); $res["COMMENTS"] = $res["COMMENTS"] . $htmlExtra; ob_end_clean();
  • Здесь мы используем класс ui-select из UI библиотеки Bitrix, что добавляет стилизацию для вашего выпадающего списка.
  • Не забудьте, что UI компоненты нужно сначала загрузить с помощью Extension::load.

Итог:

  1. Мы добавили выпадающий список с оценками от 1 до 5 в комментарии отчёта, который интегрируется с Bitrix.
  2. Добавлена обработка выбора на сервере.
  3. При необходимости, можно использовать UI компоненты Bitrix для улучшенной стилизации и функционала.

Этот подход позволит вам эффективно собирать и отображать оценки загруженности для отчётов сотрудников.

Вопрос

давай сделаем с кнопкой "Оценить" (пока без обработчика)

Мысль

Crafting the interface

I’m working through a select menu and a "Оценить" button, aiming for a user-friendly design. The select menu will range from 1 to 5, allowing for dynamic interaction.

Reasoned for 4 seconds

Ответ

Ниже пример, как можно вывести выпадающий список с оценками (1–5) и кнопку «Оценить», не сохраняя пока результат в БД. Мы просто вставляем эту верстку (вместе со скриптом) в конец $res["COMMENTS"], чтобы она появилась в детальной карточке (там же, где выводятся комментарии).

php
// Предположим, что мы находимся внутри if (is_array($report)) { ... } и уже делаем ob_start(); ob_start(); // Получим текущую (если есть) оценку из отчёта. Если нет, будет 0 или пусто. $currentWorkloadMark = (int)($report["UF_WORKLOAD_MARK"] ?? 0); ?> <div class="my-extra-field" style="margin-top: 10px;"> <label for="workload_mark" style="display:inline-block; width:140px;"> <?= htmlspecialcharsbx("Оценка загруженности:") ?> </label> <select id="workload_mark" name="UF_WORKLOAD_MARK"> <option value="1" <?= ($currentWorkloadMark === 1 ? 'selected' : '') ?>>1</option> <option value="2" <?= ($currentWorkloadMark === 2 ? 'selected' : '') ?>>2</option> <option value="3" <?= ($currentWorkloadMark === 3 ? 'selected' : '') ?>>3</option> <option value="4" <?= ($currentWorkloadMark === 4 ? 'selected' : '') ?>>4</option> <option value="5" <?= ($currentWorkloadMark === 5 ? 'selected' : '') ?>>5</option> </select> <button type="button" id="btnEvaluate" style="margin-left: 10px;"> <?= htmlspecialcharsbx("Оценить") ?> </button> </div> <script> // Простой скрипт для демонстрации – пока просто выводим выбранное значение в консоль document.addEventListener('DOMContentLoaded', function() { var btn = document.getElementById('btnEvaluate'); btn.addEventListener('click', function() { var select = document.getElementById('workload_mark'); var mark = select.value; console.log("Нажали 'Оценить'. Выбранная оценка:", mark); // Здесь можно добавить AJAX или другую логику сохранения }); }); </script> <?php // Получим HTML, сформированный выше $htmlExtra = ob_get_clean(); // Теперь добавляем его к существующим $res["COMMENTS"] $res["COMMENTS"] .= $htmlExtra; // ob_end_clean() уже есть в вашем коде после этого

Что делает этот код

  1. ob_start() / ob_get_clean(): собирает HTML в буфер, чтобы потом дописать к $res["COMMENTS"].
  2. Выпадающий список (<select>) с оценками от 1 до 5.
    • Если у отчёта уже есть значение UF_WORKLOAD_MARK (например, 3), мы отмечаем его selected.
  3. Кнопка «Оценить» ( type="button" ) не вызывает перезагрузку, а просто срабатывает скрипт.
  4. JavaScript: слушает событие click по кнопке, получает текущее значение из списка и выводит его в консоль.
    • Тут вы можете добавить свою логику (например, вызывать AJAX, передавать выбранную оценку на сервер и т.д.).

Дальнейшие шаги

  • Чтобы действительно сохранить эту оценку в БД, нужно добавить обработку на серверной стороне (например, в том же case 'admin_report_full'), которая будет принимать $_POST["UF_WORKLOAD_MARK"] и вызывать CTimeManReportFull::Update($ID, ["UF_WORKLOAD_MARK" => $value]).
  • Для этого можно либо:
    • Повесить onchange/onclick JS и отправлять запрос через BX.ajax.
    • Либо сделать небольшую форму <form> ... </form> с method="POST" (или onsubmit-JS).
  • Но на данном этапе мы просто показали, как сделать кнопку «Оценить» и вывод выпадающего списка рядом с комментариями.

Поделиться этим Q&A