Micro Application 6 : Mon Petit Stock avec Angular.js

Introduction monpetitstock


Aujourd’hui, je vais créer à partir de zéro une application de gestion de stock avec angular.js qui s’appelle « Mon petit Stock ». Pour le testing, c’est ici. Je pense travailler dessus une ou deux semaines mi-temps. Je ne publie qu’une partie du code. Caractéristiques à début du codage+6heures :

  • Lorsque le stock est < à 5 : la ligne est colorée en orange, lorsque le stock est < à 0 : la ligne devient rouge.
  • Lors du tri dans le stock(En cliquant sur les titres des colonnes.), le programme continue à fonctionner, à l’aide des IDS.

Synopsis


Un utilisateur privé, ou de petite entreprise requiert une application monoposte pour gérer ses stocks et les revendre.

Description


  • Type : Application front-end Web de type Saas.
  • Langage : Js, Angular, Jquery U.I, Html 5.0, Css3.0
  • Back-end : Java et/ou Php.
  • Difficulté : +++/+++++
  • Test avec jasmine : Non.
  • Durée de codage : 1 semaine.
  • UML 2.0 : non.
  • Webstorage(stockage des données dans le navigateur) :oui.
  • Gestion de la concurence : non.
  • Webservice : non.
  • Authentification : non.

Journal (Dev en mode agile)


Jour 1 : 6 H de code

  • Css Basiques, code de base.
  • Problèmes rencontrés :  des interférences index-ID dans le code(cela ne se reproduira plus.)Explication : Angular considère $index comme le numéro dans le tableau HTML, seulement, avec la fonctionnalité intégrée de tri, on doit élaborer tout le code avec des ID, comme en sql, sans cela, lorsque l’on trie, Angular remets à jour les $index, et cela fait foirer tout le code !)

Jour 2: 2 H de code

  • Refonte des css
  • Ajout de la possibilité d’ajouter un article dans le stock.
  • Premières statistiques de base.

Jour 3 : 2 h de code

  • Ajout de l’heure dans les transactions, en vue de créer des statistiques et probabilités
  • Autres projets : Lorsque la commande est validée, créer un pdf de cette commande avec en-tete professionnelle. + Créer une couche de persistance en Java et en Php + Gestion de l’upload des photos. + résoudre le pb de Math.round() qui ne fonctionne pas.+ Création d’un bouton »Supprimer la commande »

Jour 4 : 4h30 de code : Gros travail aujourd’hui !

  • Activation d’un web storage pour enregistrement des données du stock directement dans le navigateur, bien sur, c’est seulement pour la démo, en attendant de se pencher sur la persistance en BDD et surtout la gestion de la concurrence ! Car on peut aussi envisager le web service.
  • Programmation d’une liste des commandes, enregistrée avec webstorage (A améliorer, surtout l’idListe qui « bugguotte ».) Non recetté, parfois il faut cliquer pour supprimer … a revoir demain.
  • Programmation de la validation d’une commande.
  • L’impression d’une commande n’est pas encore active(A faire …)
  • NOTES


    • Le Web storage HTML5.0 est sacrément impressionnant et est activé avec html 5.0 sur la plupart des navigateurs . Pour enregistrer un fichier json dans la mémoire du navigateur ça prends 2 lignes :
    
    // web storage
    var u = JSON.stringify($scope.stock);
    localStorage.setItem(&amp;amp;quot;stock&amp;amp;quot;,u);
    
    

    et voilà, mon objet $scope.stock qui comprends tout mon stock est enregistré dans la mémoire du navigateur ! Idéal pour tester des applis sans faire de couche de persistance !

Jour 5 : 4h de code :


  • Création de la fenêtre de paramétrage qui permet d’ajouter les clients, les fournisseurs et les catégories d’articles.(Pas encore opérationnelle)
  • Utilisation d’un select avec Angular :

1. Déclarer les valeurs du select :

$scope.categories = [ { label: ‘Pinceaux’, image: ‘pot.jpg’, value: 1 }, { label: ‘Peinture’, image: ‘pot.jpg’, value: 2 }, { label: ‘Outils ‘, image: ‘pot.jpg’,value: 3 }, { label: ‘Colle’, image: ‘pot.jpg’,value: 4 } ];

2. Ecrire le select dans la vue :

3.Récupérer la valeur choisie:

$scope.correctlySelected = $scope.categories[1];

