Protokoll #11709

ID11709
Zeitstempel2025-12-23 21:00:21.908871
Clientroot
IP
Warning: Undefined array key "request_ip" in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 13

Deprecated: htmlspecialchars(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 13
Modellclaude-sonnet-4-20250514
Statuscompleted
Tokens2,716 (Input: 0, Output: 0)
Dauer85 ms
Request-Zeit
Warning: Undefined array key "request_timestamp" in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 18
Response-Zeit-

Warning: Undefined array key "error_message" in /var/www/dev.campus.systemische-tools.de/src/View/protokoll/show.php on line 22

Request

{
    "event": "PreToolUse",
    "tool_name": "Read",
    "tool_input": {
        "file_path": "\/var\/www\/dev.campus.systemische-tools.de\/public\/js\/components\/data-table.js"
    }
}

Response

{
    "tool_response": {
        "type": "text",
        "file": {
            "filePath": "\/var\/www\/dev.campus.systemische-tools.de\/public\/js\/components\/data-table.js",
            "content": "\/**\n * DataTable Component with Pagination\n * Contract: js-browser-architecture-contract_v2.yaml, html-tables-contract_v1.0.yaml\n * Principles: DRY, KISS, OOP, SRP, YAGNI\n *\/\nimport { domAdapter } from \"..\/adapters\/domAdapter.js\";\nimport { eventAdapter } from \"..\/adapters\/eventAdapter.js\";\n\nclass DataTable {\n  constructor(tableId, options, deps) {\n    this.deps = deps;\n    this.cleanupFunctions = [];\n    this.table = domAdapter.getElementById(tableId);\n\n    if (this.table === null) {\n      return;\n    }\n\n    this.tbody = domAdapter.querySelector(this.table, \"tbody\");\n    this.headers = domAdapter.querySelectorAll(this.table, \"th[data-sort]\");\n    this.rows = [];\n    this.sortColumn = null;\n    this.sortDirection = \"asc\";\n    this.searchInput = options.searchInput !== undefined\n      ? domAdapter.getElementById(options.searchInput)\n      : null;\n    this.filters = options.filters !== undefined ? options.filters : {};\n\n    \/\/ Pagination\n    this.pageSize = options.pageSize !== undefined ? options.pageSize : 20;\n    this.currentPage = 1;\n    this.paginationContainer = null;\n\n    this.setup();\n  }\n\n  setup() {\n    this.cacheRows();\n    this.bindSorting();\n    this.bindSearch();\n    this.bindFilters();\n    this.createPaginationUI();\n    this.render();\n  }\n\n  cacheRows() {\n    const rows = domAdapter.querySelectorAll(this.tbody, \"tr\");\n    this.rows = Array.from(rows).map((row) => {\n      const cells = domAdapter.querySelectorAll(row, \"td\");\n      return {\n        element: row,\n        data: Array.from(cells).map((td) =>\n          domAdapter.getTextContent(td).trim().toLowerCase()\n        ),\n        raw: Array.from(cells).map((td) =>\n          domAdapter.getTextContent(td).trim()\n        )\n      };\n    });\n  }\n\n  bindSorting() {\n    this.headers.forEach((header, index) => {\n      domAdapter.setStyle(header, \"cursor\", \"pointer\");\n\n      const cleanup = eventAdapter.on(\n        header,\n        \"click\",\n        () => this.sort(index, domAdapter.getAttribute(header, \"data-sort\")),\n        this.deps,\n        \"DATATABLE_SORT\",\n        \"datatable\"\n      );\n      this.cleanupFunctions.push(cleanup);\n    });\n  }\n\n  sort(columnIndex, columnName) {\n    const direction =\n      this.sortColumn === columnName && this.sortDirection === \"asc\"\n        ? \"desc\"\n        : \"asc\";\n    this.sortColumn = columnName;\n    this.sortDirection = direction;\n\n    this.headers.forEach((h) => {\n      domAdapter.removeClass(h, \"sort-asc\");\n      domAdapter.removeClass(h, \"sort-desc\");\n    });\n\n    const activeHeader = domAdapter.querySelector(\n      this.table,\n      `th[data-sort=\"${columnName}\"]`\n    );\n    if (activeHeader !== null) {\n      domAdapter.addClass(activeHeader, `sort-${direction}`);\n    }\n\n    this.rows.sort((a, b) => {\n      const valA = a.raw[columnIndex] !== undefined ? a.raw[columnIndex] : \"\";\n      const valB = b.raw[columnIndex] !== undefined ? b.raw[columnIndex] : \"\";\n\n      const numA = parseFloat(valA);\n      const numB = parseFloat(valB);\n      if (!isNaN(numA) && !isNaN(numB)) {\n        return direction === \"asc\" ? numA - numB : numB - numA;\n      }\n\n      return direction === \"asc\"\n        ? valA.localeCompare(valB, \"de\")\n        : valB.localeCompare(valA, \"de\");\n    });\n\n    this.currentPage = 1;\n    this.render();\n  }\n\n  bindSearch() {\n    if (this.searchInput === null) {\n      return;\n    }\n\n    const cleanup = eventAdapter.on(\n      this.searchInput,\n      \"input\",\n      () => {\n        this.currentPage = 1;\n        this.applyFilters();\n      },\n      this.deps,\n      \"DATATABLE_SEARCH\",\n      \"datatable\"\n    );\n    this.cleanupFunctions.push(cleanup);\n  }\n\n  bindFilters() {\n    Object.entries(this.filters).forEach(([id, _columnIndex]) => {\n      const select = domAdapter.getElementById(id);\n      if (select !== null) {\n        const cleanup = eventAdapter.on(\n          select,\n          \"change\",\n          () => {\n            this.currentPage = 1;\n            this.applyFilters();\n          },\n          this.deps,\n          \"DATATABLE_FILTER\",\n          \"datatable\"\n        );\n        this.cleanupFunctions.push(cleanup);\n      }\n    });\n  }\n\n  applyFilters() {\n    const searchTerm = this.searchInput !== null\n      ? this.searchInput.value.toLowerCase()\n      : \"\";\n\n    this.rows.forEach((row) => {\n      let visible = true;\n\n      if (searchTerm !== \"\") {\n        visible = row.data.some((cell) => cell.includes(searchTerm));\n      }\n\n      Object.entries(this.filters).forEach(([id, columnIndex]) => {\n        const select = domAdapter.getElementById(id);\n        if (select !== null && select.value !== \"\") {\n          visible = visible && row.data[columnIndex] === select.value.toLowerCase();\n        }\n      });\n\n      row.visible = visible;\n    });\n\n    this.render();\n  }\n\n  createPaginationUI() {\n    this.paginationContainer = domAdapter.createElement(\"div\");\n    domAdapter.addClass(this.paginationContainer, \"pagination\");\n    this.table.parentNode.insertBefore(\n      this.paginationContainer,\n      this.table.nextSibling\n    );\n  }\n\n  getVisibleRows() {\n    return this.rows.filter((row) => row.visible !== false);\n  }\n\n  getTotalPages() {\n    const visibleRows = this.getVisibleRows();\n    return Math.ceil(visibleRows.length \/ this.pageSize);\n  }\n\n  goToPage(page) {\n    const totalPages = this.getTotalPages();\n    if (page < 1) {\n      this.currentPage = 1;\n    } else if (page > totalPages) {\n      this.currentPage = totalPages;\n    } else {\n      this.currentPage = page;\n    }\n    this.render();\n  }\n\n  render() {\n    domAdapter.setInnerHTML(this.tbody, \"\");\n    const visibleRows = this.getVisibleRows();\n    const totalPages = this.getTotalPages();\n\n    if (this.currentPage > totalPages && totalPages > 0) {\n      this.currentPage = totalPages;\n    }\n\n    const startIndex = (this.currentPage - 1) * this.pageSize;\n    const endIndex = startIndex + this.pageSize;\n    const pageRows = visibleRows.slice(startIndex, endIndex);\n\n    if (pageRows.length === 0) {\n      const emptyRow = domAdapter.createElement(\"tr\");\n      domAdapter.setInnerHTML(\n        emptyRow,\n        `<td colspan=\"${this.headers.length}\" style=\"text-align:center;\">Keine Einträge gefunden<\/td>`\n      );\n      domAdapter.appendChild(this.tbody, emptyRow);\n    } else {\n      pageRows.forEach((row) => domAdapter.appendChild(this.tbody, row.element));\n    }\n\n    this.renderPagination(visibleRows.length, totalPages);\n  }\n\n  renderPagination(totalItems, totalPages) {\n    \/\/ Clear previous pagination\n    domAdapter.setInnerHTML(this.paginationContainer, \"\");\n\n    \/\/ Don't show pagination if only one page\n    if (totalPages <= 1) {\n      return;\n    }\n\n    \/\/ Info text\n    const startItem = (this.currentPage - 1) * this.pageSize + 1;\n    const endItem = Math.min(this.currentPage * this.pageSize, totalItems);\n    const info = domAdapter.createElement(\"span\");\n    domAdapter.addClass(info, \"pagination__info\");\n    domAdapter.setTextContent(info, `${startItem}-${endItem} von ${totalItems}`);\n    domAdapter.appendChild(this.paginationContainer, info);\n\n    \/\/ Buttons container\n    const buttons = domAdapter.createElement(\"span\");\n    domAdapter.addClass(buttons, \"pagination__buttons\");\n\n    \/\/ Previous button\n    const prevBtn = domAdapter.createElement(\"button\");\n    domAdapter.addClass(prevBtn, \"pagination__btn\");\n    domAdapter.setTextContent(prevBtn, \"←\");\n    if (this.currentPage === 1) {\n      domAdapter.setAttribute(prevBtn, \"disabled\", \"true\");\n    }\n    const prevCleanup = eventAdapter.on(\n      prevBtn,\n      \"click\",\n      () => this.goToPage(this.currentPage - 1),\n      this.deps,\n      \"DATATABLE_PAGE_PREV\",\n      \"datatable\"\n    );\n    this.cleanupFunctions.push(prevCleanup);\n    domAdapter.appendChild(buttons, prevBtn);\n\n    \/\/ Page numbers\n    const maxButtons = 5;\n    let startPage = Math.max(1, this.currentPage - Math.floor(maxButtons \/ 2));\n    const endPage = Math.min(totalPages, startPage + maxButtons - 1);\n    startPage = Math.max(1, endPage - maxButtons + 1);\n\n    for (let i = startPage; i <= endPage; i++) {\n      const pageBtn = domAdapter.createElement(\"button\");\n      domAdapter.addClass(pageBtn, \"pagination__btn\");\n      if (i === this.currentPage) {\n        domAdapter.addClass(pageBtn, \"pagination__btn--active\");\n      }\n      domAdapter.setTextContent(pageBtn, String(i));\n      const page = i;\n      const pageCleanup = eventAdapter.on(\n        pageBtn,\n        \"click\",\n        () => this.goToPage(page),\n        this.deps,\n        \"DATATABLE_PAGE\",\n        \"datatable\"\n      );\n      this.cleanupFunctions.push(pageCleanup);\n      domAdapter.appendChild(buttons, pageBtn);\n    }\n\n    \/\/ Next button\n    const nextBtn = domAdapter.createElement(\"button\");\n    domAdapter.addClass(nextBtn, \"pagination__btn\");\n    domAdapter.setTextContent(nextBtn, \"→\");\n    if (this.currentPage === totalPages) {\n      domAdapter.setAttribute(nextBtn, \"disabled\", \"true\");\n    }\n    const nextCleanup = eventAdapter.on(\n      nextBtn,\n      \"click\",\n      () => this.goToPage(this.currentPage + 1),\n      this.deps,\n      \"DATATABLE_PAGE_NEXT\",\n      \"datatable\"\n    );\n    this.cleanupFunctions.push(nextCleanup);\n    domAdapter.appendChild(buttons, nextBtn);\n\n    domAdapter.appendChild(this.paginationContainer, buttons);\n  }\n\n  refresh() {\n    this.cacheRows();\n    this.applyFilters();\n  }\n\n  dispose() {\n    this.cleanupFunctions.forEach((cleanup) => cleanup());\n    this.cleanupFunctions = [];\n    if (this.paginationContainer !== null && this.paginationContainer.parentNode !== null) {\n      this.paginationContainer.parentNode.removeChild(this.paginationContainer);\n    }\n  }\n}\n\n\/**\n * Initialize DataTable module with dependencies.\n * @param {Object} deps - Injected dependencies\n * @param {Object} deps.clock - Clock interface { now_epoch_ms_utc: () => number }\n * @param {Object} deps.logger - Logger interface\n * @param {Object} deps.ui - UI interface { showMessage, disableFeature, offerRetry }\n * @param {string} deps.runId - Unique run identifier\n * @returns {Object} DataT... [TRUNCATED-c56e2701ccc5542f]",
            "numLines": 362,
            "startLine": 1,
            "totalLines": 362
        }
    }
}
← Vorheriger Zur Liste Nächster →