Protokoll #11709
| ID | 11709 |
|---|---|
| Zeitstempel | 2025-12-23 21:00:21.908871 |
| Client | root |
| 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 |
| Modell | claude-sonnet-4-20250514 |
| Status | completed |
| Tokens | 2,716 (Input: 0, Output: 0) |
| Dauer | 85 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
}
}
}