var valeurChoisie = $scope.correctlySelected.label ;

  • Utilisation du plug in angular-filter qui étends les possibilités de filtrage d’Angular un peu à la manière d’SQL, on peut ainsi regrouper des objets JS selon un de leur critère. Je l’utilise désormais pour afficher les commandes en fonction de leur ID. Voir un exemple ici .
  • Le problème des Ids des commandes n’en est pas un.  Explication : Lors de la création d’une commande, un algorithme doit chercher dans le tableau JS de la liste des commandes l’idCommande de valeur max, puis l’incrémenter de un pour l’affecter à la nouvelle commande. Vu que je n’avais pas cet algorithme au jour 4, en attendant, j’ai écris un bête truc du genre : idCommande = $scope.listeCommandes.length+1. Evidemment que c’est faux, mais cela permet de simuler un fonctionnement, jusqu’à ce que je programme le véritable algorithme d’incrémentation qui va ressembler à cela, demain :
var highest = Math.max.apply(this,$.map($.parseJSON(str), function(o){ return o.y; }));
  •  Le pb de Maths.round est à résoudre avec un code du style total = total.toFixed(2)
  • A faire : rendre le prog angular compatible avec ie7 avec les méthodes du net.

Jour 6 : 3h de code :


  • Ajout de gros icônes dans la barre des tâches supérieures.
  • Suppression d’une boucle for JS  inutile qui provoquait une erreur dans firebug.
  • Aux premiers abords ..J’ai résolu le pb de l’incrémentation des numéros ID des commandes de cette façon:

function getIdListe(){

		var arr = $scope.listeCommandes;
		console.log(arr);

&lt;!-- 		 Si la liste de commandes est vide, alors l'id de la commande est de 1 --!&gt;;
		if (arr.length == 0 ){
			idListe=1;
			return idListe;
		}

		 &amp;lt;!-- Si la liste des commandes X est remplie, alors l'id de la commande est de X+1 --&amp;gt;
		if (arr.length != 0){
			var z = 0;
			for (z = 0;z &lt; arr.length; z++){
				var idListe = arr[z].idliste;
			}
			var idListe = idListe+1;
			return idListe;
		}
	};

Explication Du code précédent :

Si la liste des commandes est vide, alors l’id de la commande sera de 1.(je sais le nom de la variable idListe porte à confusion… Je dois toute la renommer par idCommande) Si la liste des commandes est >0, alors l’id de la commande sera du dernier idliste+1.

  • Ajout de la marque d’un article et d’autres photos.

Jour 7 : 3h de code :


  • Choix du client dans la Commande
  • Ajouter un Fournisseur et Ajouter une catégorie activés dans « paramétrer »
  • Ajout d’un premier graphique qui se modifie en temps réel, simples énumérations de Stocks
  • Ajout de la possibilité de supprimer un stock.
  • Ajout de la possibilité d’augmenter tous les prix d’un certain pourcentage.( Pbs d’arrondis à gérer sur les champs input, j’ai essayé plein de solutions, rien n’a fonctionné).
  • Ajout du pourcentage de bénéfices par articles.
  • Re-design de la liste des commandes, avec une date.

Notes :

Il est plus compliqué de générer des statistiques avec JS plutôt qu’avec SQL. Cependant, le résultat temps réel est excellent. Au sujet de la prise en compte de stocks négatifs, cela va compliquer les nouvelles entrées, et il faudra prendre en compte la perte d’argent, si le réassort du même article est réalisé à un tarif plus cher(ou contraire). Ce qui va aussi me poser pb, c’est qu’avec sql, pour chercher un fournisseur, c’est très simple : on fait un simple select * from fournisseur where fournisseur= »nomfournisseur », mais le faire avec JS en temps réel est une autre paire de manches. Certes il y a des librairies… Mais bon c’est un peu plus difficile, et du coup ça mets un peu plus de temps. Je pense faire éventuellement une pause sur cette application, pour m’entrainer à d’autres tricks et à JAVAEE. Mais je vais d’abord mettre pleins de graphiques; dont des graphiques élaborés avec D3.js dans cette application, ainsi que des probabilités. Les maths que j’utilise sont ceux de 1ère S et Term S et ES tranposés à Javascript.

