Merge branch 'reservation-test'
This commit is contained in:
34
README.md
34
README.md
@@ -11,7 +11,8 @@
|
||||
|
||||
Lien du git : gitlab2.istic.univ-rennes1.fr/trochas/mmm-projet
|
||||
|
||||
Différentes commandes a effectuer pour lancer le projet:
|
||||
|
||||
#### Différentes commandes a effectuer pour lancer le projet:
|
||||
|
||||
npx expo install react-native-maps@1.9.0
|
||||
npm install react-native-maps @react-navigation/native @react-navigation/bottom-tabs react-native-safe-area-context react-native-screens firebase
|
||||
@@ -20,4 +21,33 @@ npm install @react-native-community/datetimepicker
|
||||
npx expo install expo-image-picker
|
||||
npx expo install expo-location
|
||||
|
||||
npx expo start
|
||||
npx expo start
|
||||
|
||||
|
||||
#### Présentation de l'application :
|
||||
|
||||
##### 5 écrans :
|
||||
Accuil :
|
||||
- Affiche le chantier sélectionné :
|
||||
- Résumé du chantier
|
||||
- état éditable par l'utilisateur
|
||||
- Liste d'anomalies, possibilité d'en ajouter ou de les supprimer
|
||||
- Sélectionner un chantier via le bouton en haut à gauche.
|
||||
|
||||
Ressources :
|
||||
|
||||
Ouvriers :
|
||||
|
||||
MapScreen :
|
||||
|
||||
Ajouter :
|
||||
Permet d'ajouter un chantier ou une ressource (ouvrier,véhicule,outil)
|
||||
##### Fonctionnalité manquante :
|
||||
|
||||
Par manque de temps nous n'avons pas peu finnalité certaine fonctionnalité
|
||||
|
||||
- possibilité de modifier les ressources d'un chantier (ex: réajustement des besoins)
|
||||
- modifier la quantité totale d'une ressource (ex: restock de ressources)
|
||||
- gestion des stocks non finalisée :
|
||||
Un chantier comptabilise du stock uniquement quand il est "En cours", s'il est dans un autre été, ses réservations ne sont pas comptabilisées, donc les autres chantiers peuvent utiliser le stock libéré. Si on le remet l'état à "En cours" et que le stock n'est pas suffisant, alors la quantité disponible du stock passe en négatif.
|
||||
Ce problème peut être corrigé en bloquant le changement d'état si la quantité de stock n'est pas suffisante, mais aurait besoin de la possibilité de modifier les ressources du chantier, ou la quantité des ressources.
|
||||
@@ -9,7 +9,11 @@ import { ThemedView } from "@/components/theme/themed-view";
|
||||
|
||||
export default function AddScreen() {
|
||||
const [typeAdd, setTypeAdd] = useState('');
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
|
||||
function onPressSwitchMode(){
|
||||
setEditMode(!editMode);
|
||||
}
|
||||
|
||||
return(
|
||||
<ThemedView lvl={3} style={styles.back}>
|
||||
|
||||
@@ -5,7 +5,6 @@ import Constants from "expo-constants"; //pour connaître la taille de la barre
|
||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { FlatList, Image, StyleSheet, Text, View } from "react-native";
|
||||
import rawConcerts from "../../data/concerts.json";
|
||||
import { getUsers } from "@/services/ressourcesService";
|
||||
import { useChantier } from "../ContextChantier";
|
||||
import SelectChantier from "@/components/selectChantier";
|
||||
@@ -34,7 +33,7 @@ export default function GestionOuvrier() {
|
||||
async function loadData() {
|
||||
try {
|
||||
//Nous ne gardons que les Ouvriers, qui peuvent être assignés à un chantier
|
||||
const data = (await getRessources()).filter(u => u.type === "ouvrier");
|
||||
const data = (await getRessources()).filter(u => u.type === "Ouvrier");
|
||||
setRessources(data);
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du chargement :", error);
|
||||
@@ -43,19 +42,6 @@ export default function GestionOuvrier() {
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const concertsData: Concert[] = Array.isArray(rawConcerts)
|
||||
? (rawConcerts as Concert[])
|
||||
: [];
|
||||
|
||||
const filteredData = useMemo(() => {
|
||||
if (!Array.isArray(concertsData)) return [];
|
||||
const q = search.trim().toLowerCase();
|
||||
if (!q) return concertsData;
|
||||
return concertsData.filter(
|
||||
(item) => !!item && (item.group ?? "").toLowerCase().includes(q)
|
||||
);
|
||||
}, [concertsData, search]);
|
||||
|
||||
const renderItem = ({ item, index }: { item?: Ressources; index: number }) => {
|
||||
if (!item) {
|
||||
return null;
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useRessources } from "../ContextRessource";
|
||||
export default function GestionnaireRessource() {
|
||||
const [search, setSearch] = useState("");
|
||||
const {ressources, setRessources} = useRessources();
|
||||
const [filterType, setFilterType] = useState("tout");
|
||||
const [filterType, setFilterType] = useState("Tout");
|
||||
const [showFilterMenu, setShowFilterMenu] = useState(false);
|
||||
const router = useRouter();
|
||||
|
||||
@@ -33,7 +33,7 @@ export default function GestionnaireRessource() {
|
||||
|
||||
const filteredData = ressources.filter((r) => {
|
||||
const matchName = r.name.toLowerCase().includes(search.toLowerCase());
|
||||
const matchType = filterType === "tout" || r.type === filterType;
|
||||
const matchType = filterType === "Tout" || r.type === filterType;
|
||||
return matchName && matchType;
|
||||
});
|
||||
|
||||
@@ -43,6 +43,7 @@ export default function GestionnaireRessource() {
|
||||
<ThemedView lvl={1} shadow={true} style={styles.card}>
|
||||
<Image source={{ uri: item.Image }} style={styles.image} />
|
||||
<ThemedView lvl={1} style={styles.info}>
|
||||
<ThemedText>Id : {item.id}</ThemedText>
|
||||
<ThemedText>Nom : {item.name}</ThemedText>
|
||||
<ThemedText>Type : {item.type}</ThemedText>
|
||||
<ThemedText>Quantité totale : {item.quantity}</ThemedText>
|
||||
@@ -68,7 +69,7 @@ export default function GestionnaireRessource() {
|
||||
<ThemedView lvl={2} style={styles.filterMenuOverlay}>
|
||||
<ThemedView lvl={5} style={styles.filterMenu}>
|
||||
<ThemedText style={styles.filterTitle}>Filtrer par type</ThemedText>
|
||||
{["tout", "Outil", "Machine"].map((t) => (
|
||||
{["Tout", "Outil", "Machine","Ouvrier"].map((t) => (
|
||||
<ThemedButton
|
||||
key={t}
|
||||
lvl={1}
|
||||
|
||||
@@ -23,9 +23,11 @@ export default function Home() {
|
||||
<View style={{width:"100%", position: 'absolute'}}>
|
||||
<SelectChantier></SelectChantier>
|
||||
</View>
|
||||
{chantier&&
|
||||
<View style={{width:"100%", position: 'absolute',marginLeft:"50%"}}>
|
||||
<SetStatus></SetStatus>
|
||||
</View>
|
||||
}
|
||||
<ScrollView>
|
||||
<View style={{paddingTop:60}}>
|
||||
<ChantierSummary style={styles.summary} data={{ chantier }} />
|
||||
|
||||
33
app/ContextReservation.tsx
Normal file
33
app/ContextReservation.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Reservation } from "@/class/class";
|
||||
import { createContext, ReactNode, useContext, useMemo, useState } from "react";
|
||||
|
||||
type ReservationContextType = {
|
||||
reservations: Reservation[];
|
||||
setReservations: (list: Reservation[]) => void;
|
||||
};
|
||||
|
||||
const ReservationsContext = createContext<ReservationContextType | null>(null);
|
||||
|
||||
type ReservationsProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const ReservationsProvider = ({ children }: ReservationsProviderProps) => {
|
||||
const [reservations, setReservations] = useState<Reservation[]>([]);
|
||||
|
||||
const value = useMemo(() => ({ reservations, setReservations }), [reservations]);
|
||||
|
||||
return (
|
||||
<ReservationsContext.Provider value={value}>
|
||||
{children}
|
||||
</ReservationsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useReservations = () => {
|
||||
const context = useContext(ReservationsContext);
|
||||
if (!context) {
|
||||
throw new Error("useRessources doit être utilisé dans <ReservationsContext>");
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -16,9 +16,11 @@ import { Platform, UIManager } from 'react-native';
|
||||
import { ChantierProvider } from "./ContextChantier";
|
||||
import { UserProvider } from "./ContextUser";
|
||||
import { RessourcesProvider } from "./ContextRessource";
|
||||
import { ReservationsProvider } from "./ContextReservation";
|
||||
import LoginScreen from "./login/login";
|
||||
|
||||
|
||||
|
||||
export const unstable_settings = {
|
||||
anchor: "(tabs)",
|
||||
};
|
||||
@@ -60,18 +62,20 @@ export default function RootLayout() {
|
||||
<UserProvider>
|
||||
<ChantierProvider>
|
||||
<RessourcesProvider>
|
||||
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
|
||||
<Stack>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="selectChantier" options={{ headerShown: false }}/>
|
||||
<Stack.Screen
|
||||
name="modal"
|
||||
options={{ presentation: "modal", title: "Modal" }}
|
||||
/>
|
||||
<Stack.Screen name="login" options={{ headerShown: false }} />
|
||||
</Stack>
|
||||
<StatusBar style="auto" />
|
||||
</ThemeProvider>
|
||||
<ReservationsProvider>
|
||||
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
|
||||
<Stack>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="selectChantier" options={{ headerShown: false }}/>
|
||||
<Stack.Screen
|
||||
name="modal"
|
||||
options={{ presentation: "modal", title: "Modal" }}
|
||||
/>
|
||||
<Stack.Screen name="login" options={{ headerShown: false }} />
|
||||
</Stack>
|
||||
<StatusBar style="auto" />
|
||||
</ThemeProvider>
|
||||
</ReservationsProvider>
|
||||
</RessourcesProvider>
|
||||
</ChantierProvider>
|
||||
</UserProvider>
|
||||
|
||||
@@ -5,11 +5,11 @@ export type Chantier = {
|
||||
etat: string;
|
||||
contact: string;
|
||||
chef: User;
|
||||
equipe: User[];
|
||||
materiel: Ressources[];
|
||||
equipe: Reservation[];
|
||||
materiel: Reservation[];
|
||||
vehicules: Reservation[];
|
||||
dateDep: Date;
|
||||
tempsEst: number;
|
||||
vehicules: Ressources[];
|
||||
anomalies: string[];
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
@@ -19,7 +19,7 @@ export type User = {
|
||||
id: string;
|
||||
name: string;
|
||||
last_name: string;
|
||||
allocation: Reservation[];
|
||||
allocation?: Reservation[];
|
||||
role: string;
|
||||
qualifications: string;
|
||||
};
|
||||
@@ -27,7 +27,7 @@ export type User = {
|
||||
export type Ressources = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string; //"machine","outil","ouvrier"
|
||||
type: string; //"Machine","Outil","Ouvrier"
|
||||
Image: string;
|
||||
quantity: number;
|
||||
available_quantity: number;
|
||||
@@ -36,6 +36,7 @@ export type Ressources = {
|
||||
|
||||
export type Reservation = {
|
||||
id: string;
|
||||
dateChantier: Date;
|
||||
dateFin: Date;
|
||||
chantier: Chantier;
|
||||
ressource: Ressources;
|
||||
quantity: number;
|
||||
};
|
||||
31
class/utils.tsx
Normal file
31
class/utils.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Reservation,Chantier,User, Ressources } from "./class";
|
||||
|
||||
export function getNbItemReservation(reservations:Reservation[]):number{
|
||||
var res = 0;
|
||||
reservations.forEach(reserv => {
|
||||
res += reserv.quantity;
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
export function getReservationOfRessource(ressource:Ressources, allReservations:Reservation[]):Reservation[]{
|
||||
const res:Reservation[] = [];
|
||||
allReservations.forEach(reserv => {
|
||||
if(reserv.ressource.name===ressource.name){
|
||||
res.push(reserv);
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
export function getNbUseRessources(ressource:Ressources, allReservations:Reservation[]):number{
|
||||
var res:number = 0;
|
||||
getReservationOfRessource(ressource,allReservations).forEach(reserv => {
|
||||
if(reserv.chantier.etat==="En cours"){
|
||||
res+=reserv.quantity;
|
||||
}
|
||||
})
|
||||
return res;
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import { StyleSheet, ScrollView, Button, TextInput, Text, View, Modal } from 're
|
||||
import { useChantier } from '../../app/ContextChantier';
|
||||
import { useRessources } from '../../app/ContextRessource';
|
||||
import { useUser } from '../../app/ContextUser';
|
||||
import { getRessources, getUsers, addChantier } from '@/services/ressourcesService';
|
||||
import { Chantier, Ressources, User } from '@/class/class';
|
||||
import { getRessources, getUsers, addChantier, sendNewChantier } from '@/services/ressourcesService';
|
||||
import { Chantier, Ressources, User, Reservation } from '@/class/class';
|
||||
import { ThemedText } from '@/components/theme/themed-text';
|
||||
import { ThemedButton } from '@/components/theme/themed-button';
|
||||
import { ThemedTextInput } from '@/components/theme/themed-textinput';
|
||||
@@ -34,7 +34,6 @@ export default function AddChantier() {
|
||||
|
||||
const [editMode,setEditMode] = useState(false);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [objet, setObjet] = useState('');
|
||||
const [date, setDate] = useState(new Date());
|
||||
const [morning, setMorning] = useState(true);
|
||||
@@ -44,6 +43,7 @@ export default function AddChantier() {
|
||||
const [contact, setContact] = useState('');
|
||||
const [machines, setMachines] = useState<RessourcesQte[]>();
|
||||
const [ouvriers, setOuviers] = useState<RessourcesQte[]>();
|
||||
const [outils, setOutils] = useState<RessourcesQte[]>();
|
||||
|
||||
const [showDateSelect,setSowDateSelect] = useState(false);
|
||||
const [openConfirmation,setOpenConfirmation] = useState(false);
|
||||
@@ -52,7 +52,6 @@ export default function AddChantier() {
|
||||
const [ressourcesSelect, setRessourcesSelect] = useState<string[]>([]);
|
||||
|
||||
async function handleAddChantier() {
|
||||
setLoading(true);
|
||||
setOpenConfirmation(true);
|
||||
}
|
||||
|
||||
@@ -64,15 +63,89 @@ export default function AddChantier() {
|
||||
}
|
||||
};
|
||||
async function onConfirm(): Promise<void> {
|
||||
if (!isValidChantier() || !chefChantier) return;
|
||||
try {
|
||||
setLoading(true);
|
||||
if (!isValidChantier() || !chefChantier){
|
||||
alert("Choisir un chef de Chantier");
|
||||
return;
|
||||
}
|
||||
setOpenConfirmation(false);
|
||||
var latitude=0;
|
||||
var longitude=0;
|
||||
try { //verification de l'adresse
|
||||
const coords = await geocodeAddress(adresse);
|
||||
if (coords) {
|
||||
latitude=coords.latitude;
|
||||
longitude=coords.longitude;
|
||||
}
|
||||
else{
|
||||
console.error("Impossible de géocoder l'adresse");
|
||||
alert("Adresse introuvable. Veuillez vérifier l'adresse.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la création du chantier:", error);
|
||||
alert("Erreur lors de la création du chantier");
|
||||
}
|
||||
|
||||
|
||||
|
||||
var chantier: Chantier = {
|
||||
id:"0",
|
||||
name: objet,
|
||||
adresse: adresse,
|
||||
etat: 'En cours',
|
||||
contact: contact,
|
||||
chef: chefChantier,
|
||||
dateDep: date,
|
||||
tempsEst: Number(duree),
|
||||
anomalies: [],
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
equipe: [],
|
||||
materiel: [],
|
||||
vehicules: []
|
||||
}
|
||||
|
||||
if(machines){
|
||||
machines.forEach(item => {
|
||||
chantier.vehicules.push({
|
||||
id:"0",
|
||||
chantier: chantier,
|
||||
ressource: item[0],
|
||||
quantity: item[1],
|
||||
})
|
||||
});
|
||||
}
|
||||
if(ouvriers){
|
||||
ouvriers.forEach(item => {
|
||||
chantier.equipe.push({
|
||||
id:"0",
|
||||
chantier: chantier,
|
||||
ressource: item[0],
|
||||
quantity: item[1],
|
||||
})
|
||||
});
|
||||
}
|
||||
if(outils){
|
||||
outils.forEach(item => {
|
||||
chantier.materiel.push({
|
||||
id:"0",
|
||||
chantier: chantier,
|
||||
ressource: item[0],
|
||||
quantity: item[1],
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
sendNewChantier(chantier);
|
||||
|
||||
|
||||
|
||||
|
||||
/*try {
|
||||
const coords = await geocodeAddress(adresse);
|
||||
|
||||
if (!coords) {
|
||||
console.error("Impossible de géocoder l'adresse");
|
||||
alert("Adresse introuvable. Veuillez vérifier l'adresse.");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const chantierDate = new Date(date);
|
||||
@@ -85,17 +158,17 @@ export default function AddChantier() {
|
||||
contact,
|
||||
chef: doc(db, "user", chefChantier.id),
|
||||
equipe: [],
|
||||
/*materiel: materiels
|
||||
? [doc(db, "ressources", String(materiels.id))]
|
||||
: [],*/
|
||||
//materiel: materiels
|
||||
// ? [doc(db, "ressources", String(materiels.id))]
|
||||
// : [],
|
||||
vehicules: machines?.map(e =>
|
||||
doc(db, "ressources", String(e[0].id))
|
||||
) || [],
|
||||
anomalies: [],
|
||||
dateDep: chantierDate,
|
||||
tempsEst: parseInt(duree) || 1,
|
||||
latitude: coords.latitude, //TODO
|
||||
longitude: coords.longitude, //TODO
|
||||
latitude: coords.latitude,
|
||||
longitude: coords.longitude,
|
||||
};
|
||||
const id = await addChantier(chantierFirestore as any);
|
||||
if (id) {
|
||||
@@ -113,9 +186,7 @@ export default function AddChantier() {
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la création du chantier:", error);
|
||||
alert("Erreur lors de la création du chantier");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
function onCancel(): void {
|
||||
@@ -231,7 +302,7 @@ export default function AddChantier() {
|
||||
</View>
|
||||
<View style = {styles.inputLine}>
|
||||
<ThemedText style = {styles.inputName}>Outils:</ThemedText>
|
||||
<SelectRessource style={styles.input} sendRessources={setOuviers} ressourceType="Outil"/>
|
||||
<SelectRessource style={styles.input} sendRessources={setOutils} ressourceType="Outil"/>
|
||||
</View>
|
||||
|
||||
|
||||
|
||||
@@ -52,73 +52,74 @@ export default function AddRessource({ressourceType, ...otherProps }: Props) {
|
||||
const [quantiteDisponible,setQuantiteDisponible] = useState('');
|
||||
const [openConfirmation,setOpenConfirmation] = useState(false);
|
||||
|
||||
async function handleAddRessource() {
|
||||
async function handleAddRessource() {
|
||||
setLoading(true);
|
||||
setOpenConfirmation(true);
|
||||
}
|
||||
|
||||
async function onConfirm(): Promise<void> {
|
||||
if(isValidRessource()){
|
||||
try{
|
||||
setLoading(true);
|
||||
const nouvelleRessource : Ressources = {
|
||||
id : '',
|
||||
name: nom,
|
||||
type : ressourceType,
|
||||
quantity : parseInt(quantite),
|
||||
available_quantity : parseInt(quantite),
|
||||
Image : "",
|
||||
allocation : [],
|
||||
};
|
||||
const id = await addRessources(nouvelleRessource);
|
||||
async function onConfirm(): Promise<void> {
|
||||
if(isValidRessource()){
|
||||
try{
|
||||
setLoading(true);
|
||||
const nouvelleRessource : Ressources = {
|
||||
id : '',
|
||||
name: nom,
|
||||
type : ressourceType,
|
||||
quantity : parseInt(quantite),
|
||||
available_quantity : parseInt(quantite),
|
||||
Image : "",
|
||||
allocation : [],
|
||||
};
|
||||
const id = await addRessources(nouvelleRessource);
|
||||
|
||||
if(id){
|
||||
setRessources([...ressources,{...nouvelleRessource, id}]);
|
||||
setOpenConfirmation(false);
|
||||
setNom('');
|
||||
setQuantite('');
|
||||
setQuantiteDisponible('');
|
||||
}
|
||||
}catch(error){
|
||||
}finally{
|
||||
if(id){
|
||||
setRessources([...ressources,{...nouvelleRessource, id}]);
|
||||
setOpenConfirmation(false);
|
||||
setLoading(false);
|
||||
setNom('');
|
||||
setQuantite('');
|
||||
setQuantiteDisponible('');
|
||||
}
|
||||
}catch(error){
|
||||
}finally{
|
||||
setOpenConfirmation(false);
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onCancel(): void {
|
||||
setOpenConfirmation(false);
|
||||
}
|
||||
|
||||
function onCancel(): void {
|
||||
setOpenConfirmation(false);
|
||||
}
|
||||
function isValidRessource():Boolean{
|
||||
return nom!= "" && quantite != ""
|
||||
}
|
||||
|
||||
const renderValidationScreen = () => {
|
||||
return(
|
||||
<Modal transparent={true} >
|
||||
<View style={styles.overlay}>
|
||||
<ThemedView style={styles.overlayView}>
|
||||
<ThemedText style={{fontSize: 25}}>Créer la nouvelle ressource {ressourceType} suivante ? :</ThemedText>
|
||||
<ThemedView lvl={2} style={styles.summaryNewChantier}>
|
||||
<ThemedText style={{fontSize: 20}}>Nom: {nom===''?"NONE":nom}</ThemedText>
|
||||
<ThemedText style={{fontSize: 20}}>Quantité Total: {quantite===''?"0":quantite} </ThemedText>
|
||||
</ThemedView>
|
||||
<View style={styles.overlayView}>
|
||||
<ThemedButton lvl={2} border={5} style={styles.buttonValid} onPress={() => onConfirm()}>
|
||||
<ThemedText style={{fontSize: 25}}>Confirmer</ThemedText>
|
||||
</ThemedButton>
|
||||
</View>
|
||||
<View style={styles.overlayView}>
|
||||
<ThemedButton lvl={2} border={5} style={styles.buttonValid} onPress={() => onCancel()}>
|
||||
<ThemedText style={{fontSize: 25}}>Annuler</ThemedText>
|
||||
</ThemedButton>
|
||||
</View>
|
||||
const renderValidationScreen = () => {
|
||||
return(
|
||||
<Modal transparent={true} >
|
||||
<View style={styles.overlay}>
|
||||
<ThemedView style={styles.overlayView}>
|
||||
<ThemedText style={{fontSize: 25}}>Créer la nouvelle ressource {ressourceType} suivante ? :</ThemedText>
|
||||
<ThemedView lvl={2} style={styles.summaryNewChantier}>
|
||||
<ThemedText style={{fontSize: 20}}>Nom: {nom===''?"NONE":nom}</ThemedText>
|
||||
<ThemedText style={{fontSize: 20}}>Quantité Total: {quantite===''?"0":quantite} </ThemedText>
|
||||
</ThemedView>
|
||||
</View>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
<View style={styles.overlayView}>
|
||||
<ThemedButton lvl={2} border={5} style={styles.buttonValid} onPress={() => onConfirm()}>
|
||||
<ThemedText style={{fontSize: 25}}>Confirmer</ThemedText>
|
||||
</ThemedButton>
|
||||
</View>
|
||||
<View style={styles.overlayView}>
|
||||
<ThemedButton lvl={2} border={5} style={styles.buttonValid} onPress={() => onCancel()}>
|
||||
<ThemedText style={{fontSize: 25}}>Annuler</ThemedText>
|
||||
</ThemedButton>
|
||||
</View>
|
||||
</ThemedView>
|
||||
</View>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const renderInut = (name : string, preFill : string, value : string, setValue : ((text:string) => void),numeric:boolean) => {
|
||||
return (
|
||||
@@ -129,7 +130,7 @@ export default function AddRessource({ressourceType, ...otherProps }: Props) {
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
return (
|
||||
<ThemedView lvl={3} style={styles.back}>
|
||||
<View style={styles.container}>
|
||||
{editMode &&
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { Chantier, Ressources } from '@/class/class';
|
||||
import { ThemedView, } from '@/components/theme/themed-view';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Image, StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
|
||||
import { ThemedText } from '@/components/theme/themed-text';
|
||||
import { ThemedButton } from '@/components/theme/themed-button';
|
||||
import { getNbUseRessources } from '@/class/utils';
|
||||
import { useReservations } from '@/app/ContextReservation';
|
||||
import { getReservations } from '@/services/ressourcesService';
|
||||
|
||||
type RessourcesQte = [Ressources, number];
|
||||
|
||||
@@ -16,11 +19,11 @@ type Props = {
|
||||
|
||||
export default function RessourceSummary({ressource: ressource,qte,style,sendRessource: sendRessource, ...otherProps }: Props) {
|
||||
|
||||
|
||||
const { reservations, setReservations } = useReservations();
|
||||
const [count,setCount] = useState(qte);
|
||||
|
||||
function onPressAdd(ressource: Ressources): void {
|
||||
if(count<ressource.quantity){
|
||||
if(count<ressource.quantity-getNbUseRessources(ressource,reservations)){
|
||||
setCount(count+1);
|
||||
sendRessource([ressource, count+1]);
|
||||
}
|
||||
@@ -33,20 +36,28 @@ export default function RessourceSummary({ressource: ressource,qte,style,sendRes
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
async function loadReservations() {
|
||||
const list = await getReservations();
|
||||
setReservations(list);
|
||||
}
|
||||
|
||||
loadReservations();
|
||||
}, []);
|
||||
|
||||
|
||||
return(
|
||||
<View style={style}>
|
||||
<ThemedView lvl={2} border={3} style={{padding:10,width:"100%",borderRadius:10,flexDirection: 'row',justifyContent: 'space-between',}}>
|
||||
<View>
|
||||
<ThemedText>{ressource.id}</ThemedText>
|
||||
<ThemedText>{ressource.name}</ThemedText>
|
||||
<ThemedText>{ressource.quantity}</ThemedText>
|
||||
<ThemedText>{ressource.type}</ThemedText>
|
||||
<ThemedText>Nom : {ressource.name}</ThemedText>
|
||||
<ThemedText>Restant : {ressource.quantity-getNbUseRessources(ressource,reservations)}/{ressource.quantity}</ThemedText>
|
||||
</View>
|
||||
<View style={{alignItems:"center"}}>
|
||||
<ThemedButton style={styles.button} lvl={3} onPress={() => onPressAdd(ressource)}>
|
||||
<ThemedText>+</ThemedText>
|
||||
</ThemedButton>
|
||||
<ThemedText>{count}/{ressource.quantity}</ThemedText>
|
||||
<ThemedText>{count}/{ressource.quantity-getNbUseRessources(ressource,reservations)}</ThemedText>
|
||||
<ThemedButton style={styles.button} lvl={3} onPress={() => onPressSub(ressource)}>
|
||||
<ThemedText>-</ThemedText>
|
||||
</ThemedButton>
|
||||
|
||||
@@ -53,6 +53,7 @@ export default function SelectChafChantier({style,sendChefChantier , ...otherPro
|
||||
return(
|
||||
<View style={{padding:10,width:"100%"}}>
|
||||
<ThemedButton lvl={2} style={{padding:10,width:"100%",borderRadius:10}} onPress={() => {onPressUser(item)}}>
|
||||
<ThemedText>{item.id}</ThemedText>
|
||||
<ThemedText>{item.name}</ThemedText>
|
||||
<ThemedText>{item.last_name}</ThemedText>
|
||||
<ThemedText>{item.role}</ThemedText>
|
||||
|
||||
@@ -67,10 +67,10 @@ export default function Anomaly({data,style}: Props) {
|
||||
<View style={styles.addContainer}>
|
||||
<TextInput style={styles.input} placeholder="Nouvelle anomalie..." value={newAnomaly} onChangeText={setNewAnomaly} />
|
||||
<TouchableOpacity style={styles.addButton} onPress={handleAdd}>
|
||||
<Text style={styles.addButtonText}>Ajouter</Text>
|
||||
<ThemedText style={styles.addButtonText}>Ajouter</ThemedText>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity onPress={selectImage} style={styles.addButton}>
|
||||
<Text style={styles.addButton}>Choisir une image</Text>
|
||||
<ThemedText style={styles.addButton}>Choisir une image</ThemedText>
|
||||
</TouchableOpacity>
|
||||
{imageUri && (
|
||||
<Image source={{ uri: imageUri }} style={styles.image} />
|
||||
@@ -151,13 +151,11 @@ const styles = StyleSheet.create({
|
||||
marginRight: 8,
|
||||
},
|
||||
addButton: {
|
||||
color: "white",
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
borderRadius: 8,
|
||||
},
|
||||
addButtonText: {
|
||||
color: "white",
|
||||
fontWeight: "bold",
|
||||
},
|
||||
image: { width: 200, height: 200, borderRadius: 10 }
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ThemedView, } from '@/components/theme/themed-view';
|
||||
import React from 'react';
|
||||
import { Image, StyleProp, StyleSheet, View, ViewStyle } from 'react-native';
|
||||
import { ThemedText } from './theme/themed-text';
|
||||
import { getNbItemReservation } from '@/class/utils';
|
||||
|
||||
type Props = {
|
||||
data: {
|
||||
@@ -20,9 +21,20 @@ export default function ChantierSummary({data,style , ...otherProps }: Props) {
|
||||
<Image source={{ uri:"https://cdn.discordapp.com/attachments/1425108443571945644/1427207643180826757/raw.png?ex=693f1a72&is=693dc8f2&hm=86ffb97145fc8d3aec822b87d99be233c98477d4424c1ef58f80eb81b17c7c80&" /*chantier.urlImg*/ }} style={styles.image} />
|
||||
</View>
|
||||
<View style={{flex: 1}}>
|
||||
<ThemedText>Adresse: {data.chantier.adresse}</ThemedText>
|
||||
<ThemedText>Chef de chantier: {data.chantier.chef.last_name}{" "}{data.chantier.chef.name}</ThemedText>
|
||||
<ThemedText>État: {data.chantier.etat}</ThemedText>
|
||||
<ThemedText selectable={true}>Id: {data.chantier.id}</ThemedText>
|
||||
<ThemedText selectable={true}>Objet: {data.chantier.name}</ThemedText>
|
||||
<ThemedText selectable={true}>Adresse: {data.chantier.adresse}</ThemedText>
|
||||
<ThemedText selectable={true}>Chef de chantier: {data.chantier.chef.last_name}{" "}{data.chantier.chef.name}</ThemedText>
|
||||
<ThemedText selectable={true}>État: {data.chantier.etat}</ThemedText>
|
||||
<ThemedText selectable={true}>equipe:
|
||||
{getNbItemReservation(data.chantier.equipe)} ({data.chantier.equipe.length} type{data.chantier.equipe.length>1&&"s"})
|
||||
</ThemedText>
|
||||
<ThemedText selectable={true}>materiel:
|
||||
{getNbItemReservation(data.chantier.materiel)} ({data.chantier.materiel.length} type{data.chantier.materiel.length>1&&"s"})
|
||||
</ThemedText>
|
||||
<ThemedText selectable={true}>vehicules:
|
||||
{getNbItemReservation(data.chantier.vehicules)} ({data.chantier.vehicules.length} type{data.chantier.vehicules.length>1&&"s"})
|
||||
</ThemedText>
|
||||
</View>
|
||||
</ThemedView>
|
||||
) :
|
||||
@@ -42,7 +54,7 @@ const styles = StyleSheet.create({
|
||||
borderRadius: 10,
|
||||
//borderWidth: 1,
|
||||
flexDirection: 'row',
|
||||
height: 150,
|
||||
//height: 150,
|
||||
gap: 10,
|
||||
},
|
||||
image:{
|
||||
|
||||
@@ -65,20 +65,20 @@ export default function SelectChantier() {
|
||||
open.value = withTiming(isOpen ? 1 : 0);
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
/*useEffect(() => {
|
||||
async function loadChantiers() {
|
||||
const list = await getChantiers();
|
||||
setChantiers(list);
|
||||
}
|
||||
|
||||
loadChantiers();
|
||||
}, []);
|
||||
}, []);*/
|
||||
|
||||
const filteredChantiers = chantiers.filter((chantier) => {
|
||||
var keyWords:string[] = search.toLowerCase().split(" ") ;
|
||||
var containsAllKeyWord:boolean = true;
|
||||
keyWords.forEach(keyWord => {
|
||||
containsAllKeyWord = containsAllKeyWord && (chantier.adresse.toLowerCase().includes(keyWord))
|
||||
containsAllKeyWord = containsAllKeyWord && (chantier.adresse.toLowerCase().includes(keyWord) || chantier.name.toLowerCase().includes(keyWord))
|
||||
});
|
||||
return containsAllKeyWord
|
||||
});
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
[
|
||||
{
|
||||
"group": "Bernard DupYEEd",
|
||||
"date":"Rennes",
|
||||
"nationality": "French",
|
||||
"location": "PlombYEEr",
|
||||
"price": 20,
|
||||
"ticketsLeft": 36,
|
||||
"Image": "https://media.discordapp.net/attachments/1415267028201246812/1424825038657425518/a06e3304-86ca-4b4f-8016-c4ae9844b0df.png?ex=68e9f879&is=68e8a6f9&hm=b6ff1f540d5c382930b56bd6f90565f517ee179347d6ee6aebd5254b10cf4c88&=&format=webp&quality=lossless&width=579&height=579",
|
||||
"favorite": false
|
||||
},
|
||||
{
|
||||
"group": "MYEEchel Câble",
|
||||
"date":"Nantes",
|
||||
"nationality": "French",
|
||||
"location": "ElectrYEEcien",
|
||||
"price": 22,
|
||||
"ticketsLeft": 400,
|
||||
"Image": "https://media.discordapp.net/attachments/1415267028201246812/1424826240090509332/7fdbfe06-8300-441e-81ac-87851d004dc3.png?ex=68e9f997&is=68e8a817&hm=cc71621c3e7c3c1aaeda5555e9dd4204d43414cd0332c2b116b10d009b68df3c&=&format=webp&quality=lossless&width=579&height=579",
|
||||
"favorite": false
|
||||
|
||||
},
|
||||
{
|
||||
"group": "PYEErre soulever",
|
||||
"date":"Redon",
|
||||
"nationality": "French",
|
||||
"location": "GrutYEEr",
|
||||
"price": 32,
|
||||
"ticketsLeft": 0,
|
||||
"Image": "https://media.discordapp.net/attachments/1425108443571945644/1427207643180826757/raw.png?ex=68ee0632&is=68ecb4b2&hm=1efc51065c6abfb1af75b8382f9924c2eb177c7d7672f7ed9837e96ef3076d16&=&format=webp&quality=lossless&width=233&height=350",
|
||||
"favorite": false
|
||||
|
||||
},
|
||||
{
|
||||
"group": "Greg NegatYEEf",
|
||||
"date":"Pacé",
|
||||
"nationality": "French",
|
||||
"location": "ElectrYEEcien",
|
||||
"price": 20,
|
||||
"ticketsLeft": 36,
|
||||
"Image": "https://media.discordapp.net/attachments/1415267028201246812/1424826240090509332/7fdbfe06-8300-441e-81ac-87851d004dc3.png?ex=68e9f997&is=68e8a817&hm=cc71621c3e7c3c1aaeda5555e9dd4204d43414cd0332c2b116b10d009b68df3c&=&format=webp&quality=lossless&width=579&height=579",
|
||||
"favorite": true
|
||||
|
||||
}
|
||||
]
|
||||
@@ -1,4 +1,4 @@
|
||||
import { addDoc, arrayUnion, collection, doc, getDoc, getDocs, Timestamp, updateDoc } from "firebase/firestore";
|
||||
import { addDoc, arrayUnion, collection, doc, Firestore, getDoc, getDocs, Timestamp, updateDoc, DocumentReference, query, where } from "firebase/firestore";
|
||||
import { Chantier, Reservation, Ressources, User } from "../class/class";
|
||||
import { db } from "../firebase_config";
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function getUsers(): Promise<User[]> {
|
||||
return {
|
||||
id: doc.id,
|
||||
...data,
|
||||
allocation: data.allocation?.map(convertReservation) || [],
|
||||
//allocation: data.allocation?.map(convertReservation) || [],
|
||||
} as User;
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -28,8 +28,9 @@ export async function getRessources(): Promise<Ressources[]> {
|
||||
return snapshot.docs.map((doc) => {
|
||||
const data = doc.data();
|
||||
return {
|
||||
id: doc.id,
|
||||
...data,
|
||||
allocation: data.allocation?.map(convertReservation) || [],
|
||||
//allocation: data.allocation?.map(convertReservation) || [],
|
||||
} as Ressources;
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -52,41 +53,100 @@ export async function addRessources(ressourceData: Omit<Ressources, 'id'>): Prom
|
||||
}
|
||||
///////////////////////////////////CHANTIER/////////////////////////////////
|
||||
export async function getChantiers(): Promise<Chantier[]> {
|
||||
const snap = await getDocs(collection(db, "chantier"));
|
||||
const chantiers: Chantier[] = [];
|
||||
try {
|
||||
const snap = await getDocs(collection(db, "chantier"));
|
||||
|
||||
for (const docSnap of snap.docs) {
|
||||
const data = docSnap.data();
|
||||
//Faut convertir les Timestamp en Date ( merci à firebase :) )
|
||||
const dateDep = data.dateDep instanceof Timestamp ? data.dateDep.toDate() : new Date(data.dateDep);
|
||||
let chef: User | null = null;
|
||||
if (data.chef) {
|
||||
const chefSnap = await getDoc(data.chef);
|
||||
if (chefSnap.exists()) {
|
||||
chef = chefSnap.data() as User;
|
||||
}
|
||||
}
|
||||
let equipe: User[] = [];
|
||||
if (Array.isArray(data.equipe)) {
|
||||
equipe = await Promise.all(
|
||||
data.equipe.map(async (ref: any) => {
|
||||
const snap = await getDoc(ref);
|
||||
return snap.exists() ? (snap.data() as User) : null;
|
||||
})
|
||||
).then(list => list.filter(x => x !== null)) as User[];
|
||||
}
|
||||
|
||||
|
||||
chantiers.push({
|
||||
...data,
|
||||
id: docSnap.id,
|
||||
dateDep,
|
||||
chef,
|
||||
equipe
|
||||
} as Chantier);
|
||||
for (const docSnap of snap.docs) {
|
||||
try {
|
||||
const data = docSnap.data();
|
||||
//Faut convertir les Timestamp en Date ( merci à firebase :) )
|
||||
const dateDep = data.dateDep instanceof Timestamp ? data.dateDep.toDate() : new Date(data.dateDep);
|
||||
let chef: User | null = null;
|
||||
if (data.chef) {
|
||||
const chefSnap = await getDoc(data.chef);
|
||||
if (chefSnap.exists()) {
|
||||
chef = chefSnap.data() as User;
|
||||
}
|
||||
}
|
||||
const equipe:Reservation[] = [];
|
||||
const vehicules:Reservation[] = [];
|
||||
const materiel:Reservation[] = [];
|
||||
const all:Reservation[] = await getReservationsByChantier(docSnap.id);
|
||||
|
||||
all.forEach(element => {
|
||||
if(element.ressource.type==="Ouvrier"){
|
||||
equipe.push(element)
|
||||
}
|
||||
else if(element.ressource.type==="Machine"){
|
||||
vehicules.push(element)
|
||||
}
|
||||
else if(element.ressource.type==="Outil"){
|
||||
materiel.push(element)
|
||||
}
|
||||
});
|
||||
|
||||
chantiers.push({
|
||||
...data,
|
||||
id: docSnap.id,
|
||||
dateDep,
|
||||
chef,
|
||||
equipe,
|
||||
vehicules,
|
||||
materiel,
|
||||
} as Chantier);
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la lecture d'un chantiers : " + error);
|
||||
//alert("Erreur lors de la lecture d'un chantiers : " + error);
|
||||
}
|
||||
}
|
||||
return chantiers;
|
||||
} catch (error) {
|
||||
alert("Erreur lors de la lecture des chantiers : " + error);
|
||||
}
|
||||
return chantiers
|
||||
}
|
||||
|
||||
//récupère les reservations d'un chantier
|
||||
export async function getReservationsByChantier(chantierId: string): Promise<Reservation[]> {
|
||||
const q = query(
|
||||
collection(db, "Reservation"),
|
||||
where("chantier", "==", doc(db, "chantier", chantierId)),
|
||||
);
|
||||
|
||||
const snap = await getDocs(q);
|
||||
|
||||
const results = await Promise.all(
|
||||
snap.docs.map(convertReservation)
|
||||
);
|
||||
|
||||
return results.filter(
|
||||
(r): r is Reservation => r !== null
|
||||
);
|
||||
}
|
||||
|
||||
///////////////////////////////////RESERVATION/////////////////////////////////
|
||||
export async function getReservations(): Promise<Reservation[]> {
|
||||
try {
|
||||
const snap = await getDocs(collection(db, "Reservation"));
|
||||
|
||||
const results = await Promise.all(
|
||||
snap.docs.map(convertReservation)
|
||||
);
|
||||
|
||||
return results.filter(
|
||||
(r): r is Reservation => r !== null
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la lecture des Reservations : " + error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//CHANGE CHANTIER STATUS
|
||||
export async function changeChantierStatus(chantierId: string, newStatus: string): Promise<void> {
|
||||
try {
|
||||
@@ -144,12 +204,63 @@ export async function deleteAnomalie(chantierId: string, anomalie_String: string
|
||||
}
|
||||
}
|
||||
|
||||
function convertReservation(res: any): Reservation {
|
||||
return {
|
||||
id: res.id,
|
||||
dateChantier:
|
||||
res.dateChantier instanceof Timestamp ? res.dateChantier.toDate() : new Date(res.dateChantier),
|
||||
dateFin:
|
||||
res.dateFin instanceof Timestamp ? res.dateFin.toDate() : new Date(res.dateFin),
|
||||
};
|
||||
}
|
||||
type ReservationFirestore = {
|
||||
chantier: DocumentReference;
|
||||
ressource: DocumentReference;
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
async function convertReservation(res: any): Promise<Reservation|null> {
|
||||
try {
|
||||
const data = res.data() as ReservationFirestore;
|
||||
|
||||
const chantierSnap = await getDoc(data.chantier as DocumentReference);
|
||||
const ressourceSnap = await getDoc(data.ressource as DocumentReference);
|
||||
|
||||
return {
|
||||
id: res.id,
|
||||
chantier: chantierSnap.data() as Chantier,
|
||||
ressource: ressourceSnap.data() as Ressources,
|
||||
quantity: data.quantity,
|
||||
};
|
||||
} catch (err) {
|
||||
console.warn("Reservation ignorée :", res.id , err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//ENVOYER CHANTIER
|
||||
export async function sendNewChantier(chantier:Chantier): Promise<void> {
|
||||
|
||||
const chantierRef = await addDoc(collection(db, "chantier"), {
|
||||
name:chantier.name,
|
||||
adresse:chantier.adresse,
|
||||
etat:chantier.etat,
|
||||
contact:chantier.contact,
|
||||
chef: doc(db, "user", chantier.chef.id), //un objet déjà dans la base de donné
|
||||
date: Timestamp.fromDate(chantier.dateDep),
|
||||
tempsEst: chantier.tempsEst,
|
||||
anomalies: chantier.anomalies ?? [], //strings[]
|
||||
latitude: chantier.latitude,
|
||||
longitude: chantier.longitude,
|
||||
})
|
||||
await Promise.all([
|
||||
sendNewReservation(chantier.equipe, chantierRef.id),
|
||||
sendNewReservation(chantier.materiel, chantierRef.id),
|
||||
sendNewReservation(chantier.vehicules, chantierRef.id),
|
||||
]);
|
||||
}
|
||||
|
||||
export async function sendNewReservation(list: Reservation[],chantierId:string): Promise<void> {
|
||||
const promises = list.map((reservation) =>
|
||||
addDoc(collection(db,"Reservation"),{
|
||||
chantier: doc(db, "chantier", chantierId),
|
||||
ressource: doc(db, "ressources", reservation.ressource.id),
|
||||
quantity: reservation.quantity,
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user