@@ -0,0 +1,158 @@
<!DOCTYPE html>
< html lang = "ru" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width,initial-scale=1" >
< title > Файлы — Админ-панель< / title >
< link rel = "stylesheet" href = "admin.css" >
< style >
. file-icon { width : 80 px ; height : 80 px ; border-radius : 8 px ; background : var ( - - bg ) ; border : 1 px solid var ( - - border ) ; display : flex ; align-items : center ; justify-content : center ; font-size : 1.6 rem ; flex-shrink : 0 ; color : var ( - - muted ) }
. file-name { font-weight : 600 ; font-size : .95 rem }
. file-name a { color : var ( - - text ) ; text-decoration : none }
. file-name a : hover { color : var ( - - accent ) }
< / style >
< / head >
< body data-page = "files" >
< div class = "layout" >
< div class = "sidebar" id = "sidebar" > < / div >
< div class = "main" >
< h2 > Файлы< / h2 >
< div class = "filters" >
< div > < label > Поиск< / label > < input type = "text" id = "searchInput" class = "search-input" placeholder = "Имя файла" > < / div >
< div > < label > Студент< / label > < input type = "text" id = "studentFilter" list = "studentFilterList" placeholder = "Все / введите имя..." > < datalist id = "studentFilterList" > < / datalist > < / div >
< div > < label > Группа< / label > < select id = "groupFilter" > < option value = "" > Все группы< / option > < / select > < / div >
< div > < label > С < / label > < input type = "date" id = "dateFrom" > < / div >
< div > < label > По< / label > < input type = "date" id = "dateTo" > < / div >
< / div >
< div class = "grid" id = "grid" > < div class = "empty" > Загрузка...< / div > < / div >
< div class = "pager" id = "pager" > < / div >
< / div >
< / div >
< div class = "modal-overlay" id = "imgModal" onclick = "this.classList.remove('open')" >
< img id = "imgModalSrc" >
< / div >
< div class = "toast" id = "toast" > < / div >
< script src = "admin.js" > < / script >
< script >
const PAGE _SIZE = 20 ;
let page = 1 ;
let totalFiles = 0 ;
let currentFiles = [ ] ;
let groupsLoaded = false ;
let studentsLoaded = false ;
let searchTimer = null ;
function isImageFile ( name ) {
return /\.(jpe?g|png|gif|webp|bmp|avif|svg|ico)$/i . test ( name || '' ) ;
}
function humanSize ( b ) {
if ( ! b ) return '' ;
if ( b < 1024 ) return b + ' Б' ;
if ( b < 1048576 ) return ( b / 1024 ) . toFixed ( 1 ) + ' КБ' ;
return ( b / 1048576 ) . toFixed ( 1 ) + ' МБ' ;
}
function fileCardHTML ( f ) {
const url = ` ${ API } /api/files/ ${ f . token } ` ;
const thumb = isImageFile ( f . name )
? ` <img src=" ${ url } " onclick="showImg(' ${ url } ')" alt=""> `
: ` <div class="file-icon">📄</div> ` ;
const target = isImageFile ( f . name )
? ` href="#" onclick="showImg(' ${ url } ');return false;" `
: ` href=" ${ url } " download ` ;
return `
<div class="card">
${ thumb }
<div class="info">
<div class="file-name"><a ${ target } > ${ esc ( f . name ) } </a></div>
<div class="group"> ${ esc ( f . student _name ) } · ${ esc ( f . group _name ) } </div>
<div class="time"> ${ new Date ( f . created _at ) . toLocaleString ( 'ru' ) } ${ f . size ? ' · ' + humanSize ( f . size ) : '' } </div>
</div>
</div>
` ;
}
function renderGrid ( ) {
const grid = document . getElementById ( 'grid' ) ;
if ( ! currentFiles . length ) {
grid . className = 'grid' ;
grid . innerHTML = '<div class="empty">Файлов не найдено</div>' ;
return ;
}
grid . className = 'grid' ;
grid . innerHTML = currentFiles . map ( fileCardHTML ) . join ( '' ) ;
}
async function loadGroupOptions ( ) {
if ( groupsLoaded ) return ;
const sel = document . getElementById ( 'groupFilter' ) ;
while ( sel . options . length > 1 ) sel . remove ( 1 ) ;
const res = await fetch ( ` ${ API } /api/groups ` , { headers : hdr ( ) } ) ;
const groups = await res . json ( ) ;
groups . forEach ( g => {
const o = document . createElement ( 'option' ) ;
o . value = g . id ; o . textContent = g . name ;
sel . appendChild ( o ) ;
} ) ;
groupsLoaded = true ;
}
async function loadStudentOptions ( ) {
if ( studentsLoaded ) return ;
const dl = document . getElementById ( 'studentFilterList' ) ;
const res = await fetch ( ` ${ API } /api/students/names ` , { headers : hdr ( ) } ) ;
const names = await res . json ( ) ;
dl . innerHTML = names . map ( n => ` <option value=" ${ esc ( n ) } "> ` ) . join ( '' ) ;
studentsLoaded = true ;
}
async function loadFiles ( ) {
const p = new URLSearchParams ( ) ;
const q = document . getElementById ( 'searchInput' ) . value ;
const sn = document . getElementById ( 'studentFilter' ) . value ;
const gid = document . getElementById ( 'groupFilter' ) . value ;
const df = document . getElementById ( 'dateFrom' ) . value ;
const dt = document . getElementById ( 'dateTo' ) . value ;
if ( q ) p . set ( 'search' , q ) ;
if ( sn ) p . set ( 'student_name' , sn ) ;
if ( gid ) p . set ( 'group_id' , gid ) ;
if ( df ) p . set ( 'date_from' , df ) ;
if ( dt ) p . set ( 'date_to' , dt ) ;
p . set ( 'limit' , PAGE _SIZE ) ;
p . set ( 'offset' , ( page - 1 ) * PAGE _SIZE ) ;
const res = await fetch ( ` ${ API } /api/files? ${ p } ` , { headers : hdr ( ) } ) ;
const data = await res . json ( ) ;
currentFiles = data . files ;
totalFiles = data . total ;
renderGrid ( ) ;
renderPager ( document . getElementById ( 'pager' ) , page , Math . max ( 1 , Math . ceil ( totalFiles / PAGE _SIZE ) ) , goPage ) ;
}
function goPage ( p ) {
page = p ;
loadFiles ( ) ;
}
document . getElementById ( 'studentFilter' ) . addEventListener ( 'change' , ( ) => { page = 1 ; loadFiles ( ) ; } ) ;
document . getElementById ( 'groupFilter' ) . addEventListener ( 'change' , ( ) => { page = 1 ; loadFiles ( ) ; } ) ;
document . getElementById ( 'dateFrom' ) . addEventListener ( 'change' , ( ) => { page = 1 ; loadFiles ( ) ; } ) ;
document . getElementById ( 'dateTo' ) . addEventListener ( 'change' , ( ) => { page = 1 ; loadFiles ( ) ; } ) ;
document . getElementById ( 'searchInput' ) . addEventListener ( 'input' , ( ) => {
clearTimeout ( searchTimer ) ;
searchTimer = setTimeout ( ( ) => { page = 1 ; loadFiles ( ) ; } , 300 ) ;
} ) ;
( async ( ) => {
if ( await checkAuth ( ) ) {
buildSidebar ( document . body . dataset . page ) ;
loadGroupOptions ( ) ;
loadStudentOptions ( ) ;
loadFiles ( ) ;
}
} ) ( ) ;
< / script >
< / body >
< / html >