From 61faa1af74b1a68acec7c593dcc01fd63de97834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=B0=D1=88=D0=B8=D0=BD=20=D0=9C=D0=B0=D0=BA=D1=81?= =?UTF-8?q?=D0=B8=D0=BC?= Date: Mon, 25 Dec 2023 20:40:46 +0400 Subject: [PATCH] =?UTF-8?q?=D0=A3=D0=BA=D1=80=D0=B0=D0=BB=20=D0=B2=D1=81?= =?UTF-8?q?=D0=B5,=20=D1=87=D1=82=D0=BE=20=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE,?= =?UTF-8?q?=20=D0=BA=D0=B0=D0=BA=D0=B8=D0=BC=20=D1=82=D0=BE=20=D1=87=D1=83?= =?UTF-8?q?=D0=B4=D0=BE=D0=BC=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5?= =?UTF-8?q?=D1=82,=20=D1=81=D0=B5=D0=B9=D1=87=D0=B0=D1=81=20=D0=B1=D1=83?= =?UTF-8?q?=D0=B4=D1=83=20=D0=BD=D0=BE=D1=80=D0=BC=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D0=BB=D1=8B=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=20PDF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../myapplication/api/MyServerService.kt | 7 + .../api/item/RestItemRepository.kt | 5 + .../myapplication/api/report/ReportRemote.kt | 12 ++ .../composeui/navigation/MainNavbar.kt | 2 + .../composeui/navigation/Screen.kt | 3 + .../composeui/AppViewModelProvider.kt | 5 + .../database/entities/composeui/ReportPage.kt | 156 ++++++++++++++++++ .../entities/composeui/ReportViewModel.kt | 57 +++++++ .../entities/composeui/UserProfile.kt | 3 +- app/src/main/res/values/strings.xml | 3 + server/router.js | 61 +++++++ 11 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/example/myapplication/api/report/ReportRemote.kt create mode 100644 app/src/main/java/com/example/myapplication/database/entities/composeui/ReportPage.kt create mode 100644 app/src/main/java/com/example/myapplication/database/entities/composeui/ReportViewModel.kt create mode 100644 server/router.js diff --git a/app/src/main/java/com/example/myapplication/api/MyServerService.kt b/app/src/main/java/com/example/myapplication/api/MyServerService.kt index 1558ca5..1088826 100644 --- a/app/src/main/java/com/example/myapplication/api/MyServerService.kt +++ b/app/src/main/java/com/example/myapplication/api/MyServerService.kt @@ -5,6 +5,7 @@ import com.example.myapplication.api.rent.RentRemote import com.example.myapplication.api.item.ItemFromBikeRemote import com.example.myapplication.api.item.ItemRemote import com.example.myapplication.api.item.ItemWithBikeRemote +import com.example.myapplication.api.report.ReportRemote import com.example.myapplication.api.user.UserRemote import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import kotlinx.serialization.json.Json @@ -23,6 +24,12 @@ import retrofit2.http.Path import retrofit2.http.Query interface MyServerService { + + @GET("report") + suspend fun getReportInfo( + @Query("fromDate") fromDate: String, + @Query("toDate") toDate: String + ): List @GET("rents") suspend fun getRents(): List @GET("users/{id}") diff --git a/app/src/main/java/com/example/myapplication/api/item/RestItemRepository.kt b/app/src/main/java/com/example/myapplication/api/item/RestItemRepository.kt index 9b18a9b..453a43e 100644 --- a/app/src/main/java/com/example/myapplication/api/item/RestItemRepository.kt +++ b/app/src/main/java/com/example/myapplication/api/item/RestItemRepository.kt @@ -1,6 +1,7 @@ package com.example.myapplication.api.item import com.example.myapplication.api.MyServerService +import com.example.myapplication.api.report.ReportRemote import com.example.myapplication.database.entities.model.Item import com.example.myapplication.database.entities.repository.OfflineRentItemRepository import com.example.myapplication.database.entities.repository.OfflineItemRepository @@ -48,4 +49,8 @@ class RestItemRepository( dbRentItemRepository.deleteItemsByUid(item.uid) dbItemRepository.deleteItem(item) } + 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/com/example/myapplication/api/report/ReportRemote.kt b/app/src/main/java/com/example/myapplication/api/report/ReportRemote.kt new file mode 100644 index 0000000..069833f --- /dev/null +++ b/app/src/main/java/com/example/myapplication/api/report/ReportRemote.kt @@ -0,0 +1,12 @@ +package com.example.myapplication.api.report + +import kotlinx.serialization.Serializable + +@Serializable +data class ReportRemote( + val id: Int = 0, + val dateTime: Int = 0, + val weight: Double = 0.0, + val maxCount: Int = 0, + val bikeId: Int = 0 +) \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/composeui/navigation/MainNavbar.kt b/app/src/main/java/com/example/myapplication/composeui/navigation/MainNavbar.kt index 295c8f9..66c3559 100644 --- a/app/src/main/java/com/example/myapplication/composeui/navigation/MainNavbar.kt +++ b/app/src/main/java/com/example/myapplication/composeui/navigation/MainNavbar.kt @@ -55,6 +55,7 @@ import com.example.myapplication.database.entities.composeui.CurrentUserViewMode import com.example.myapplication.database.entities.composeui.LoginScreen import com.example.myapplication.database.entities.composeui.RentList import com.example.myapplication.database.entities.composeui.RentView +import com.example.myapplication.database.entities.composeui.ReportPage import com.example.myapplication.database.entities.composeui.UserProfile import com.example.myapplication.database.entities.composeui.edit.BikeEdit import com.example.myapplication.database.entities.composeui.edit.ItemEdit @@ -190,6 +191,7 @@ fun Navhost( composable(Screen.RentList.route) { RentList(navController, currentUserViewModel = currentUserViewModel) } composable(Screen.Cart.route) { Cart(currentUserViewModel = currentUserViewModel) } composable(Screen.UserProfile.route) { UserProfile(navController,isDarkTheme, dataStore, currentUserViewModel = currentUserViewModel) } + composable(Screen.Report.route) { ReportPage(navController = navController) } composable(Screen.LoginScreen.route) { LoginScreen(navController) } composable( Screen.BikeEdit.route, diff --git a/app/src/main/java/com/example/myapplication/composeui/navigation/Screen.kt b/app/src/main/java/com/example/myapplication/composeui/navigation/Screen.kt index 7a9e7bd..96acff0 100644 --- a/app/src/main/java/com/example/myapplication/composeui/navigation/Screen.kt +++ b/app/src/main/java/com/example/myapplication/composeui/navigation/Screen.kt @@ -15,6 +15,9 @@ enum class Screen( val icon: ImageVector = Icons.Filled.Favorite, val showInBottomBar: Boolean = true ) { + Report( + "report", R.string.report + ), BikeList( "Bike-list", R.string.Bike_main_title, Icons.Filled.Home ), diff --git a/app/src/main/java/com/example/myapplication/database/entities/composeui/AppViewModelProvider.kt b/app/src/main/java/com/example/myapplication/database/entities/composeui/AppViewModelProvider.kt index 01d96c5..4756075 100644 --- a/app/src/main/java/com/example/myapplication/database/entities/composeui/AppViewModelProvider.kt +++ b/app/src/main/java/com/example/myapplication/database/entities/composeui/AppViewModelProvider.kt @@ -23,6 +23,11 @@ object AppViewModelProvider { initializer { BikeListViewModel(bikeApplication().container.bikeRestRepository) } + initializer { + ReportViewModel( + bikeApplication().container.itemRestRepository, + ) + } initializer { BikeEditViewModel( this.createSavedStateHandle(), diff --git a/app/src/main/java/com/example/myapplication/database/entities/composeui/ReportPage.kt b/app/src/main/java/com/example/myapplication/database/entities/composeui/ReportPage.kt new file mode 100644 index 0000000..776f85b --- /dev/null +++ b/app/src/main/java/com/example/myapplication/database/entities/composeui/ReportPage.kt @@ -0,0 +1,156 @@ +package com.example.myapplication.database.entities.composeui + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +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.DatePicker +import androidx.compose.material3.DisplayMode +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.rememberDatePickerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +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.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import com.example.myapplication.R +import com.example.myapplication.api.report.ReportRemote +import kotlinx.coroutines.launch +import java.util.Date + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ReportPage (navController: NavController?, viewModel: ReportViewModel = viewModel(factory = AppViewModelProvider.Factory)) +{ + + val dateStateStart = rememberDatePickerState(initialDisplayMode = DisplayMode.Input) + val dateStateEnd = rememberDatePickerState(initialDisplayMode = DisplayMode.Input) + + val coroutineScope = rememberCoroutineScope() + val reportResultPageState = viewModel.reportResultPageUiState + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) + { + Text( + text = stringResource(id = R.string.startDate), + style = MaterialTheme.typography.headlineLarge + ) + DatePicker( + state = dateStateStart, + ) + val selectedDateStart = dateStateStart.selectedDateMillis + if (selectedDateStart != null) { + val resultDate= Date(selectedDateStart) + viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(startDate = resultDate)) + } + else + { + viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(startDate = Date(0))) + } + Text( + text = stringResource(id = R.string.endDate), + style = MaterialTheme.typography.headlineLarge + ) + + DatePicker( + state = dateStateEnd, + ) + val selectedDateEnd = dateStateEnd.selectedDateMillis + if (selectedDateEnd != null) { + val resultDate = Date(selectedDateEnd) + viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(endDate = resultDate)) + } + else + { + viewModel.onUpdate(viewModel.reportPageUiState.reportDetails.copy(endDate = Date(0))) + } + + Spacer(modifier = Modifier.height(16.dp)) + Button( + onClick = {coroutineScope.launch { viewModel.getReport() } }, + enabled = viewModel.reportPageUiState.isEntryValid, + modifier = Modifier + .fillMaxWidth() + .padding(all = 10.dp) + .border(4.dp, MaterialTheme.colorScheme.onPrimary, shape = RoundedCornerShape(10.dp)), + shape = RoundedCornerShape(10.dp), + colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.primary) + ) { + Text("Сформировать отчет") + } + Spacer(modifier = Modifier.height(32.dp)) + Text( + text = "Результат", + style = MaterialTheme.typography.headlineLarge + ) + TableScreen(reportData = reportResultPageState.resReport) + } +} + +@Composable +fun RowScope.TableCell( + text: String, + weight: Float +) { + Text( + text = text, + Modifier + .border(1.dp, Color.Black) + .weight(weight) + .padding(8.dp) + ) +} + + +@Composable +fun TableScreen(reportData: List) { + + val column1Weight = .3f // 30% + val column2Weight = 1f // 30% + + Column( + Modifier + .padding(16.dp)) { + + Row(Modifier.background(Color.White)) { + TableCell(text = "ID", weight = column1Weight) + TableCell(text = "Товар", weight = column1Weight) + TableCell(text = "Кол-во", weight = column1Weight) + TableCell(text = "Цена", weight = column1Weight) + } + + // Here are all the lines of your table. + reportData.forEach { + val (productId, productName, sellsAmount, sellsPrice) = it + Row(Modifier.fillMaxWidth()) { + TableCell(text = productId.toString(), weight = column1Weight) + TableCell(text = sellsAmount.toString(), weight = column1Weight) + TableCell(text = sellsPrice.toString(), weight = column1Weight) + } + } +/* Row(Modifier.fillMaxWidth()) { + TableCell(text = "Общая цена: " + reportData.map { x -> x.sellsPrice}.sum().toString(), weight = column2Weight) + }*/ + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/myapplication/database/entities/composeui/ReportViewModel.kt b/app/src/main/java/com/example/myapplication/database/entities/composeui/ReportViewModel.kt new file mode 100644 index 0000000..2d1ebea --- /dev/null +++ b/app/src/main/java/com/example/myapplication/database/entities/composeui/ReportViewModel.kt @@ -0,0 +1,57 @@ +package com.example.myapplication.database.entities.composeui + +import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import com.example.myapplication.api.item.RestItemRepository +import com.example.myapplication.api.report.ReportRemote +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +class ReportViewModel(private val itemRespository: RestItemRepository): ViewModel() { + var reportPageUiState by mutableStateOf(ReportPageUiState()) + private set + + var reportResultPageUiState by mutableStateOf(ReportResultPageUiState()) + private set + + fun onUpdate(reportDetails: ReportDetails) { + reportPageUiState = ReportPageUiState(reportDetails = reportDetails, isEntryValid = validateInput(reportDetails)) + } + + private fun validateInput(uiState: ReportDetails = reportPageUiState.reportDetails): Boolean { + Log.d("validateInput", uiState.endDate.toString()) + return with(uiState) { + startDate != Date(0) && endDate != Date(0) && startDate < endDate + } + } + + suspend fun getReport() { + val dateFormatter = SimpleDateFormat("yyyy", Locale.getDefault()) + + // Преобразование даты в строку в формате "yyyy" + val startDateQueryParam = dateFormatter.format(reportPageUiState.reportDetails.startDate) + val endDateQueryParam = dateFormatter.format(reportPageUiState.reportDetails.endDate) + + // Вызов репозитория с отформатированными датами + val res = itemRespository.getReport(startDateQueryParam, endDateQueryParam) + reportResultPageUiState = ReportResultPageUiState(res) + } +} + +data class ReportDetails( + val startDate: Date = Date(0), + val endDate: Date = Date(0) +) + +data class ReportPageUiState( + val reportDetails: ReportDetails = ReportDetails(), + val isEntryValid: Boolean = false +) + +data class ReportResultPageUiState( + var resReport: List = emptyList() +) diff --git a/app/src/main/java/com/example/myapplication/database/entities/composeui/UserProfile.kt b/app/src/main/java/com/example/myapplication/database/entities/composeui/UserProfile.kt index 6a00b1e..2eca0c2 100644 --- a/app/src/main/java/com/example/myapplication/database/entities/composeui/UserProfile.kt +++ b/app/src/main/java/com/example/myapplication/database/entities/composeui/UserProfile.kt @@ -285,7 +285,8 @@ fun UserProfile( Button( onClick = { coroutineScope.launch { - createPdfFile(context = context, fileName = "отчет.pdf") + //createPdfFile(context = context, fileName = "отчет.pdf") + navController?.navigate(Screen.Report.route) Log.d("context", "${context}") } }, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0a2844b..14985ca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,9 @@ Аренда Название Описание + Отчет + Дата начала + Дата конца Изображение Подтверждение Аренда diff --git a/server/router.js b/server/router.js new file mode 100644 index 0000000..3750d9c --- /dev/null +++ b/server/router.js @@ -0,0 +1,61 @@ +const data = require('./data.json'); + +module.exports = (req, res, next) => { + if (req.url.startsWith('/search') && req.method === 'GET') { + try { + const searchText = req.query.name; + const searched = data.products.filter( + (product) => + (product.name.toLowerCase()).includes(searchText.toLowerCase()) + ); + return res.json(searched); + } catch (error) { + console.error('Error loading data:', error); + res.status(500).json({ message: 'Internal Server Error' }); + } + } else if (req.url.startsWith('/report') && req.method === 'GET') { + try { + // Добавляем фильтрацию по полю dateTime + const fromDate = req.query.fromDate; + const toDate = req.query.toDate; + const filteredData = filterByDateTime(data.items, fromDate, toDate); + + // Возвращаем отфильтрованные элементы + const reportData = generateReport(filteredData); + res.json(reportData); + } catch (error) { + console.error('Error loading data:', error); + return res.status(500).json({ message: 'Internal Server Error' }); + } + } else { + next(); + } +}; + +// Функция generateReport для примера +function generateReport(items) { + // Ваш код для генерации отчета + return items.map((item) => ({ + id: item.id, + dateTime: item.dateTime, + weight: item.weight, + maxCount: item.maxCount, + bikeId: item.bikeId + + // Другие поля отчета, которые вам нужны + })); +} + +// Функция для фильтрации по полю dateTime +function filterByDateTime(items, fromDate, toDate) { + if (!fromDate && !toDate) { + return items; + } + + const filteredItems = items.filter((item) => { + const itemDate = item.dateTime; + return (!fromDate || itemDate >= fromDate) && (!toDate || itemDate <= toDate); + }); + + return filteredItems; +}