Jour 8 : 1h30 de code :


  • Activation de « Ajouter un nouveau Client' »
  • Nouvelle refonte des Css avec un style light.
  • Ajout de la date d’acquisition au format classique dans le stock.
  • Ajout du prix total d’une commande dans la liste des commandes, à l’aide de l’ajout d’un filtre Angular trouvé ici .
  • Etude de la réaction des graphiques pour le stock… (Ils doivent s’actualiser en temps réel..)
  • Demain, je mets du D3.js ou du highcharts et de l’autocomplétion Google.
  • Penser à ajouter une date de péremption, pour de nouvelles stats !

Jour 9 : 2h00 de code :


  • Création d’une alerte Réassort : à terme, il va être possible d’envoyer un email automatique en cas de pénurie . (d' »out of stock »)(Ce sera donc un Workflow). (Durée environ 1h30)
  • Création d’un graphique de la valeur des stocks disponibles( Durée 8 minutes).

Hier, j’ai relu mon petit livre datant de 1961 « La recherche opérationnelle », de Faure.

Du coup, j’ai réalisé qu’il existait des possiblités d’optimiser à l’aide de la recherche opérationnelle le réassort, et qu’il est possible de gagner de l’argent à ce moment là. Par contre, pour l’instant, je suis assez loin de pouvoir implémenter de telles optimisations dans le code, mais cela sera néanmoins possible, je pense.

A faire : Je vais créer une pagination sur les tableaux, cela sera bien plus esthétique. De plus, il faut aussi ajouter des filtres par catégorie. Je vais m’aider pour cela d’un plug in Angular de style ng-table.

Jour 10: 2h30 de code :


  • Intégration d’une pagination BOOTSTRAP sur le tableau des stocks… C’est pas simple du tout, à cause de $scope. Je l’ai fait à l’aide de cet exemple :http://plnkr.co/edit/Hi7nvd?p=preview
  • Ce que je redoutais est arrivé, le binding ralentit le tout. il faut donc ajouter des boutons ‘clic’ pour exécuter le binding sur action utilisateur, et pas sur « onchange ». On en déduit qu’il faut utiliser le binding judicieusement dans le tableau des stocks… Je suis un peu déçu car c’était super bien en temps réel…
  • Les filtres ne sont pas encore opérationnels, hormis le champs recherche, qui ne recherche que sur la page en cours et pas dans toutes les données du stock.. Ce n’est pas du SQL mais du Angular(Bien qu’on pourrait le faire aussi).
  • Je viens de constater avec horreur que cette méthode de pagination induit un temps de latence qui m’a obligé à désactiver provisoirement le bouton suppression d’un article.

Jour 11 : 1h30 de code


  • Ajout d’un filtre  par catégorie, comme dans ce plunker : http://plnkr.co/edit/60GO0zhSGoEUm7tu6mbc?p=preview
  • Suppression de la pagination qui en fait ne sert pas.

Jour 12


Après une pause de 2 mois, je reviens sur le projet, ce 15 juin 2015:

A intégrer :
* Calculs et graphiques :

– Modifier le graphe « Valeur des stocks disponibles » pour ajouter le sigle €.
– Nouvel onglet avec Nouveaux graphes de taux de vente par objet ou par catégorie
– Nouvel onglet avec nombre de commandes par dates
– Nouvel onglet statistique d’appréciation consommateurs
– Réussir à arrondir les prix après augmententation globale des prix de vente.
– Ativer le réassort, et pondérer le nouveau prix d’achat à l’aide d’un calcul.
– Indicateur Qualité : Nb de ruptures de sotck par mois
– NOmbre d’article par catégories
– Dépréciation d’un stock.

* Look an feel :

– Trouver un moyen de diminuer la latence sur l’augementation global des prix de vente, + un curseur rond personnalisé
– Dans la fenêtre paramêtrer : Lister les élèments + opportunité de supprimer et modifier.
– Pouvoir rechercher une liste des commandes.
* Sauvegarde :
– Sauvegarder les paramêtres en web storage
– Organiser une sauvegarde sur MongoDb

* Général :
– Activer l’envoi d’emails après « clic sur un réassort »
– Activer la création d’une commande PDF + envoi par email de confirmation.

Jour 13 : 2h de Code


  • Activation de l’Upload de Photos sur le serveur en Angular/Jquery/Php
  • Codage de la partie de contrôle des champs de la fenêtre « Ajouter un Stock »

 Voici ma fonction générique de test :

&lt;!-- TESTE SI UNE ENTREE NUMERIQUE EST CORRECTE DANS UN FORMULAIRE --&gt;
	function testChampsNombre(nb){
		if (nb!=0&nb!=null){
			return true
		}
		else{
			return false
		};

	};

J’y fais appel, plus tard dans cette fonction :

$scope.ajoutStock = function() {

		if (testChampsNombre($scope.nouvelarticle[0].quantite)&testChampsNombre($scope.nouvelarticle[0].commandes)&testChampsNombre($scope.nouvelarticle[0].commandes)&testChampsNombre($scope.nouvelarticle[0].achat)&testChampsNombre($scope.nouvelarticle[0].prix)){
		$scope.stock.push({
			id:			$scope.nouvelarticle[0].id,
			photo:		$scope.nouvelarticle[0].photo,
			nom:		$scope.nouvelarticle[0].nom	,
			categorie:	$scope.correctlySelected.label	,
			fournisseur: $scope.correctlySelectedFournisseurs.label	,
			marque:		'Dev en cours',
			quantite:	$scope.nouvelarticle[0].quantite	, 
			commandes:	$scope.nouvelarticle[0].commandes	, 
			prix:		$scope.nouvelarticle[0].prix,
			achat:		$scope.nouvelarticle[0].achat,
			heureajout:		$scope.getHeure(),
			dateAjout: $scope.afficheDateCourte()
		});
		$scope.nouvelarticle[0].id = $scope.stock.length +1; 
		$scope.infos = " Un nouvel article appelé "+$scope.nouvelarticle[0].nom+" a été ajouté au stock à "+$scope.getHeure()+" ";
		}

		else{
			$scope.infos = " Renseignez tous les Champs, les 0 ne sont pas admis.";
		};

	};

Jour 14 : Problèmes sur l’hébergeur one.com


Cet hébergeur a un cache par défaut, cela empêche d’uploader correctement l’image. J’essaye de les contacter mais cela ne fonctionne pas, même avec un .htaccess, avec écrit dedans:

Header add « disablevcache » « true »
Header add X-Varnish-Control « disabled »

Photos:


stockcccc

Le code :


Voici mon code au jour 1, après 6h :

&lt;html ng-app="magasin"&gt;
    &lt;head&gt;
        &lt;meta charset="iso-8859-15" &gt;
        &lt;!-- -----------------------------------------------DEBUT DU CHARGEMENT DES LIBRAIRIES------------------------ --&gt;
        &lt;!--    APPEL LIB ANGULAR --&gt;
        &lt;script    type="text/javascript"     src="angular-1.3.13/angular.min.js"&gt;&lt;/script&gt;
        &lt;!--    APPEL LIB JQUERY --&gt;
        &lt;script    type="text/javascript"     src="librairies/jquery.js"&gt;&lt;/script&gt;
        
        
        &lt;!-- ----------------------------------------------FIN DU CHARGEMENT DES LIBRAIRIES------------------------ -----&gt;
  &lt;script&gt;
  
  
 var monAppli = angular.module('magasin',[]);
 
            
monAppli.controller("Controleur", function ($scope) {
    
// INITIALISATION DES VARIABLES
    $scope.predicate = '';
    $scope.panier = [];
    
    // Panier de démonstration, le panier réel sera chargé à partir du back-end.
    $scope.stock = [
        {id:1,photo:'pot.jpg',nom: 'Pots Gris'            ,categorie:'peinture', quantite: 8    ,commandes: 0,prix: 3.95},
        {id:2,photo:'pot.jpg',nom: 'Pot Vert'            ,categorie:'peinture', quantite: 17    ,commandes: 0, prix: 12.95},
        {id:3,photo:'pot.jpg',nom: 'Pot Rouge'            ,categorie:'peinture', quantite: 5    ,commandes: 0, prix: 6.95},
        {id:4,photo:'pot.jpg',nom: 'Pot Bleu'            ,categorie:'peinture', quantite: 25    ,commandes: 0, prix: 6.95},
        {id:5,photo:'pot.jpg',nom: 'Pinceaux'            , categorie:'outils',  quantite: 1    ,commandes: 0, prix: 6.95},
      ];

// DEBUT DU PROGRAMME

    // AJOUTER UN ARTICLE DANS LE PANIER
    $scope.ajouter = function(index,id) {//Id=l'id de l'article cliqué dans 
       
    // CONTROLE DU STOCK
            
        if ($scope.stock[index].quantite-$scope.stock[index].commandes&lt;1){
            &lt;!-- $scope.stock.splice(index, 1); --&gt;
        }
            
    // AJOUT DANS LE PANIER    
            
        // SI PANIER VIDE, ALORS AJOUT SIMPLE DE LARTICLE 
        if ($scope.panier.length==0){
            $scope.panier.push({
                id:$scope.stock[index].id,
                photo:$scope.stock[index].photo,
                nom:$scope.stock[index].nom    ,
                quantite:1    , 
                prix:$scope.stock[index].prix});
        }    
        
        // SINON CONTROLE DE DOUBLONS PUIS AJOUT DE LARTICLE
        else {
            if (testDoublons(id)!=true){
                //Ajout de l'article en fonction de son id
                for (i=0;i&lt;$scope.stock.length;i++){
                    if (id == $scope.stock[i].id ){
                        $scope.panier.push({
                        id:$scope.stock[i].id,
                        photo:$scope.stock[i].photo,
                        nom:$scope.stock[i].nom    ,
                        quantite:1, 
                        prix:$scope.stock[i].prix});
                    };
                }            
            }
        }
                
        // FONCTION DE CONTROLE DE DOUBLON DANS LE PANIER, SI PRESENCE DUN DOUBLON, INCREMENTER LOBJET PRESENT DANS LE PANIER DE 1, SINON NE RIEN FAIRE DAUTRE                
        function testDoublons(id){
            var i=0 ;
            for (i=0;i&lt;$scope.panier.length;i++){
                if (id == $scope.panier[i].id ){
                    $scope.panier[i].quantite += 1 ;
                    return true;
                }            
            }
        };            

        // Dans tous les cas, on ajoute 1 à la quantité en stock, après click sur Ajouter au panier
        var i=0 ;
        for (i=0;i&lt;$scope.stock.length;i++){
            if (id == $scope.panier[i].id ){
            $scope.stock[id-1].commandes += 1 ; 
                
            }            
        };
    }  
      
    // SUPPRIMER UN ARTICLE DANS LE PANIER  
    $scope.supprimer = function(index) {
        $scope.panier.splice(index, 1);
    }    
    
    // AFFICHE LE PRIX TOTAL DANS LE PANIER
    $scope.getTotalPrix = function(){
        var total = 0;
        for(var i = 0; i &lt; $scope.panier.length; i++){
            total += $scope.panier[i].prix*$scope.panier[i].quantite;
        }
        total = Math.round(total,2);    
        return total;
    }
    
    $scope.setArticlelQuantite = function(id){
        return article.quantite;
    }

    // Gestion de l'input quantité dans le panier.
    $scope.ajouterDansPanier = function(index,id) {//Id=id de l'article
        console.log($scope.panier[index].quantite ); 
        var qty =     $scope.panier[index].quantite;
        if($scope.panier[index].quantite&lt;1){
            $scope.panier.splice(index, 1);
        }
        for (i=0;i&lt;$scope.stock.length;i++){
            if (id == $scope.stock[i].id ){
                $scope.stock[i].commandes = qty;
            }            
        }
    }      

});

&lt;/script&gt;

&lt;/head&gt;
&lt;body ng-controller='Controleur' &gt;
    
    &lt;img src="monpetitstock.jpg" class="logo"&gt;&lt;/img&gt;

    &lt;div  id="panier" &gt;&lt;h1&gt;Votre Panier : {{getTotalPrix()}} &euro;&lt;/h1&gt;
        
        &lt;table class="CSSTableGenerator" &gt;
            &lt;tr &gt;
                &lt;th &gt;Index&lt;/th&gt;
                &lt;th &gt;id&lt;/th&gt;
                &lt;th &gt;Photo&lt;/th&gt;
                &lt;th &gt;Nom&lt;/th&gt;
                &lt;th &gt;Quantité&lt;/th&gt;
                &lt;th &gt;Prix&lt;/th&gt;
                &lt;th&gt;Total&lt;/th&gt;
                &lt;th&gt;Supprimer&lt;/th&gt;
            &lt;/tr&gt;
        
            &lt;tr ng-repeat='article in panier ' "&gt;
                &lt;td&gt;{{$index}}&lt;/td&gt;
                &lt;td&gt;{{article.id}}&lt;/td&gt;
                &lt;td&gt;&lt;img src="{{article.photo}}" class="img"&gt; &lt;/img&gt;&lt;/td&gt;
                &lt;td&gt;{{article.nom}}&lt;/td&gt;
                &lt;td &gt;&lt;input ng-model='article.quantite' ng-change="ajouterDansPanier($index,article.id)" type="number" class="champsnombre" &gt; &lt;/td&gt;
                &lt;td&gt;{{article.prix |  currency:"&euro;"}}&lt;/td&gt;
                &lt;td&gt;{{article.prix * article.quantite |  currency:"&euro;"}}&lt;/td&gt;
                &lt;td&gt;&lt;button ng-click="supprimer($index)"&gt;Supprimer&lt;/button&gt;&lt;/td&gt;
            &lt;/tr&gt;
        &lt;/table&gt;
    &lt;/div&gt;


    &lt;div  id="stock"&gt;&lt;h1&gt; Stock&lt;/h1&gt;
            
            &lt;table class="CSSTableGenerator "&gt;
            &lt;tr &gt;
                            &lt;th ng-click="predicate=''"&gt;Index&lt;/th&gt;
                            &lt;th ng-click="predicate=''"&gt;Id&lt;/th&gt;
                            &lt;th ng-click="predicate=''"&gt; Photo&lt;/th&gt;
                            &lt;th ng-click="predicate='nom'"&gt;Nom&lt;/th&gt;
                            &lt;th ng-click="predicate='categorie'"&gt;Catégorie&lt;/th&gt;
                            &lt;th ng-click="predicate='quantite'"&gt;En stock&lt;/th&gt;
                            &lt;th ng-click="predicate='commandes'"&gt;Commandes&lt;/th&gt;
                            &lt;th ng-click="predicate='prix'"&gt; Prix&lt;/th&gt;
                            &lt;th ng-click="predicate=''" &gt; Ajouter&lt;/th&gt;
            &lt;/tr&gt;
            &lt;tr ng-repeat="article in stock | orderBy:predicate" ng-class="{ highlight: article.quantite-article.commandes &lt; 4 ,highlightred: article.quantite-article.commandes &lt; 1 }" &gt;
                &lt;td&gt;{{$index}}&lt;/td&gt;
                &lt;td&gt;{{article.id}}&lt;/td&gt;
                &lt;td&gt; &lt;img src="{{article.photo}}" class="img"&gt; &lt;/td&gt;
                &lt;td&gt;{{article.nom}}&lt;/td&gt;
                &lt;td&gt;{{article.categorie}}&lt;/td&gt;
                &lt;td&gt;{{article.quantite-article.commandes}}&lt;/td&gt;
                &lt;td&gt;{{article.commandes}}&lt;/td&gt;
                &lt;td&gt;{{article.prix |  currency:"&euro;"}}&lt;/td&gt;
                &lt;td&gt;&lt;button ng-click="ajouter($index,article.id)"&gt;Ajouter au Panier&lt;/button&gt;&lt;/td&gt;
            &lt;/tr&gt;
            &lt;/table&gt;

    &lt;/div&gt;          
&lt;/body&gt;
&lt;/html&gt;

&lt;style&gt;

body{
    background-color:white;
}

.logo{
    max-height:50px;
}

.img{
    max-height:50px;
}

#panier{
    margin-left:auto;
    margin-top:auto;
    margin-right:auto;
    margin-bottom:auto;
    width:1000px;
    height:400px;
    overflow:auto;
    max-height:400px;
    background-color:#ffffff;
}
.champsnombre{
    width:60px;
}

#stock{
    margin-left:auto;
    margin-top:auto;
    margin-right:auto;
    margin-bottom:auto;
}

.highlight {
    background-color: orange;
}

.highlightred {
    background-color: red;
    
}

th {
    cursor: pointer;
    background-color:ivory;
    hover:
}

th:hover {
    cursor: pointer;
    background-color:lightgrey;
    
}

h1{    
    font-size:1.4em;
    text-align: center;
    background-color:lightgrey;
    -webkit-border-radius: 10px 10px 10px 10px;
    border-radius: 10px 10px 10px 10px;
    -webkit-box-shadow:inset 2px 2px 2px 2px #635453;
    box-shadow:inset 2px 2px 2px 2px #635453;
}

&lt;/style&gt;



Publicités