diff --git a/app/src/main/java/ru/ulstu/is/pmu/api/ApiStatus.kt b/app/src/main/java/ru/ulstu/is/pmu/api/ApiStatus.kt index 127e66f..522877b 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/api/ApiStatus.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/api/ApiStatus.kt @@ -1,4 +1,3 @@ -package ru.ulstu.is.pmu.api +package ru.ulstu.`is`.pmu.api -class ApiStatus { -} \ No newline at end of file +enum class ApiStatus { LOADING, ERROR, DONE } \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/api/MyServerService.kt b/app/src/main/java/ru/ulstu/is/pmu/api/MyServerService.kt index 633c626..29e325b 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/api/MyServerService.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/api/MyServerService.kt @@ -21,9 +21,16 @@ import retrofit2.http.Path import retrofit2.http.Query import ru.ulstu.`is`.pmu.api.model.UserRemote import ru.ulstu.`is`.pmu.api.model.TaskRemote +import ru.ulstu.`is`.pmu.api.report.ReportRemote interface MyServerService { + @GET("report") + suspend fun getReportInfo( + @Query("fromDate") fromDate: String, + @Query("toDate") toDate: String + ): List + @GET("users") suspend fun getUsers(): List diff --git a/app/src/main/java/ru/ulstu/is/pmu/api/model/TaskRemote.kt b/app/src/main/java/ru/ulstu/is/pmu/api/model/TaskRemote.kt index 6d02c10..51df69a 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/api/model/TaskRemote.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/api/model/TaskRemote.kt @@ -9,8 +9,8 @@ data class TaskRemote( val name: String = "", val description: String = "", val endDate: String = "", - val favorite: Boolean, - val userId: Int, + val favorite: Boolean = false, // Значение по умолчанию + val userId: Int = 1, // Значение по умолчанию ) fun TaskRemote.toTask(): Task = Task( @@ -18,7 +18,7 @@ fun TaskRemote.toTask(): Task = Task( name, description, endDate, - favorite = false, + favorite, userId ) @@ -27,6 +27,6 @@ fun Task.toTaskRemote(): TaskRemote = TaskRemote( name, description, endDate, - favorite = false, + favorite, userId = 1 ) \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/api/report/ReportRemote.kt b/app/src/main/java/ru/ulstu/is/pmu/api/report/ReportRemote.kt index 2480719..b36cadb 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/api/report/ReportRemote.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/api/report/ReportRemote.kt @@ -1,4 +1,11 @@ -package ru.ulstu.is.pmu.api.report +package ru.ulstu.`is`.pmu.api.report -class ReportRemote { -} \ No newline at end of file +import kotlinx.serialization.Serializable + +@Serializable +data class ReportRemote( + val id: Int = 0, + val name: String = "", + val description: String = "", + val endDate: String = "" +) \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/api/task/RestTaskRepository.kt b/app/src/main/java/ru/ulstu/is/pmu/api/task/RestTaskRepository.kt index 68dd86b..de355bb 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/api/task/RestTaskRepository.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/api/task/RestTaskRepository.kt @@ -10,12 +10,16 @@ import ru.ulstu.`is`.pmu.api.MyServerService import ru.ulstu.`is`.pmu.api.user.RestUserRepository import ru.ulstu.`is`.pmu.api.model.toTask import ru.ulstu.`is`.pmu.api.model.toTaskRemote +import ru.ulstu.`is`.pmu.api.report.ReportRemote import ru.ulstu.`is`.pmu.common.AppContainer import ru.ulstu.`is`.pmu.common.TaskRepository import ru.ulstu.`is`.pmu.database.AppDatabase import ru.ulstu.`is`.pmu.database.remotekeys.repository.OfflineRemoteKeyRepository import ru.ulstu.`is`.pmu.database.task.model.Task import ru.ulstu.`is`.pmu.database.task.repository.OfflineTaskRepository +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale class RestTaskRepository( private val service: MyServerService, @@ -46,6 +50,50 @@ class RestTaskRepository( ).flow } + override fun getAllFavoriteTasks(): Flow> { + Log.d(RestTaskRepository::class.simpleName, "Get tasks") + + val pagingSourceFactory = { dbTaskRepository.getAllTasksFavoritePagingSource() } + + @OptIn(ExperimentalPagingApi::class) + return Pager( + config = PagingConfig( + pageSize = AppContainer.LIMIT, + enablePlaceholders = false + ), + remoteMediator = TaskRemoteMediator( + service, + dbTaskRepository, + dbRemoteKeyRepository, + userRestRepository, + database, + ), + pagingSourceFactory = pagingSourceFactory + ).flow + } + + override fun getAllDateTasks(): Flow> { + Log.d(RestTaskRepository::class.simpleName, "Get tasks") + + val pagingSourceFactory = { dbTaskRepository.getAllTasksDatePagingSource() } + + @OptIn(ExperimentalPagingApi::class) + return Pager( + config = PagingConfig( + pageSize = AppContainer.LIMIT, + enablePlaceholders = false + ), + remoteMediator = TaskRemoteMediator( + service, + dbTaskRepository, + dbRemoteKeyRepository, + userRestRepository, + database, + ), + pagingSourceFactory = pagingSourceFactory + ).flow + } + override suspend fun getTask(uid: Int): Task = service.getTask(uid).toTask() @@ -60,4 +108,19 @@ class RestTaskRepository( override suspend fun deleteTask(task: Task) { service.deleteTask(task.uid).toTask() } + + override suspend fun favoriteTask(task: Task) { + task.favorite = true + service.updateTask(task.uid, task.toTaskRemote()).toTask() + } + + override suspend fun deletefavoriteTask(task: Task) { + task.favorite = false + service.updateTask(task.uid, task.toTaskRemote()).toTask() + } + + suspend fun getReport(fromDate: String, toDate: String):List + { + return service.getReportInfo(fromDate,toDate) + } } \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/common/AppViewModelProvider.kt b/app/src/main/java/ru/ulstu/is/pmu/common/AppViewModelProvider.kt index bf8df32..e801021 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/common/AppViewModelProvider.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/common/AppViewModelProvider.kt @@ -6,8 +6,11 @@ import androidx.lifecycle.viewmodel.CreationExtras import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import ru.ulstu.`is`.pmu.TaskApplication +import ru.ulstu.`is`.pmu.ui.ReportViewModel import ru.ulstu.`is`.pmu.ui.task.edit.UserDropDownViewModel import ru.ulstu.`is`.pmu.ui.task.edit.TaskEditViewModel +import ru.ulstu.`is`.pmu.ui.task.list.FavoriteTaskList +import ru.ulstu.`is`.pmu.ui.task.list.FavoriteTaskListViewModel import ru.ulstu.`is`.pmu.ui.task.list.TaskListViewModel object AppViewModelProvider { @@ -24,6 +27,14 @@ object AppViewModelProvider { initializer { UserDropDownViewModel(taskApplication().container.userRestRepository) } + initializer { + FavoriteTaskListViewModel(taskApplication().container.taskRestRepository) + } + initializer { + ReportViewModel( + taskApplication().container.taskRestRepository, + ) + } } } diff --git a/app/src/main/java/ru/ulstu/is/pmu/common/TaskRepository.kt b/app/src/main/java/ru/ulstu/is/pmu/common/TaskRepository.kt index 4f40b17..f51fe24 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/common/TaskRepository.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/common/TaskRepository.kt @@ -6,8 +6,12 @@ import ru.ulstu.`is`.pmu.database.task.model.Task interface TaskRepository { fun getAllTasks(): Flow> + fun getAllFavoriteTasks(): Flow> + fun getAllDateTasks(): Flow> suspend fun getTask(uid: Int): Task suspend fun insertTask(task: Task) suspend fun updateTask(task: Task) suspend fun deleteTask(task: Task) + suspend fun favoriteTask(task: Task) + suspend fun deletefavoriteTask(task: Task) } \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/database/remotekeys/model/RemoteKeys.kt b/app/src/main/java/ru/ulstu/is/pmu/database/remotekeys/model/RemoteKeys.kt index 1e095ea..0147e9c 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/database/remotekeys/model/RemoteKeys.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/database/remotekeys/model/RemoteKeys.kt @@ -5,9 +5,11 @@ import androidx.room.PrimaryKey import androidx.room.TypeConverter import androidx.room.TypeConverters import ru.ulstu.`is`.pmu.database.task.model.Task +import ru.ulstu.`is`.pmu.database.task.model.User enum class RemoteKeyType(private val type: String) { - STUDENT(Task::class.simpleName ?: "Task"); + STUDENT(Task::class.simpleName ?: "Task"), + USER(User::class.simpleName ?: "User"); @TypeConverter fun toRemoteKeyType(value: String) = RemoteKeyType.values().first { it.type == value } diff --git a/app/src/main/java/ru/ulstu/is/pmu/database/student/dao/TaskDao.kt b/app/src/main/java/ru/ulstu/is/pmu/database/student/dao/TaskDao.kt index 4bfdb77..caaa304 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/database/student/dao/TaskDao.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/database/student/dao/TaskDao.kt @@ -14,6 +14,12 @@ interface TaskDao { @Query("select * from tasks order by name collate nocase asc") fun getAll(): PagingSource + @Query("SELECT * FROM tasks WHERE favorite = 1") + fun getFavoriteTasks(): PagingSource + + @Query("SELECT * FROM tasks ORDER BY DATE(SUBSTR(endDate, 7, 4) || '-' || SUBSTR(endDate, 4, 2) || '-' || SUBSTR(endDate, 1, 2)) ASC") + fun getTasksSortedByDate(): PagingSource + @Query("select * from tasks where tasks.uid = :uid") fun getByUid(uid: Int): Flow @@ -26,6 +32,9 @@ interface TaskDao { @Delete suspend fun delete(task: Task) + @Query("UPDATE tasks SET favorite = :favorite WHERE uid = :taskId") + suspend fun updateFavorite(taskId: Int, favorite: Boolean) + @Query("DELETE FROM tasks") suspend fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/database/student/dao/UserDao.kt b/app/src/main/java/ru/ulstu/is/pmu/database/student/dao/UserDao.kt index fb02a8d..3f535e0 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/database/student/dao/UserDao.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/database/student/dao/UserDao.kt @@ -1,23 +1,27 @@ package ru.ulstu.`is`.pmu.database.task.dao +import androidx.paging.PagingSource import androidx.room.Dao import androidx.room.Delete import androidx.room.Insert import androidx.room.Query import androidx.room.Update +import ru.ulstu.`is`.pmu.database.task.model.Task import ru.ulstu.`is`.pmu.database.task.model.User @Dao interface UserDao { @Query("select * from users order by login collate nocase asc") suspend fun getAll(): List - @Insert - suspend fun insert(user: User) + suspend fun insert(vararg user: User) @Update suspend fun update(user: User) @Delete suspend fun delete(user: User) + + @Query("DELETE FROM users") + suspend fun deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/database/student/model/Task.kt b/app/src/main/java/ru/ulstu/is/pmu/database/student/model/Task.kt index 0b5b886..b927057 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/database/student/model/Task.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/database/student/model/Task.kt @@ -39,7 +39,7 @@ data class Task( endDate: String, favorite: Boolean, user: User, - ) : this(0, name, description, endDate, favorite, user.uid) + ) : this(0, name, description, endDate, favorite, 1) companion object { diff --git a/app/src/main/java/ru/ulstu/is/pmu/database/student/repository/OfflineTaskRepository.kt b/app/src/main/java/ru/ulstu/is/pmu/database/student/repository/OfflineTaskRepository.kt index dbb5a19..3151122 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/database/student/repository/OfflineTaskRepository.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/database/student/repository/OfflineTaskRepository.kt @@ -20,6 +20,22 @@ class OfflineTaskRepository(private val taskDao: TaskDao) : TaskRepository { pagingSourceFactory = taskDao::getAll ).flow + override fun getAllFavoriteTasks(): Flow> = Pager( + config = PagingConfig( + pageSize = AppContainer.LIMIT, + enablePlaceholders = false + ), + pagingSourceFactory = taskDao::getFavoriteTasks + ).flow + + override fun getAllDateTasks(): Flow> = Pager( + config = PagingConfig( + pageSize = AppContainer.LIMIT, + enablePlaceholders = false + ), + pagingSourceFactory = taskDao::getTasksSortedByDate + ).flow + override suspend fun getTask(uid: Int): Task = taskDao.getByUid(uid).first() override suspend fun insertTask(task: Task) = taskDao.insert(task) @@ -27,12 +43,21 @@ class OfflineTaskRepository(private val taskDao: TaskDao) : TaskRepository { override suspend fun updateTask(task: Task) = taskDao.update(task) override suspend fun deleteTask(task: Task) = taskDao.delete(task) + override suspend fun favoriteTask(task: Task) { - TODO("Not yet implemented") + taskDao.updateFavorite(task.uid, !task.favorite) + } + + override suspend fun deletefavoriteTask(task: Task) { + taskDao.updateFavorite(task.uid, !task.favorite) } fun getAllTasksPagingSource(): PagingSource = taskDao.getAll() + fun getAllTasksFavoritePagingSource(): PagingSource = taskDao.getFavoriteTasks() + + fun getAllTasksDatePagingSource(): PagingSource = taskDao.getTasksSortedByDate() + suspend fun insertTasks(tasks: List) = taskDao.insert(*tasks.toTypedArray()) diff --git a/app/src/main/java/ru/ulstu/is/pmu/database/student/repository/OfflineUserRepository.kt b/app/src/main/java/ru/ulstu/is/pmu/database/student/repository/OfflineUserRepository.kt index 924675d..320b622 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/database/student/repository/OfflineUserRepository.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/database/student/repository/OfflineUserRepository.kt @@ -1,11 +1,23 @@ package ru.ulstu.`is`.pmu.database.task.repository +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.PagingSource +import kotlinx.coroutines.flow.Flow +import ru.ulstu.`is`.pmu.common.AppContainer import ru.ulstu.`is`.pmu.common.UserRepository import ru.ulstu.`is`.pmu.database.task.dao.UserDao +import ru.ulstu.`is`.pmu.database.task.model.Task import ru.ulstu.`is`.pmu.database.task.model.User class OfflineUserRepository(private val userDao: UserDao) : UserRepository { override suspend fun getAllUsers(): List = userDao.getAll() + + suspend fun insertUsers(users: List) = + userDao.insert(*users.toTypedArray()) + suspend fun createUser(user: User) = userDao.insert(user) suspend fun updateUser(user: User) = userDao.update(user) + suspend fun clearUsers() = userDao.deleteAll() } \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/ui/ReportPage.kt b/app/src/main/java/ru/ulstu/is/pmu/ui/ReportPage.kt index 445bd32..c507734 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/ui/ReportPage.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/ui/ReportPage.kt @@ -1,4 +1,362 @@ -package ru.ulstu.is.pmu.ui +package ru.ulstu.`is`.pmu.ui -class ReportPage { +import android.content.ContentResolver +import android.content.ContentValues +import android.content.Context +import android.content.Intent +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Typeface +import android.graphics.pdf.PdfDocument +import android.net.Uri +import android.os.Environment +import android.provider.MediaStore +import android.util.Log +import android.widget.CalendarView +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.DatePicker +import androidx.compose.material3.DatePickerState +import androidx.compose.material3.DisplayMode +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import ru.ulstu.`is`.pmu.R +import ru.ulstu.`is`.pmu.api.report.ReportRemote +import ru.ulstu.`is`.pmu.common.AppViewModelProvider +import ru.ulstu.`is`.pmu.ui.ReportViewModel +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.OutputStream +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ReportPage(navController: NavController?, viewModel: ReportViewModel = viewModel(factory = AppViewModelProvider.Factory)) { + val coroutineScope = rememberCoroutineScope() + + var showStartDatePicker by remember { mutableStateOf(false) } + var showEndDatePicker by remember { mutableStateOf(false) } + + val context = LocalContext.current + + + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + Button( + onClick = { showStartDatePicker = true }, + modifier = Modifier.width(350.dp) + ) { + Text("Начальная дата") + } + } + + + if (showStartDatePicker) { + DatePicker( + onDateSelected = { selectedDate -> + viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(startDate = selectedDate)) + showStartDatePicker = false + }, + onDismissRequest = { showStartDatePicker = false } + ) + } + + Button( + onClick = { showEndDatePicker = true }, + modifier = Modifier.width(350.dp) + ) { + Text("Конечная дата") + } + + if (showEndDatePicker) { + DatePicker( + onDateSelected = { selectedDate -> + viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(endDate = selectedDate)) + showEndDatePicker = false + if (viewModel.reportPageUiState.reportDetails.startDate != null) { + coroutineScope.launch { + viewModel.getReport() + createPdfFile(context, "Отчет.pdf", viewModel.reportResultPageUiState.resReport) + } + } + }, + onDismissRequest = { showEndDatePicker = false } + ) + } + + Spacer(modifier = Modifier.height(32.dp)) + Text( + text = "Результат", + style = MaterialTheme.typography.headlineLarge + ) + + ReportTable(reportData = viewModel.reportResultPageUiState.resReport) + } +} + + + +@Composable +fun ReportTable(reportData: List) { + Column(modifier = Modifier.padding(16.dp)) { + reportData.forEach { report -> + ReportCard(report) + } + } +} + +@Composable +fun ReportCard(report: ReportRemote) { + val (taskId, taskName, taskDescription, taskEndDate) = report + Card( + modifier = Modifier + .padding(8.dp) + .fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp) // Исправлено здесь + ) { + Column(modifier = Modifier.padding(16.dp)) { + Text("ID: ${taskId}", style = MaterialTheme.typography.bodyMedium) + Text("Название: $taskName", style = MaterialTheme.typography.bodyMedium) + Text("Описание: $taskDescription", style = MaterialTheme.typography.bodyMedium) + Text("Дата завершения: $taskEndDate", style = MaterialTheme.typography.bodyMedium) + } + } +} + +@Composable +fun CustomCalendarView(onDateSelected: (Date) -> Unit) { + AndroidView( + modifier = Modifier.wrapContentSize(), + factory = { context -> + val calendarView = CalendarView(context) + + // Настройки CalendarView + val calendar = Calendar.getInstance() + + calendarView.setOnDateChangeListener { _, year, month, dayOfMonth -> + calendar.set(Calendar.YEAR, year) + calendar.set(Calendar.MONTH, month) + calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth) + onDateSelected(calendar.time) + } + + calendarView + } + ) +} + +@Composable +fun DatePicker(onDateSelected: (Date) -> Unit, onDismissRequest: () -> Unit) { + val selDate = remember { mutableStateOf(Date()) } + + //todo - add strings to resource after POC + Dialog(onDismissRequest = { onDismissRequest() }, properties = DialogProperties()) { + Column( + modifier = Modifier + .wrapContentSize() + .background( + color = Color.White + ) + ) { + Column( + Modifier + .defaultMinSize(minHeight = 72.dp) + .fillMaxWidth() + .background( + color = MaterialTheme.colorScheme.primary + ) + .padding(16.dp) + ) { + Text( + text = "Выберите дату" + ) + + Spacer(modifier = Modifier.size(24.dp)) + + Text( + text = SimpleDateFormat("MMM d, yyyy", Locale.getDefault()).format(selDate.value) + ) + + Spacer(modifier = Modifier.size(16.dp)) + } + + CustomCalendarView(onDateSelected = { + selDate.value = it + }) + + Spacer(modifier = Modifier.size(8.dp)) + + Row( + modifier = Modifier + .align(Alignment.End) + .padding(bottom = 16.dp, end = 16.dp) + .background( + color = Color.White, + shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) + ) + ) { + TextButton( + onClick = onDismissRequest + ) { + //TODO - hardcode string + Text( + text = "Отмена" + ) + } + + TextButton( + onClick = { + onDateSelected(selDate.value) + onDismissRequest() + } + ) { + //TODO - hardcode string + Text( + text = "Подтвердить" + ) + } + + } + } + } +} + +suspend fun createPdfFile(context: Context, fileName: String, reportData: List) { + withContext(Dispatchers.IO) { + val contentValues = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) + put(MediaStore.MediaColumns.MIME_TYPE, "application/pdf") + put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS) + } + + val contentResolver: ContentResolver = context.contentResolver + val uri: Uri? = contentResolver.insert(MediaStore.Files.getContentUri("external"), contentValues) + + uri?.let { + try { + contentResolver.openOutputStream(uri)?.use { outputStream -> + createPdfContent(outputStream, reportData) + } + + context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)) + } catch (e: IOException) { + e.printStackTrace() + } + } + } +} + +private fun createPdfContent(outputStream: OutputStream, reportData: List) { + val pdfDocument = PdfDocument() + + val pageInfo = PdfDocument.PageInfo.Builder(595, 842, 1).create() + val page = pdfDocument.startPage(pageInfo) + val canvas = page.canvas + + val paint = Paint() + paint.color = android.graphics.Color.BLACK + paint.textSize = 24f + + // Отображаем заголовок + val title = "Отчет" + val xTitle = (pageInfo.pageWidth - paint.measureText(title)) / 2 + val yTitle = 40f + canvas.drawText(title, xTitle, yTitle, paint) + + // Определение структуры таблицы + val tableWidth = pageInfo.pageWidth - 80f + val columnCount = 4 + val columnWidth = tableWidth / columnCount + val rowHeight = 40f + + // Отображаем заголовки столбцов + val columnHeaderPaint = Paint() + columnHeaderPaint.color = android.graphics.Color.BLACK + columnHeaderPaint.textSize = 18f + val columnHeaderTitles = listOf("ID", "Название", "Описание", "Дата окончания") + for (i in 0 until columnCount) { + val x = 40f + i * columnWidth + val y = yTitle + 80f + canvas.drawText(columnHeaderTitles[i], x, y, columnHeaderPaint) + } + + // Отображаем данные в таблице + val rowDataPaint = Paint() + rowDataPaint.color = android.graphics.Color.BLACK + rowDataPaint.textSize = 18f + for ((index, report) in reportData.withIndex()) { + val x = 40f + val y = yTitle + 120f + index * rowHeight + + // Отображаем данные каждого столбца + canvas.drawText(report.id.toString(), x, y, rowDataPaint) + canvas.drawText(report.name, x + columnWidth, y, rowDataPaint) + canvas.drawText(report.description, x + 2 * columnWidth, y, rowDataPaint) + canvas.drawText(report.endDate, x + 3 * columnWidth, y, rowDataPaint) + } + + pdfDocument.finishPage(page) + + pdfDocument.writeTo(outputStream) + pdfDocument.close() +} + +private fun drawCell(canvas: Canvas, paint: Paint, text: String, x: Float, y: Float, width: Int) { + canvas.drawText(text, x + (width - paint.measureText(text)) / 2, y + paint.textSize, paint) } \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/ui/ReportViewModel.kt b/app/src/main/java/ru/ulstu/is/pmu/ui/ReportViewModel.kt index 9d8da7a..d56f8ba 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/ui/ReportViewModel.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/ui/ReportViewModel.kt @@ -9,8 +9,10 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch import ru.ulstu.`is`.pmu.api.model.TaskRemote +import ru.ulstu.`is`.pmu.api.model.toTask import ru.ulstu.`is`.pmu.api.report.ReportRemote import ru.ulstu.`is`.pmu.api.task.RestTaskRepository +import ru.ulstu.`is`.pmu.database.task.model.Task import java.text.SimpleDateFormat import java.util.Date import java.util.Locale diff --git a/app/src/main/java/ru/ulstu/is/pmu/ui/navigation/MainNavbar.kt b/app/src/main/java/ru/ulstu/is/pmu/ui/navigation/MainNavbar.kt index 905c136..26cf4c3 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/ui/navigation/MainNavbar.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/ui/navigation/MainNavbar.kt @@ -29,8 +29,10 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import ru.ulstu.`is`.pmu.R +import ru.ulstu.`is`.pmu.ui.ReportPage import ru.ulstu.`is`.pmu.ui.about.About import ru.ulstu.`is`.pmu.ui.task.edit.TaskEdit +import ru.ulstu.`is`.pmu.ui.task.list.FavoriteTaskList import ru.ulstu.`is`.pmu.ui.task.list.TaskList @OptIn(ExperimentalMaterial3Api::class) @@ -109,6 +111,8 @@ fun Navhost( ) { TaskEdit(navController) } + composable(Screen.TaskFavoriteList.route) { FavoriteTaskList(navController) } + composable(Screen.Report.route) { ReportPage(navController = navController) } } } diff --git a/app/src/main/java/ru/ulstu/is/pmu/ui/navigation/Screen.kt b/app/src/main/java/ru/ulstu/is/pmu/ui/navigation/Screen.kt index 156244f..bdd0599 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/ui/navigation/Screen.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/ui/navigation/Screen.kt @@ -5,6 +5,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.List +import androidx.compose.material.icons.filled.Send import androidx.compose.ui.graphics.vector.ImageVector import ru.ulstu.`is`.pmu.R @@ -20,6 +21,12 @@ enum class Screen( About( "about", R.string.about_title, Icons.Filled.Info ), + TaskFavoriteList( + "task-favorite", R.string.task_favorite_view_title + ), + Report( + "report", R.string.report, Icons.Filled.Send + ), TaskEdit( "task-edit/{id}", R.string.task_view_title, showInBottomBar = false ); @@ -27,7 +34,9 @@ enum class Screen( companion object { val bottomBarItems = listOf( TaskList, + TaskFavoriteList, About, + Report ) fun getItem(route: String): Screen? { diff --git a/app/src/main/java/ru/ulstu/is/pmu/ui/student/edit/StudentEdit.kt b/app/src/main/java/ru/ulstu/is/pmu/ui/student/edit/StudentEdit.kt index d16067d..ca7a233 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/ui/student/edit/StudentEdit.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/ui/student/edit/StudentEdit.kt @@ -1,11 +1,19 @@ package ru.ulstu.`is`.pmu.ui.task.edit import android.content.res.Configuration +import android.widget.CalendarView import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem @@ -16,6 +24,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -23,12 +32,16 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import kotlinx.coroutines.launch @@ -37,6 +50,10 @@ import ru.ulstu.`is`.pmu.database.task.model.User import ru.ulstu.`is`.pmu.database.task.model.Task import ru.ulstu.`is`.pmu.common.AppViewModelProvider import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Date +import java.util.Locale @Composable fun TaskEdit( @@ -44,17 +61,14 @@ fun TaskEdit( viewModel: TaskEditViewModel = viewModel(factory = AppViewModelProvider.Factory), userViewModel: UserDropDownViewModel = viewModel(factory = AppViewModelProvider.Factory) ) { - val coroutineScope = rememberCoroutineScope() userViewModel.setCurrentUser(viewModel.taskUiState.taskDetails.userId) TaskEdit( taskUiState = viewModel.taskUiState, userUiState = userViewModel.userUiState, usersListUiState = userViewModel.usersListUiState, onClick = { - coroutineScope.launch { viewModel.saveTask() navController.popBackStack() - } }, onUpdate = viewModel::updateUiState, onUserUpdate = userViewModel::updateUiState @@ -121,6 +135,7 @@ private fun TaskEdit( onUpdate: (TaskDetails) -> Unit, onUserUpdate: (User) -> Unit ) { + var showInvalidDateDialog by remember { mutableStateOf(false) } Column( Modifier .fillMaxWidth() @@ -140,48 +155,166 @@ private fun TaskEdit( label = { Text(stringResource(id = R.string.task_lastname)) }, singleLine = true ) - UserDropDown( - userUiState = userUiState, - usersListUiState = usersListUiState, - onUserUpdate = { - onUpdate(taskUiState.taskDetails.copy(userId = it.uid)) - onUserUpdate(it) - } - ) + var showDatePicker by remember { mutableStateOf(false) } + if (showDatePicker) { + DatePicker( + onDateSelected = { selectedDate -> + onUpdate(taskUiState.taskDetails.copy(endDate = SimpleDateFormat("dd.MM.yyyy").format(selectedDate))) + showDatePicker = false + }, + onDismissRequest = { + showDatePicker = false + } + ) + } + + Button( + onClick = { showDatePicker = true }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Выбрать дату окончания") + } + OutlinedTextField( modifier = Modifier.fillMaxWidth(), value = taskUiState.taskDetails.endDate, onValueChange = { onUpdate(taskUiState.taskDetails.copy(endDate = it)) }, label = { Text(stringResource(id = R.string.task_phone)) }, - singleLine = true + singleLine = true , + enabled = false ) Button( - onClick = onClick, + onClick = { + if (!isValidDate(taskUiState.taskDetails.endDate)) { + showInvalidDateDialog = true + } else { + onClick() + } + }, enabled = taskUiState.isEntryValid, - shape = MaterialTheme.shapes.small, modifier = Modifier.fillMaxWidth() ) { Text(text = stringResource(R.string.task_save_button)) } } + + if (showInvalidDateDialog) { + AlertDialog( + onDismissRequest = { showInvalidDateDialog = false }, + title = { Text("Неверный формат даты") }, + text = { Text("Введите дату по шаблону: 01.12.2023") }, + confirmButton = { + Button(onClick = { showInvalidDateDialog = false }) { + Text("Подтвердить") + } + } + ) + } +} + +fun isValidDate(date: String): Boolean { + val regex = Regex("""^\d{2}\.\d{2}\.\d{4}$""") + return regex.matches(date) } -@Preview(name = "Light Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO) -@Preview(name = "Dark Mode", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable -fun TaskEditPreview() { - PmudemoTheme { - Surface( - color = MaterialTheme.colorScheme.background +fun CustomCalendarView(onDateSelected: (Date) -> Unit) { + AndroidView( + modifier = Modifier.wrapContentSize(), + factory = { context -> + val calendarView = CalendarView(context) + + // Настройки CalendarView + val calendar = Calendar.getInstance() + + // Устанавливаем минимальную дату выбора на сегодняшний день + calendarView.minDate = calendar.timeInMillis + + calendarView.setOnDateChangeListener { _, year, month, dayOfMonth -> + calendar.set(Calendar.YEAR, year) + calendar.set(Calendar.MONTH, month) + calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth) + onDateSelected(calendar.time) + } + + calendarView + } + ) +} + + +@Composable +fun DatePicker(onDateSelected: (Date) -> Unit, onDismissRequest: () -> Unit) { + val selDate = remember { mutableStateOf(Date()) } + + //todo - add strings to resource after POC + Dialog(onDismissRequest = { onDismissRequest() }, properties = DialogProperties()) { + Column( + modifier = Modifier + .wrapContentSize() + .background( + color = Color.White + ) ) { - TaskEdit( - taskUiState = Task.getTask().toUiState(true), - userUiState = User.DEMO_User.toUiState(), - usersListUiState = UsersListUiState(listOf()), - onClick = {}, - onUpdate = {}, - onUserUpdate = {} - ) + Column( + Modifier + .defaultMinSize(minHeight = 72.dp) + .fillMaxWidth() + .background( + color = MaterialTheme.colorScheme.primary + ) + .padding(16.dp) + ) { + Text( + text = "Выберите дату" + ) + + Spacer(modifier = Modifier.size(24.dp)) + + Text( + text = SimpleDateFormat("MMM d, yyyy", Locale.getDefault()).format(selDate.value) + ) + + Spacer(modifier = Modifier.size(16.dp)) + } + + CustomCalendarView(onDateSelected = { + selDate.value = it + }) + + Spacer(modifier = Modifier.size(8.dp)) + + Row( + modifier = Modifier + .align(Alignment.End) + .padding(bottom = 16.dp, end = 16.dp) + .background( + color = Color.White, + shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp) + ) + ) { + TextButton( + onClick = onDismissRequest + ) { + //TODO - hardcode string + Text( + text = "Отмена" + ) + } + + TextButton( + onClick = { + onDateSelected(selDate.value) + onDismissRequest() + } + ) { + //TODO - hardcode string + Text( + text = "Подтвердить" + ) + } + + } } } } \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/ui/student/edit/StudentEditViewModel.kt b/app/src/main/java/ru/ulstu/is/pmu/ui/student/edit/StudentEditViewModel.kt index 79cbb55..32ed2cd 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/ui/student/edit/StudentEditViewModel.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/ui/student/edit/StudentEditViewModel.kt @@ -7,13 +7,14 @@ import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.launch +import ru.ulstu.`is`.pmu.common.MyViewModel import ru.ulstu.`is`.pmu.common.TaskRepository import ru.ulstu.`is`.pmu.database.task.model.Task class TaskEditViewModel( savedStateHandle: SavedStateHandle, private val taskRepository: TaskRepository -) : ViewModel() { +) : MyViewModel() { var taskUiState by mutableStateOf(TaskUiState()) private set @@ -21,11 +22,16 @@ class TaskEditViewModel( private val taskUid: Int = checkNotNull(savedStateHandle["id"]) init { - viewModelScope.launch { - if (taskUid > 0) { - taskUiState = taskRepository.getTask(taskUid) - .toUiState(true) - } + if (taskUid > 0) { + runInScope( + actionSuccess = { + taskUiState = taskRepository.getTask(taskUid) + .toUiState(true) + }, + actionError = { + taskUiState = TaskUiState() + } + ) } } @@ -35,10 +41,9 @@ class TaskEditViewModel( isEntryValid = validateInput(taskDetails) ) } - - suspend fun saveTask() { + fun saveTask() { if (validateInput()) { - if (taskUid > 0) { + runInScope ( actionSuccess = { if (taskUid > 0) { taskRepository.updateTask( taskUiState.taskDetails.toTask(taskUid) ) @@ -46,7 +51,8 @@ class TaskEditViewModel( taskRepository.insertTask( taskUiState.taskDetails.toTask() ) - } + } + } ) } } @@ -55,7 +61,6 @@ class TaskEditViewModel( name.isNotBlank() && description.isNotBlank() && endDate.isNotBlank() - && userId > 0 } } } diff --git a/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/FavoriteTaskList.kt b/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/FavoriteTaskList.kt index f4543ce..4fd0e6a 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/FavoriteTaskList.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/FavoriteTaskList.kt @@ -1,4 +1,176 @@ -package ru.ulstu.is.pmu.ui.student.list +package ru.ulstu.`is`.pmu.ui.task.list -class FavoriteTaskList { +import android.content.res.Configuration +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.spring +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.ExperimentalMaterialApi +import androidx.compose.material.IconButton +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.DismissDirection +import androidx.compose.material3.DismissState +import androidx.compose.material3.DismissValue +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.SwipeToDismiss +import androidx.compose.material3.Text +import androidx.compose.material3.rememberDismissState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import androidx.paging.PagingData +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.itemContentType +import androidx.paging.compose.itemKey +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.launch +import ru.ulstu.`is`.pmu.common.AppViewModelProvider +import ru.ulstu.`is`.pmu.database.task.model.Task +import ru.ulstu.`is`.pmu.ui.navigation.Screen +import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme + +@Composable +fun FavoriteTaskList( + navController: NavController, + viewModel: FavoriteTaskListViewModel = viewModel(factory = AppViewModelProvider.Factory) +) { + val coroutineScope = rememberCoroutineScope() + val taskListUiState = viewModel.taskListUiState.collectAsLazyPagingItems() + Scaffold( + topBar = {} + ) { innerPadding -> + FavoriteTaskList( + modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + taskList = taskListUiState + ) + } +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) +@Composable +private fun SwipeToDelete( + task: Task +) { + Card( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 7.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = String.format("%s%n%s%n%s", task.name, task.description, task.endDate), + modifier = Modifier.weight(1f) + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) +@Composable +private fun FavoriteTaskList( + modifier: Modifier = Modifier, + taskList: LazyPagingItems +) { + val refreshScope = rememberCoroutineScope() + var refreshing by remember { mutableStateOf(false) } + fun refresh() = refreshScope.launch { + refreshing = true + taskList.refresh() + refreshing = false + } + + val state = rememberPullRefreshState(refreshing, ::refresh) + Box( + modifier = modifier.pullRefresh(state) + ) { + Column( + modifier = modifier.fillMaxSize() + ) { + LazyColumn(modifier = Modifier.padding(all = 10.dp)) { + items( + count = taskList.itemCount, + key = taskList.itemKey(), + contentType = taskList.itemContentType() + ) { index -> + val task = taskList[index] + task?.let { + SwipeToDelete( + task = task + ) + } + } + } + PullRefreshIndicator( + refreshing, state, + Modifier + .align(CenterHorizontally) + .zIndex(100f) + ) + } + } +} + + + +@Composable +private fun TaskListItem( + task: Task, modifier: Modifier = Modifier +) { + Card( + modifier = modifier.fillMaxWidth(), + elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) + ) { + Column( + modifier = modifier.padding(all = 10.dp) + ) { + Text( + text = String.format("%s %s", task.name, task.description) + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/FavoriteTaskListViewModel.kt b/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/FavoriteTaskListViewModel.kt index 41a2d2e..4b1142f 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/FavoriteTaskListViewModel.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/FavoriteTaskListViewModel.kt @@ -1,4 +1,15 @@ -package ru.ulstu.is.pmu.ui.student.list +package ru.ulstu.`is`.pmu.ui.task.list -class FavoriteTaskListViewModel { +import androidx.lifecycle.ViewModel +import androidx.paging.PagingData +import kotlinx.coroutines.flow.Flow +import ru.ulstu.`is`.pmu.common.MyViewModel +import ru.ulstu.`is`.pmu.common.TaskRepository +import ru.ulstu.`is`.pmu.database.task.model.Task + +class FavoriteTaskListViewModel( + private val taskRepository: TaskRepository +) : MyViewModel() { + + val taskListUiState: Flow> = taskRepository.getAllFavoriteTasks() } \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/StudentList.kt b/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/StudentList.kt index 5725fc2..c524da0 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/StudentList.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/StudentList.kt @@ -16,11 +16,13 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.IconButton +import androidx.compose.material.TextButton import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Favorite +import androidx.compose.material.icons.filled.FavoriteBorder import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.rememberPullRefreshState @@ -66,13 +68,20 @@ import ru.ulstu.`is`.pmu.common.AppViewModelProvider import ru.ulstu.`is`.pmu.database.task.model.Task import ru.ulstu.`is`.pmu.ui.navigation.Screen import ru.ulstu.`is`.pmu.ui.theme.PmudemoTheme +import androidx.compose.material3.AlertDialog +import androidx.compose.runtime.remember +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.withStyle +import java.text.SimpleDateFormat +import java.util.* +import java.util.Date @Composable fun TaskList( navController: NavController, viewModel: TaskListViewModel = viewModel(factory = AppViewModelProvider.Factory) ) { - val coroutineScope = rememberCoroutineScope() val taskListUiState = viewModel.taskListUiState.collectAsLazyPagingItems() Scaffold( topBar = {}, @@ -92,21 +101,15 @@ fun TaskList( .padding(innerPadding) .fillMaxSize(), taskList = taskListUiState, - onClick = { uid: Int -> - val route = Screen.TaskEdit.route.replace("{id}", uid.toString()) - navController.navigate(route) - }, onDeleteClick = { task: Task -> // Обработка удаления задачи - coroutineScope.launch { viewModel.deleteTask(task) - } + taskListUiState.refresh() }, onAddToFavoritesClick = { task: Task -> // Обработка добавления задачи в избранное - coroutineScope.launch { - viewModel.favoriteTask(task) - } + if(task.favorite == false) { viewModel.favoriteTask(task) } else viewModel.deletefavoriteTask(task) + taskListUiState.refresh() }, onEditClick = { task: Task -> // Обработка редактирования задачи @@ -145,15 +148,66 @@ fun DismissBackground(dismissState: DismissState) { } } +fun calculateDaysDifference(endDate: Date, currentDate: Date): Int { + try { + val timeDifference = endDate.time - currentDate.time + return (timeDifference / (24 * 60 * 60 * 1000)).toInt() + } catch (e: Exception) { + e.printStackTrace() + } + + return 0 +} + + @OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable private fun SwipeToDelete( task: Task, - onClick: (uid: Int) -> Unit, onDeleteClick: (task: Task) -> Unit, onAddToFavoritesClick: (task: Task) -> Unit, onEditClick: (task: Task) -> Unit ) { + var showDialog by remember { mutableStateOf(false) } + // Добавляем диалоговое окно для подтверждения удаления задачи + if (showDialog) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.5f)) // Меняем уровень прозрачности и цвет + ) { + // Диалоговое окно + AlertDialog( + onDismissRequest = { showDialog = false }, + title = { + Text(text = "Удаление задачи") + }, + text = { + Text(text = "Вы уверены, что хотите удалить эту задачу?") + }, + confirmButton = { + TextButton( + onClick = { + onDeleteClick(task) + showDialog = + false // Закрываем диалоговое окно после подтверждения удаления + } + ) { + Text(text = "Да") + } + }, + dismissButton = { + TextButton( + onClick = { + showDialog = false // Закрываем диалоговое окно без удаления задачи + } + ) { + Text(text = "Отмена") + } + } + ) + } + } Card( modifier = Modifier .fillMaxWidth() @@ -165,8 +219,35 @@ private fun SwipeToDelete( .padding(16.dp), verticalAlignment = Alignment.CenterVertically ) { + val currentDateString = SimpleDateFormat("dd.MM.yyyy").format(Date()) + val currentDate = SimpleDateFormat("dd.MM.yyyy").parse(currentDateString) + + val taskName = task.name + val taskDescription = task.description + val endDate = SimpleDateFormat("dd.MM.yyyy").parse(task.endDate) + + val isOverdue = currentDate.after(endDate) + val overdueText = if (isOverdue) { + val daysDifference = calculateDaysDifference(currentDate, endDate) + val daysWord = if (daysDifference < 23) "дня" else "дней" + "Задача просрочена на $daysDifference $daysWord" + } else { + task.endDate + } + Text( - text = String.format("%s %s", task.name, task.description), + text = buildAnnotatedString { + withStyle(style = SpanStyle(color = Color.Black)) { + append("$taskName\n$taskDescription\n") + } + if (isOverdue) { + withStyle(style = SpanStyle(color = Color.Red)) { + append(overdueText) + } + } else { + append(overdueText) + } + }, modifier = Modifier.weight(1f) ) @@ -177,18 +258,18 @@ private fun SwipeToDelete( Icon( imageVector = Icons.Default.Edit, contentDescription = "Редактировать", - tint = Color.Blue + tint = Color.Black ) } IconButton( - onClick = { onDeleteClick(task) }, + onClick = { showDialog = true }, // Показываем диалоговое окно при нажатии на кнопку удаления modifier = Modifier.padding(start = 8.dp) ) { Icon( imageVector = Icons.Default.Delete, contentDescription = "Удалить", - tint = Color.Red + tint = Color.Black ) } @@ -197,8 +278,9 @@ private fun SwipeToDelete( modifier = Modifier.padding(start = 8.dp) ) { Icon( - imageVector = Icons.Default.Favorite, - contentDescription = "Добавить в избранное" + imageVector = if (task.favorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder, + contentDescription = "Добавить в избранное", + tint = Color.Black ) } } @@ -210,7 +292,6 @@ private fun SwipeToDelete( private fun TaskList( modifier: Modifier = Modifier, taskList: LazyPagingItems, - onClick: (uid: Int) -> Unit, onDeleteClick: (task: Task) -> Unit, onAddToFavoritesClick: (task: Task) -> Unit, onEditClick: (task: Task) -> Unit @@ -240,7 +321,6 @@ private fun TaskList( task?.let { SwipeToDelete( task = task, - onClick = onClick, onDeleteClick = onDeleteClick, onAddToFavoritesClick = onAddToFavoritesClick, onEditClick = onEditClick @@ -256,24 +336,4 @@ private fun TaskList( ) } } -} - - - -@Composable -private fun TaskListItem( - task: Task, modifier: Modifier = Modifier -) { - Card( - modifier = modifier.fillMaxWidth(), - elevation = CardDefaults.cardElevation(defaultElevation = 2.dp) - ) { - Column( - modifier = modifier.padding(all = 10.dp) - ) { - Text( - text = String.format("%s %s", task.name, task.description) - ) - } - } } \ No newline at end of file diff --git a/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/StudentListViewModel.kt b/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/StudentListViewModel.kt index 06a0368..6b73535 100644 --- a/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/StudentListViewModel.kt +++ b/app/src/main/java/ru/ulstu/is/pmu/ui/student/list/StudentListViewModel.kt @@ -3,17 +3,25 @@ package ru.ulstu.`is`.pmu.ui.task.list import androidx.lifecycle.ViewModel import androidx.paging.PagingData import kotlinx.coroutines.flow.Flow +import ru.ulstu.`is`.pmu.common.MyViewModel import ru.ulstu.`is`.pmu.common.TaskRepository import ru.ulstu.`is`.pmu.database.task.model.Task class TaskListViewModel( private val taskRepository: TaskRepository -) : ViewModel() { +) : MyViewModel() { - val taskListUiState: Flow> = taskRepository.getAllTasks() + val taskListUiState: Flow> = taskRepository.getAllDateTasks() - suspend fun deleteTask(task: Task) { - taskRepository.deleteTask(task) + fun deleteTask(task: Task) { + runInScope ( actionSuccess = {taskRepository.deleteTask(task)}) } + fun favoriteTask(task: Task) { + runInScope ( actionSuccess = {taskRepository.favoriteTask(task)}) + } + + fun deletefavoriteTask(task: Task) { + runInScope ( actionSuccess = {taskRepository.deletefavoriteTask(task)}) + } } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0bfdf34..1756bdf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,16 +1,22 @@ pmu-demo - Имя - Фамилия - Группа - Телефон + Название задачи + Описание задачи + Пользователь + Дата окончания e-mail - Список студентов + Список задач Профиль студента + Избранные Записи о студентах отсутствуют Группа не указана Сохранить О нас + Назад + Загрузка… + Отчет + Дата начала + Дата конца

Это текст о нас!

\n\n

Здесь могла быть Ваша реклама!

\n\n diff --git a/server/.gitignore b/server/.gitignore deleted file mode 100644 index 1b06270..0000000 --- a/server/.gitignore +++ /dev/null @@ -1,42 +0,0 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. - -# Compiled output -/dist -/tmp -/out-tsc -/bazel-out - -# Node -/node_modules -npm-debug.log -yarn-error.log - -# IDEs and editors -.idea/ -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# Visual Studio Code -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -.history/* - -# Miscellaneous -/.angular/cache -.sass-cache/ -/connect.lock -/coverage -/libpeerconnection.log -testem.log -/typings - -# System files -.DS_Store -Thumbs.db \ No newline at end of file diff --git a/server/data.json b/server/data.json index 94ae9c7..85889b4 100644 --- a/server/data.json +++ b/server/data.json @@ -14,11 +14,15 @@ "tasks": [ { "id": 1, - "name": "123456", - "description": "32155", - "endDate": "55", - "favorite": false, - "userId": 1 + "name": "dsads", + "description": "12312312", + "endDate": "02.11.2023" + }, + { + "id": 3, + "name": "test", + "description": "test", + "endDate": "05.12.2023" } ] } \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index 0203f7f..9e6f93f 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -7,10 +7,26 @@ "": { "name": "fake-db", "version": "1.0.0", + "dependencies": { + "pdfkit": "^0.14.0" + }, "devDependencies": { "json-server": "0.17.4" } }, + "node_modules/@swc/helpers": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.3.17.tgz", + "integrity": "sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@swc/helpers/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -48,12 +64,54 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -90,6 +148,14 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dependencies": { + "base64-js": "^1.1.2" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -103,7 +169,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2", "get-intrinsic": "^1.2.1", @@ -143,6 +208,14 @@ "node": ">=12" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -278,6 +351,11 @@ "node": ">= 0.10" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -287,11 +365,46 @@ "ms": "2.0.0" } }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-equal/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, "node_modules/define-data-property": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.1", "gopd": "^1.0.1", @@ -301,6 +414,22 @@ "node": ">= 0.4" } }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -320,6 +449,11 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -354,6 +488,30 @@ "node": ">= 0.8" } }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -516,6 +674,30 @@ "node": ">= 0.8" } }, + "node_modules/fontkit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.9.0.tgz", + "integrity": "sha512-HkW/8Lrk8jl18kzQHvAw9aTHe1cqsyx5sDnxncx652+CIfhawokEPkeM3BoIC+z/Xv7a0yMr0f3pRRwhGH455g==", + "dependencies": { + "@swc/helpers": "^0.3.13", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "deep-equal": "^2.0.5", + "dfa": "^1.2.0", + "restructure": "^2.0.1", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.3.1", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -538,7 +720,14 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -556,7 +745,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "dev": true, "dependencies": { "function-bind": "^1.1.2", "has-proto": "^1.0.1", @@ -571,7 +759,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -585,6 +772,14 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -598,7 +793,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.2" }, @@ -610,7 +804,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -622,7 +815,20 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, "engines": { "node": ">= 0.4" }, @@ -634,7 +840,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -676,6 +881,19 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/internal-slot": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "dependencies": { + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -685,6 +903,85 @@ "node": ">= 0.10" } }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -694,12 +991,130 @@ "node": ">=8" } }, + "node_modules/is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-promise": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", "dev": true }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", @@ -754,6 +1169,23 @@ "node": ">=12" } }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -940,7 +1372,46 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -981,6 +1452,17 @@ "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", "dev": true }, + "node_modules/pdfkit": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.14.0.tgz", + "integrity": "sha512-Hnor8/78jhHm6ONrxWhrqOwAVALlBnFyWOF8sstBZMiqHZgZ5A6RU+Q3yahhw82plxpT7LOfH3b3qcOX6rzMQg==", + "dependencies": { + "crypto-js": "^4.2.0", + "fontkit": "^1.8.1", + "linebreak": "^1.0.2", + "png-js": "^1.0.0" + } + }, "node_modules/pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", @@ -1008,6 +1490,11 @@ "node": ">=4" } }, + "node_modules/png-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", + "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1060,6 +1547,22 @@ "node": ">= 0.8" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -1069,6 +1572,11 @@ "node": ">=0.10.0" } }, + "node_modules/restructure": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz", + "integrity": "sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==" + }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -1142,7 +1650,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", - "dev": true, "dependencies": { "define-data-property": "^1.1.1", "get-intrinsic": "^1.2.1", @@ -1153,6 +1660,19 @@ "node": ">= 0.4" } }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -1163,7 +1683,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -1191,6 +1710,17 @@ "graceful-fs": "^4.1.3" } }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -1229,6 +1759,11 @@ "node": ">=8" } }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1251,6 +1786,29 @@ "node": ">= 0.6" } }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1278,6 +1836,53 @@ "node": ">= 0.8" } }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/server/package.json b/server/package.json index 0fb3100..1e6c73f 100644 --- a/server/package.json +++ b/server/package.json @@ -2,9 +2,10 @@ "name": "fake-db", "version": "1.0.0", "scripts": { - "start": "json-server --watch data.json --host 0.0.0.0 -p 8079" + "start": "json-server --watch data.json --middlewares ./server.js --host 0.0.0.0 -p 8079" }, "dependencies": { + "pdfkit": "^0.14.0" }, "devDependencies": { "json-server": "0.17.4"